gnunet-svn
[Top][All Lists]
Advanced

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

[taler-taler-android] branch master updated (36821f9 -> ce5a1d2)


From: gnunet
Subject: [taler-taler-android] branch master updated (36821f9 -> ce5a1d2)
Date: Thu, 03 Sep 2020 18:41:57 +0200

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

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

    from 36821f9  [cashier] amount, not balance amount (following ISO 20022)
     new d56c5ea  Don't crash on empty manual withdrawal amount
     new 74b1394  [cashier] migrate to view binding as kotlin extensions are 
broken
     new 126b071  [pos] migrate to view binding
     new 85c344b  [wallet] migrate away from kotlin view extensions
     new 1cb9161  [pos] make app work on API 24+
     new ce5a1d2  [wallet] show different withdrawal error message when app is 
offline

The 6 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:
 cashier/build.gradle                               |   5 +-
 cashier/proguard-rules.pro                         |   2 +
 .../main/java/net/taler/cashier/BalanceFragment.kt |  57 ++++++------
 .../main/java/net/taler/cashier/MainActivity.kt    |   9 +-
 .../main/java/net/taler/cashier/config/Config.kt   |   1 -
 .../net/taler/cashier/config/ConfigFragment.kt     |  65 ++++++-------
 .../net/taler/cashier/withdraw/ErrorFragment.kt    |  13 ++-
 .../taler/cashier/withdraw/TransactionFragment.kt  |  58 ++++++------
 merchant-terminal/build.gradle                     |   7 +-
 .../java/net/taler/merchantpos/MainActivity.kt     |  26 +++---
 .../merchantpos/config/ConfigFetcherFragment.kt    |   7 +-
 .../net/taler/merchantpos/config/ConfigFragment.kt |  61 ++++++------
 .../java/net/taler/merchantpos/config/PosConfig.kt |   4 +-
 .../taler/merchantpos/history/HistoryFragment.kt   |  18 ++--
 .../taler/merchantpos/order/CategoriesFragment.kt  |  15 +--
 .../net/taler/merchantpos/order/OrderFragment.kt   |  50 +++++-----
 .../taler/merchantpos/order/OrderStateFragment.kt  |  31 ++++---
 .../taler/merchantpos/order/ProductsFragment.kt    |  14 +--
 .../merchantpos/payment/PaymentSuccessFragment.kt  |  10 +-
 .../merchantpos/payment/ProcessPaymentFragment.kt  |  28 +++---
 .../net/taler/merchantpos/refund/RefundFragment.kt |  42 +++++----
 .../taler/merchantpos/refund/RefundUriFragment.kt  |  19 ++--
 .../src/main/res/layout/activity_main.xml          |   1 +
 .../main/java/net/taler/common/ContractTerms.kt    |   9 +-
 wallet/build.gradle                                |   9 +-
 .../src/main/java/net/taler/wallet/MainActivity.kt |  39 ++++----
 .../src/main/java/net/taler/wallet/MainFragment.kt |  14 +--
 .../main/java/net/taler/wallet/UriInputFragment.kt |  23 +++--
 .../net/taler/wallet/balances/BalancesFragment.kt  |  20 ++--
 .../taler/wallet/exchanges/ExchangeListFragment.kt |  26 +++---
 .../wallet/exchanges/SelectExchangeFragment.kt     |  29 +++---
 .../taler/wallet/payment/AlreadyPaidFragment.kt    |  10 +-
 .../taler/wallet/payment/ProductImageFragment.kt   |  10 +-
 .../taler/wallet/payment/PromptPaymentFragment.kt  |  56 +++++------
 .../wallet/pending/PendingOperationsFragment.kt    |  11 ++-
 .../net/taler/wallet/settings/SettingsFragment.kt  |   7 +-
 .../transactions/TransactionDetailFragment.kt      | 103 +++------------------
 .../TransactionPaymentFragment.kt}                 |  42 +++++----
 .../transactions/TransactionRefreshFragment.kt     |  56 +++++++++++
 .../transactions/TransactionRefundFragment.kt      |  61 ++++++++++++
 .../transactions/TransactionWithdrawalFragment.kt  |  68 ++++++++++++++
 .../net/taler/wallet/transactions/Transactions.kt  |  19 ++--
 .../wallet/transactions/TransactionsFragment.kt    |  45 ++++-----
 .../net/taler/wallet/withdraw/ErrorFragment.kt     |  26 ++++--
 .../wallet/withdraw/ManualWithdrawFragment.kt      |  32 ++++---
 .../wallet/withdraw/PromptWithdrawFragment.kt      |  48 +++++-----
 .../wallet/withdraw/ReviewExchangeTosFragment.kt   |  29 +++---
 .../net/taler/wallet/withdraw/WithdrawManager.kt   |  34 +++----
 wallet/src/main/res/layout/activity_main.xml       |   3 +-
 .../{app_bar_main.xml => app_content_main.xml}     |   0
 .../main/res/layout/fragment_prompt_payment.xml    |   8 +-
 wallet/src/main/res/layout/payment_bottom_bar.xml  |   1 +
 wallet/src/main/res/navigation/nav_graph.xml       |  38 +++++++-
 wallet/src/main/res/values/strings.xml             |   3 +
 54 files changed, 827 insertions(+), 595 deletions(-)
 copy wallet/src/main/java/net/taler/wallet/{payment/ProductImageFragment.kt => 
transactions/TransactionPaymentFragment.kt} (53%)
 create mode 100644 
wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
 create mode 100644 
wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt
 create mode 100644 
wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
 rename wallet/src/main/res/layout/{app_bar_main.xml => app_content_main.xml} 
(100%)

diff --git a/cashier/build.gradle b/cashier/build.gradle
index f5c56a9..a749353 100644
--- a/cashier/build.gradle
+++ b/cashier/build.gradle
@@ -17,7 +17,6 @@
 plugins {
     id "com.android.application"
     id "kotlin-android"
-    id "kotlin-android-extensions"
     id "kotlinx-serialization"
     id "androidx.navigation.safeargs.kotlin"
 }
@@ -51,6 +50,10 @@ android {
         jvmTarget = "1.8"
     }
 
+    buildFeatures {
+        viewBinding = true
+    }
+
     packagingOptions {
         exclude("META-INF/*.kotlin_module")
     }
diff --git a/cashier/proguard-rules.pro b/cashier/proguard-rules.pro
index f612b29..a1cc1f6 100644
--- a/cashier/proguard-rules.pro
+++ b/cashier/proguard-rules.pro
@@ -20,6 +20,8 @@
 # hide the original source file name.
 #-renamesourcefileattribute SourceFile
 
+-keep class net.taler.cashier.** {*;}
+
 # androidx.security:security-crypto
 # https://github.com/google/tink/issues/361
 -keep class * extends 
com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite { *; }
diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt 
b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
index cdfa142..1114080 100644
--- a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
@@ -29,8 +29,8 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_balance.*
 import 
net.taler.cashier.BalanceFragmentDirections.Companion.actionBalanceFragmentToTransactionFragment
+import net.taler.cashier.databinding.FragmentBalanceBinding
 import net.taler.cashier.withdraw.LastTransaction
 import net.taler.cashier.withdraw.WithdrawStatus
 import net.taler.common.exhaustive
@@ -50,12 +50,15 @@ class BalanceFragment : Fragment() {
     private val configManager by lazy { viewModel.configManager}
     private val withdrawManager by lazy { viewModel.withdrawManager }
 
+    private lateinit var ui: FragmentBalanceBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
         setHasOptionsMenu(true)
-        return inflater.inflate(R.layout.fragment_balance, container, false)
+        ui = FragmentBalanceBinding.inflate(layoutInflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -65,24 +68,24 @@ class BalanceFragment : Fragment() {
         viewModel.balance.observe(viewLifecycleOwner, Observer { result ->
             onBalanceUpdated(result)
         })
-        button5.setOnClickListener { onAmountButtonPressed(5) }
-        button10.setOnClickListener { onAmountButtonPressed(10) }
-        button20.setOnClickListener { onAmountButtonPressed(20) }
-        button50.setOnClickListener { onAmountButtonPressed(50) }
+        ui.button5.setOnClickListener { onAmountButtonPressed(5) }
+        ui.button10.setOnClickListener { onAmountButtonPressed(10) }
+        ui.button20.setOnClickListener { onAmountButtonPressed(20) }
+        ui.button50.setOnClickListener { onAmountButtonPressed(50) }
 
         if (savedInstanceState != null) {
-            
amountView.editText!!.setText(savedInstanceState.getCharSequence("amountView"))
+            
ui.amountView.editText!!.setText(savedInstanceState.getCharSequence("amountView"))
         }
-        amountView.editText!!.setOnEditorActionListener { _, actionId, _ ->
+        ui.amountView.editText!!.setOnEditorActionListener { _, actionId, _ ->
             if (actionId == EditorInfo.IME_ACTION_GO) {
                 onAmountConfirmed(getAmountFromView())
                 true
             } else false
         }
         configManager.currency.observe(viewLifecycleOwner, Observer { currency 
->
-            currencyView.text = currency
+            ui.currencyView.text = currency
         })
-        confirmWithdrawalButton.setOnClickListener { 
onAmountConfirmed(getAmountFromView()) }
+        ui.confirmWithdrawalButton.setOnClickListener { 
onAmountConfirmed(getAmountFromView()) }
     }
 
     override fun onStart() {
@@ -96,7 +99,7 @@ class BalanceFragment : Fragment() {
     override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
         // for some reason automatic restore isn't working at the moment!?
-        amountView?.editText?.text.let {
+        ui.amountView.editText?.text.let {
             outState.putCharSequence("amountView", it)
         }
     }
@@ -121,34 +124,34 @@ class BalanceFragment : Fragment() {
 
     private fun onBalanceUpdated(result: BalanceResult) {
         val uiList = listOf(
-            introView,
-            button5, button10, button20, button50,
-            amountView, currencyView, confirmWithdrawalButton
+            ui.introView,
+            ui.button5, ui.button10, ui.button20, ui.button50,
+            ui.amountView, ui.currencyView, ui.confirmWithdrawalButton
         )
         when (result) {
             is BalanceResult.Success -> {
-                balanceView.text = result.amount.toString()
+                ui.balanceView.text = result.amount.toString()
                 uiList.forEach { it.fadeIn() }
             }
             is BalanceResult.Error -> {
-                balanceView.text = getString(R.string.balance_error, 
result.msg)
+                ui.balanceView.text = getString(R.string.balance_error, 
result.msg)
                 uiList.forEach { it.fadeOut() }
             }
             BalanceResult.Offline -> {
-                balanceView.text = getString(R.string.balance_offline)
+                ui.balanceView.text = getString(R.string.balance_offline)
                 uiList.forEach { it.fadeOut() }
             }
         }.exhaustive
-        progressBar.fadeOut()
+        ui.progressBar.fadeOut()
     }
 
     private fun onAmountButtonPressed(amount: Int) {
-        amountView.editText!!.setText(amount.toString())
-        amountView.error = null
+        ui.amountView.editText!!.setText(amount.toString())
+        ui.amountView.error = null
     }
 
     private fun getAmountFromView(): Amount {
-        val str = amountView.editText!!.text.toString()
+        val str = ui.amountView.editText!!.text.toString()
         val currency = configManager.currency.value!!
         if (str.isBlank()) return Amount.zero(currency)
         return Amount.fromString(currency, str)
@@ -156,11 +159,11 @@ class BalanceFragment : Fragment() {
 
     private fun onAmountConfirmed(amount: Amount) {
         if (amount.isZero()) {
-            amountView.error = getString(R.string.withdraw_error_zero)
+            ui.amountView.error = getString(R.string.withdraw_error_zero)
         } else if (!withdrawManager.hasSufficientBalance(amount)) {
-            amountView.error = 
getString(R.string.withdraw_error_insufficient_balance)
+            ui.amountView.error = 
getString(R.string.withdraw_error_insufficient_balance)
         } else {
-            amountView.error = null
+            ui.amountView.error = null
             withdrawManager.withdraw(amount)
             actionBalanceFragmentToTransactionFragment().let {
                 findNavController().navigate(it)
@@ -177,13 +180,13 @@ class BalanceFragment : Fragment() {
             is WithdrawStatus.Aborted -> 
getString(R.string.transaction_last_aborted)
             else -> getString(R.string.transaction_last_error)
         }
-        lastTransactionView.text = text
+        ui.lastTransactionView.text = text
         val drawable = if (status == WithdrawStatus.Success)
             R.drawable.ic_check_circle
         else
             R.drawable.ic_error
-        
lastTransactionView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable, 
0, 0, 0)
-        lastTransactionView.visibility = VISIBLE
+        
ui.lastTransactionView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawable,
 0, 0, 0)
+        ui.lastTransactionView.visibility = VISIBLE
     }
 
 }
diff --git a/cashier/src/main/java/net/taler/cashier/MainActivity.kt 
b/cashier/src/main/java/net/taler/cashier/MainActivity.kt
index ae31be5..2f4c4ec 100644
--- a/cashier/src/main/java/net/taler/cashier/MainActivity.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainActivity.kt
@@ -25,18 +25,21 @@ import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
 import androidx.navigation.NavController
 import androidx.navigation.fragment.NavHostFragment
-import kotlinx.android.synthetic.main.activity_main.*
+import net.taler.cashier.databinding.ActivityMainBinding
 
 class MainActivity : AppCompatActivity() {
 
     private val viewModel: MainViewModel by viewModels()
     private val configManager by lazy { viewModel.configManager}
+
+    private lateinit var ui: ActivityMainBinding
     private lateinit var nav: NavController
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        setContentView(R.layout.activity_main)
-        setSupportActionBar(toolbar)
+        ui = ActivityMainBinding.inflate(layoutInflater)
+        setContentView(ui.root)
+        setSupportActionBar(ui.toolbar)
         val navHostFragment =
             supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as 
NavHostFragment
         nav = navHostFragment.navController
diff --git a/cashier/src/main/java/net/taler/cashier/config/Config.kt 
b/cashier/src/main/java/net/taler/cashier/config/Config.kt
index b50cf92..c898f95 100644
--- a/cashier/src/main/java/net/taler/cashier/config/Config.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/Config.kt
@@ -17,7 +17,6 @@
 package net.taler.cashier.config
 
 import kotlinx.serialization.Serializable
-import net.taler.lib.common.Version
 import okhttp3.Credentials
 
 data class Config(
diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt 
b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
index a7aaf2f..6498590 100644
--- a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
@@ -33,9 +33,9 @@ import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
 import com.google.android.material.snackbar.Snackbar
-import kotlinx.android.synthetic.main.fragment_config.*
 import net.taler.cashier.MainViewModel
 import net.taler.cashier.R
+import net.taler.cashier.databinding.FragmentConfigBinding
 import net.taler.common.exhaustive
 
 private const val URL_BANK_TEST = "https://bank.test.taler.net";
@@ -46,38 +46,41 @@ class ConfigFragment : Fragment() {
     private val viewModel: MainViewModel by activityViewModels()
     private val configManager by lazy { viewModel.configManager}
 
+    private lateinit var ui: FragmentConfigBinding
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_config, container, false)
+        ui = FragmentConfigBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         if (savedInstanceState == null) {
             if (configManager.config.bankUrl.isBlank()) {
-                urlView.editText!!.setText(URL_BANK_TEST)
+                ui.urlView.editText!!.setText(URL_BANK_TEST)
             } else {
-                urlView.editText!!.setText(configManager.config.bankUrl)
+                ui.urlView.editText!!.setText(configManager.config.bankUrl)
             }
-            usernameView.editText!!.setText(configManager.config.username)
-            passwordView.editText!!.setText(configManager.config.password)
+            ui.usernameView.editText!!.setText(configManager.config.username)
+            ui.passwordView.editText!!.setText(configManager.config.password)
         } else {
-            
urlView.editText!!.setText(savedInstanceState.getCharSequence("urlView"))
-            
usernameView.editText!!.setText(savedInstanceState.getCharSequence("usernameView"))
-            
passwordView.editText!!.setText(savedInstanceState.getCharSequence("passwordView"))
+            
ui.urlView.editText!!.setText(savedInstanceState.getCharSequence("urlView"))
+            
ui.usernameView.editText!!.setText(savedInstanceState.getCharSequence("usernameView"))
+            
ui.passwordView.editText!!.setText(savedInstanceState.getCharSequence("passwordView"))
         }
-        saveButton.setOnClickListener {
+        ui.saveButton.setOnClickListener {
             val config = Config(
-                bankUrl = urlView.editText!!.text.toString(),
-                username = usernameView.editText!!.text.toString(),
-                password = passwordView.editText!!.text.toString()
+                bankUrl = ui.urlView.editText!!.text.toString(),
+                username = ui.usernameView.editText!!.text.toString(),
+                password = ui.passwordView.editText!!.text.toString()
             )
             if (checkConfig(config)) {
                 // show progress
-                saveButton.visibility = INVISIBLE
-                progressBar.visibility = VISIBLE
+                ui.saveButton.visibility = INVISIBLE
+                ui.progressBar.visibility = VISIBLE
                 // kick off check and observe result
                 configManager.checkAndSaveConfig(config)
                 configManager.configResult.observe(viewLifecycleOwner, 
onConfigResult)
@@ -87,43 +90,43 @@ class ConfigFragment : Fragment() {
                 inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
             }
         }
-        demoView.text = HtmlCompat.fromHtml(
+        ui.demoView.text = HtmlCompat.fromHtml(
             getString(R.string.config_demo_hint, URL_BANK_TEST_REGISTER), 
FROM_HTML_MODE_LEGACY
         )
-        demoView.movementMethod = LinkMovementMethod.getInstance()
+        ui.demoView.movementMethod = LinkMovementMethod.getInstance()
     }
 
     override fun onStart() {
         super.onStart()
         // focus on password if it is the only missing value (like after 
locking)
-        if (urlView.editText!!.text.isNotBlank()
-            && usernameView.editText!!.text.isNotBlank()
-            && passwordView.editText!!.text.isBlank()
+        if (ui.urlView.editText!!.text.isNotBlank()
+            && ui.usernameView.editText!!.text.isNotBlank()
+            && ui.passwordView.editText!!.text.isBlank()
         ) {
-            passwordView.editText!!.requestFocus()
+            ui.passwordView.editText!!.requestFocus()
         }
     }
 
     override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
         // for some reason automatic restore isn't working at the moment!?
-        outState.putCharSequence("urlView", urlView.editText?.text)
-        outState.putCharSequence("usernameView", usernameView.editText?.text)
-        outState.putCharSequence("passwordView", passwordView.editText?.text)
+        outState.putCharSequence("urlView", ui.urlView.editText?.text)
+        outState.putCharSequence("usernameView", 
ui.usernameView.editText?.text)
+        outState.putCharSequence("passwordView", 
ui.passwordView.editText?.text)
     }
 
     private fun checkConfig(config: Config): Boolean {
         if (!config.bankUrl.startsWith("https://";)) {
-            urlView.error = getString(R.string.config_bank_url_error)
-            urlView.requestFocus()
+            ui.urlView.error = getString(R.string.config_bank_url_error)
+            ui.urlView.requestFocus()
             return false
         }
         if (config.username.isBlank()) {
-            usernameView.error = getString(R.string.config_username_error)
-            usernameView.requestFocus()
+            ui.usernameView.error = getString(R.string.config_username_error)
+            ui.usernameView.requestFocus()
             return false
         }
-        urlView.isErrorEnabled = false
+        ui.urlView.isErrorEnabled = false
         return true
     }
 
@@ -146,8 +149,8 @@ class ConfigFragment : Fragment() {
                 }
             }
         }.exhaustive
-        saveButton.visibility = VISIBLE
-        progressBar.visibility = INVISIBLE
+        ui.saveButton.visibility = VISIBLE
+        ui.progressBar.visibility = INVISIBLE
         configManager.configResult.removeObservers(viewLifecycleOwner)
     }
 
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
index ea33b0d..4f98847 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/ErrorFragment.kt
@@ -24,33 +24,36 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_error.*
 import net.taler.cashier.MainViewModel
 import net.taler.cashier.R
+import net.taler.cashier.databinding.FragmentErrorBinding
 
 class ErrorFragment : Fragment() {
 
     private val viewModel: MainViewModel by activityViewModels()
     private val withdrawManager by lazy { viewModel.withdrawManager }
 
+    private lateinit var ui: FragmentErrorBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_error, container, false)
+        ui = FragmentErrorBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { 
status ->
             if (status == null) return@Observer
             if (status is WithdrawStatus.Aborted) {
-                textView.setText(R.string.transaction_aborted)
+                ui.textView.setText(R.string.transaction_aborted)
             } else if (status is WithdrawStatus.Error) {
-                textView.text = status.msg
+                ui.textView.text = status.msg
             }
             withdrawManager.completeTransaction()
         })
-        backButton.setOnClickListener {
+        ui.backButton.setOnClickListener {
             findNavController().popBackStack()
         }
     }
diff --git 
a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt 
b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
index 0726a77..ffb1539 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/TransactionFragment.kt
@@ -25,11 +25,10 @@ import android.view.ViewGroup
 import androidx.core.content.ContextCompat.getColor
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_transaction.*
 import net.taler.cashier.MainViewModel
 import net.taler.cashier.R
+import net.taler.cashier.databinding.FragmentTransactionBinding
 import 
net.taler.cashier.withdraw.TransactionFragmentDirections.Companion.actionTransactionFragmentToBalanceFragment
 import 
net.taler.cashier.withdraw.TransactionFragmentDirections.Companion.actionTransactionFragmentToErrorFragment
 import net.taler.cashier.withdraw.WithdrawResult.Error
@@ -46,30 +45,33 @@ class TransactionFragment : Fragment() {
     private val withdrawManager by lazy { viewModel.withdrawManager }
     private val nfcManager = NfcManager()
 
+    private lateinit var ui: FragmentTransactionBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_transaction, container, 
false)
+        ui = FragmentTransactionBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        withdrawManager.withdrawAmount.observe(viewLifecycleOwner, Observer { 
amount ->
-            amountView.text = amount?.toString()
+        withdrawManager.withdrawAmount.observe(viewLifecycleOwner, { amount ->
+            ui.amountView.text = amount?.toString()
         })
-        withdrawManager.withdrawResult.observe(viewLifecycleOwner, Observer { 
result ->
+        withdrawManager.withdrawResult.observe(viewLifecycleOwner, { result ->
             onWithdrawResultReceived(result)
         })
-        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { 
status ->
+        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, { status ->
             onWithdrawStatusChanged(status)
         })
 
         // change intro text depending on whether NFC is available or not
         val hasNfc = NfcManager.hasNfc(requireContext())
         val intro = if (hasNfc) R.string.transaction_intro_nfc else 
R.string.transaction_intro
-        introView.setText(intro)
+        ui.introView.setText(intro)
 
-        cancelButton.setOnClickListener {
+        ui.cancelButton.setOnClickListener {
             findNavController().popBackStack()
         }
     }
@@ -95,9 +97,9 @@ class TransactionFragment : Fragment() {
 
     private fun onWithdrawResultReceived(result: WithdrawResult?) {
         if (result != null) {
-            progressBar.animate()
+            ui.progressBar.animate()
                 .alpha(0f)
-                .withEndAction { progressBar?.visibility = INVISIBLE }
+                .withEndAction { ui.progressBar.visibility = INVISIBLE }
                 .setDuration(750)
                 .start()
         }
@@ -113,12 +115,12 @@ class TransactionFragment : Fragment() {
                     nfcManager
                 )
                 // show QR code
-                qrCodeView.alpha = 0f
-                qrCodeView.animate()
+                ui.qrCodeView.alpha = 0f
+                ui.qrCodeView.animate()
                     .alpha(1f)
                     .withStartAction {
-                        qrCodeView.visibility = VISIBLE
-                        qrCodeView.setImageBitmap(result.qrCode)
+                        ui.qrCodeView.visibility = VISIBLE
+                        ui.qrCodeView.setImageBitmap(result.qrCode)
                     }
                     .setDuration(750)
                     .start()
@@ -129,30 +131,30 @@ class TransactionFragment : Fragment() {
 
     private fun setErrorMsg(str: String) {
         val c = getColor(requireContext(), R.color.design_default_color_error)
-        introView.setTextColor(c)
-        introView.text = str
+        ui.introView.setTextColor(c)
+        ui.introView.text = str
     }
 
     private fun onWithdrawStatusChanged(status: WithdrawStatus?): Any = when 
(status) {
         is WithdrawStatus.SelectionDone -> {
-            qrCodeView.fadeOut {
-                qrCodeView?.setImageResource(R.drawable.ic_arrow)
-                qrCodeView?.fadeIn()
+            ui.qrCodeView.fadeOut {
+                ui.qrCodeView.setImageResource(R.drawable.ic_arrow)
+                ui.qrCodeView.fadeIn()
             }
-            introView.fadeOut {
-                introView?.text = getString(R.string.transaction_intro_scanned)
-                introView?.fadeIn {
-                    confirmButton?.isEnabled = true
-                    confirmButton?.setOnClickListener {
+            ui.introView.fadeOut {
+                ui.introView.text = 
getString(R.string.transaction_intro_scanned)
+                ui.introView.fadeIn {
+                    ui.confirmButton.isEnabled = true
+                    ui.confirmButton.setOnClickListener {
                         withdrawManager.confirm(status.withdrawalId)
                     }
                 }
             }
         }
         is WithdrawStatus.Confirming -> {
-            confirmButton.isEnabled = false
-            qrCodeView.fadeOut()
-            progressBar.fadeIn()
+            ui.confirmButton.isEnabled = false
+            ui.qrCodeView.fadeOut()
+            ui.progressBar.fadeIn()
         }
         is WithdrawStatus.Success -> {
             withdrawManager.completeTransaction()
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index f7bbc1c..df8cee5 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -1,7 +1,6 @@
 plugins {
     id 'com.android.application'
     id 'kotlin-android'
-    id 'kotlin-android-extensions'
     id 'kotlinx-serialization'
     id 'androidx.navigation.safeargs.kotlin'
 }
@@ -13,7 +12,7 @@ android {
 
     defaultConfig {
         applicationId "net.taler.merchantpos"
-        minSdkVersion 26
+        minSdkVersion 24
         targetSdkVersion 29
         versionCode 1
         versionName "1.0"
@@ -40,6 +39,10 @@ android {
         jvmTarget = "1.8"
     }
 
+    buildFeatures {
+        viewBinding = true
+    }
+
     testOptions {
         unitTests {
             includeAndroidResources = true
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 533c540..47da74e 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -28,21 +28,20 @@ import android.widget.Toast.LENGTH_SHORT
 import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.GravityCompat.START
-import androidx.lifecycle.Observer
 import androidx.navigation.NavController
 import androidx.navigation.fragment.NavHostFragment
 import androidx.navigation.ui.AppBarConfiguration
 import androidx.navigation.ui.setupWithNavController
 import 
com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener
-import kotlinx.android.synthetic.main.activity_main.*
-import kotlinx.android.synthetic.main.app_bar_main.*
 import net.taler.common.NfcManager
+import net.taler.merchantpos.databinding.ActivityMainBinding
 
 class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener {
 
     private val model: MainViewModel by viewModels()
     private val nfcManager = NfcManager()
 
+    private lateinit var ui: ActivityMainBinding
     private lateinit var nav: NavController
 
     private var reallyExit = false
@@ -53,9 +52,10 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        setContentView(R.layout.activity_main)
+        ui = ActivityMainBinding.inflate(layoutInflater)
+        setContentView(ui.root)
 
-        model.paymentManager.payment.observe(this, Observer { payment ->
+        model.paymentManager.payment.observe(this, { payment ->
             payment?.talerPayUri?.let {
                 nfcManager.setTagString(it)
             }
@@ -65,12 +65,12 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
             supportFragmentManager.findFragmentById(R.id.navHostFragment) as 
NavHostFragment
         nav = navHostFragment.navController
 
-        nav_view.setupWithNavController(nav)
-        nav_view.setNavigationItemSelectedListener(this)
+        ui.navView.setupWithNavController(nav)
+        ui.navView.setNavigationItemSelectedListener(this)
 
-        setSupportActionBar(toolbar)
-        val appBarConfiguration = AppBarConfiguration(nav.graph, drawer_layout)
-        toolbar.setupWithNavController(nav, appBarConfiguration)
+        setSupportActionBar(ui.main.toolbar)
+        val appBarConfiguration = AppBarConfiguration(nav.graph, 
ui.drawerLayout)
+        ui.main.toolbar.setupWithNavController(nav, appBarConfiguration)
     }
 
     override fun onStart() {
@@ -99,14 +99,14 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener {
             R.id.nav_history -> 
nav.navigate(R.id.action_global_merchantHistory)
             R.id.nav_settings -> 
nav.navigate(R.id.action_global_merchantSettings)
         }
-        drawer_layout.closeDrawer(START)
+        ui.drawerLayout.closeDrawer(START)
         return true
     }
 
     override fun onBackPressed() {
         val currentDestination = nav.currentDestination?.id
-        if (drawer_layout.isDrawerOpen(START)) {
-            drawer_layout.closeDrawer(START)
+        if (ui.drawerLayout.isDrawerOpen(START)) {
+            ui.drawerLayout.closeDrawer(START)
         } else if (currentDestination == R.id.nav_settings && 
!model.configManager.config.isValid()) {
             // we are in the configuration screen and need a config to continue
             val intent = Intent(ACTION_MAIN).apply {
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
index 8231052..87004d8 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
@@ -27,20 +27,23 @@ import 
com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
 import com.google.android.material.snackbar.Snackbar
 import net.taler.common.navigate
 import net.taler.merchantpos.MainViewModel
-import net.taler.merchantpos.R
 import 
net.taler.merchantpos.config.ConfigFetcherFragmentDirections.Companion.actionConfigFetcherToMerchantSettings
 import 
net.taler.merchantpos.config.ConfigFetcherFragmentDirections.Companion.actionConfigFetcherToOrder
+import net.taler.merchantpos.databinding.FragmentConfigFetcherBinding
 
 class ConfigFetcherFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val configManager by lazy { model.configManager }
 
+    private lateinit var ui: FragmentConfigFetcherBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_config_fetcher, container, 
false)
+        ui = FragmentConfigFetcherBinding.inflate(inflater)
+        return ui.root
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
index daddbff..c31eb61 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFragment.kt
@@ -30,11 +30,11 @@ import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
 import com.google.android.material.snackbar.Snackbar
-import kotlinx.android.synthetic.main.fragment_merchant_config.*
 import net.taler.common.navigate
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
 import 
net.taler.merchantpos.config.ConfigFragmentDirections.Companion.actionSettingsToOrder
+import net.taler.merchantpos.databinding.FragmentMerchantConfigBinding
 import net.taler.merchantpos.topSnackbar
 
 /**
@@ -45,86 +45,89 @@ class ConfigFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private val configManager by lazy { model.configManager }
 
+    private lateinit var ui: FragmentMerchantConfigBinding
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_merchant_config, container, 
false)
+        ui = FragmentMerchantConfigBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        configUrlView.editText!!.setOnFocusChangeListener { _, hasFocus ->
+        ui.configUrlView.editText!!.setOnFocusChangeListener { _, hasFocus ->
             if (!hasFocus) checkForUrlCredentials()
         }
-        okButton.setOnClickListener {
+        ui.okButton.setOnClickListener {
             checkForUrlCredentials()
-            val inputUrl = configUrlView.editText!!.text
+            val inputUrl = ui.configUrlView.editText!!.text
             val url = if (inputUrl.startsWith("http")) {
                 inputUrl.toString()
             } else {
-                "https://$inputUrl".also { 
configUrlView.editText!!.setText(it) }
+                "https://$inputUrl".also { 
ui.configUrlView.editText!!.setText(it) }
             }
-            progressBar.visibility = VISIBLE
-            okButton.visibility = INVISIBLE
+            ui.progressBar.visibility = VISIBLE
+            ui.okButton.visibility = INVISIBLE
             val config = Config(
                 configUrl = url,
-                username = usernameView.editText!!.text.toString(),
-                password = passwordView.editText!!.text.toString()
+                username = ui.usernameView.editText!!.text.toString(),
+                password = ui.passwordView.editText!!.text.toString()
             )
-            configManager.fetchConfig(config, true, 
savePasswordCheckBox.isChecked)
+            configManager.fetchConfig(config, true, 
ui.savePasswordCheckBox.isChecked)
             configManager.configUpdateResult.observe(viewLifecycleOwner, 
Observer { result ->
                 if (onConfigUpdate(result)) {
                     
configManager.configUpdateResult.removeObservers(viewLifecycleOwner)
                 }
             })
         }
-        forgetPasswordButton.setOnClickListener {
+        ui.forgetPasswordButton.setOnClickListener {
             configManager.forgetPassword()
-            passwordView.editText!!.text = null
-            forgetPasswordButton.visibility = GONE
+            ui.passwordView.editText!!.text = null
+            ui.forgetPasswordButton.visibility = GONE
         }
-        configDocsView.movementMethod = LinkMovementMethod.getInstance()
+        ui.configDocsView.movementMethod = LinkMovementMethod.getInstance()
         updateView(savedInstanceState == null)
     }
 
     override fun onStart() {
         super.onStart()
         // focus password if this is the only empty field
-        if (passwordView.editText!!.text.isBlank()
-            && !configUrlView.editText!!.text.isBlank()
-            && !usernameView.editText!!.text.isBlank()
+        if (ui.passwordView.editText!!.text.isBlank()
+            && !ui.configUrlView.editText!!.text.isBlank()
+            && !ui.usernameView.editText!!.text.isBlank()
         ) {
-            passwordView.requestFocus()
+            ui.passwordView.requestFocus()
         }
     }
 
     private fun updateView(isInitialization: Boolean = false) {
         val config = configManager.config
-        configUrlView.editText!!.setText(
+        ui.configUrlView.editText!!.setText(
             if (isInitialization && config.configUrl.isBlank()) CONFIG_URL_DEMO
             else config.configUrl
         )
-        usernameView.editText!!.setText(
+        ui.usernameView.editText!!.setText(
             if (isInitialization && config.username.isBlank()) 
CONFIG_USERNAME_DEMO
             else config.username
         )
-        passwordView.editText!!.setText(
+        ui.passwordView.editText!!.setText(
             if (isInitialization && config.password.isBlank()) 
CONFIG_PASSWORD_DEMO
             else config.password
         )
-        forgetPasswordButton.visibility = if (config.hasPassword()) VISIBLE 
else GONE
+        ui.forgetPasswordButton.visibility = if (config.hasPassword()) VISIBLE 
else GONE
     }
 
     private fun checkForUrlCredentials() {
-        val text = configUrlView.editText!!.text.toString()
+        val text = ui.configUrlView.editText!!.text.toString()
         Uri.parse(text)?.userInfo?.let { userInfo ->
             if (userInfo.contains(':')) {
                 val (user, pass) = userInfo.split(':')
                 val strippedUrl = text.replace("${userInfo}@", "")
-                configUrlView.editText!!.setText(strippedUrl)
-                usernameView.editText!!.setText(user)
-                passwordView.editText!!.setText(pass)
+                ui.configUrlView.editText!!.setText(strippedUrl)
+                ui.usernameView.editText!!.setText(user)
+                ui.passwordView.editText!!.setText(pass)
             }
         }
     }
@@ -157,8 +160,8 @@ class ConfigFragment : Fragment() {
     }
 
     private fun onResultReceived() {
-        progressBar.visibility = INVISIBLE
-        okButton.visibility = VISIBLE
+        ui.progressBar.visibility = INVISIBLE
+        ui.okButton.visibility = VISIBLE
     }
 
 }
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 cc8caf6..7def7cc 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
@@ -16,6 +16,7 @@
 
 package net.taler.merchantpos.config
 
+import android.os.Build.VERSION.SDK_INT
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import net.taler.common.ContractProduct
@@ -49,7 +50,8 @@ data class Category(
     val nameI18n: Map<String, String>? = null
 ) {
     var selected: Boolean = false
-    val localizedName: String get() = TalerUtils.getLocalizedString(nameI18n, 
name)
+    val localizedName: String
+        get() = if (SDK_INT >= 26) TalerUtils.getLocalizedString(nameI18n, 
name) else name
 }
 
 @Serializable
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
index 8cc435a..3ef48e1 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/history/HistoryFragment.kt
@@ -23,18 +23,16 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
 import com.google.android.material.snackbar.Snackbar
-import kotlinx.android.synthetic.main.fragment_merchant_history.*
 import net.taler.common.exhaustive
 import net.taler.common.navigate
 import net.taler.merchantlib.OrderHistoryEntry
 import net.taler.merchantpos.MainViewModel
-import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentMerchantHistoryBinding
 import 
net.taler.merchantpos.history.HistoryFragmentDirections.Companion.actionGlobalMerchantSettings
 import 
net.taler.merchantpos.history.HistoryFragmentDirections.Companion.actionNavHistoryToRefundFragment
 
@@ -55,31 +53,33 @@ class HistoryFragment : Fragment(), RefundClickListener {
     private val historyManager by lazy { model.historyManager }
     private val refundManager by lazy { model.refundManager }
 
+    private lateinit var ui: FragmentMerchantHistoryBinding
     private val historyListAdapter = HistoryItemAdapter(this)
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_merchant_history, container, 
false)
+        ui = FragmentMerchantHistoryBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        list_history.apply {
+        ui.listHistory.apply {
             layoutManager = LinearLayoutManager(requireContext())
             addItemDecoration(DividerItemDecoration(context, VERTICAL))
             adapter = historyListAdapter
         }
 
-        swipeRefresh.setOnRefreshListener {
+        ui.swipeRefresh.setOnRefreshListener {
             Log.v(TAG, "refreshing!")
             historyManager.fetchHistory()
         }
-        historyManager.isLoading.observe(viewLifecycleOwner, Observer { 
loading ->
+        historyManager.isLoading.observe(viewLifecycleOwner, { loading ->
             Log.v(TAG, "setting refreshing to $loading")
-            swipeRefresh.isRefreshing = loading
+            ui.swipeRefresh.isRefreshing = loading
         })
-        historyManager.items.observe(viewLifecycleOwner, Observer { result ->
+        historyManager.items.observe(viewLifecycleOwner, { result ->
             when (result) {
                 is HistoryResult.Error -> onError(result.msg)
                 is HistoryResult.Success -> 
historyListAdapter.setData(result.items)
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
index 4f8e5af..69e74ce 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/CategoriesFragment.kt
@@ -23,12 +23,10 @@ import android.view.View.INVISIBLE
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.LinearLayoutManager
-import kotlinx.android.synthetic.main.fragment_categories.*
 import net.taler.merchantpos.MainViewModel
-import net.taler.merchantpos.R
 import net.taler.merchantpos.config.Category
+import net.taler.merchantpos.databinding.FragmentCategoriesBinding
 
 interface CategorySelectionListener {
     fun onCategorySelected(category: Category)
@@ -38,6 +36,8 @@ class CategoriesFragment : Fragment(), 
CategorySelectionListener {
 
     private val viewModel: MainViewModel by activityViewModels()
     private val orderManager by lazy { viewModel.orderManager }
+
+    private lateinit var ui: FragmentCategoriesBinding
     private val adapter = CategoryAdapter(this)
 
     override fun onCreateView(
@@ -45,18 +45,19 @@ class CategoriesFragment : Fragment(), 
CategorySelectionListener {
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_categories, container, false)
+        ui = FragmentCategoriesBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        categoriesList.apply {
+        ui.categoriesList.apply {
             adapter = this@CategoriesFragment.adapter
             layoutManager = LinearLayoutManager(requireContext())
         }
 
-        orderManager.categories.observe(viewLifecycleOwner, Observer { 
categories ->
+        orderManager.categories.observe(viewLifecycleOwner, { categories ->
             adapter.setItems(categories)
-            progressBar.visibility = INVISIBLE
+            ui.progressBar.visibility = INVISIBLE
         })
     }
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
index 7291a23..1335b65 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderFragment.kt
@@ -22,12 +22,11 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.transition.TransitionManager.beginDelayedTransition
-import kotlinx.android.synthetic.main.fragment_order.*
 import net.taler.common.navigate
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentOrderBinding
 import 
net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionGlobalConfigFetcher
 import 
net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionOrderToMerchantSettings
 import 
net.taler.merchantpos.order.OrderFragmentDirections.Companion.actionOrderToProcessPayment
@@ -40,17 +39,20 @@ class OrderFragment : Fragment() {
     private val orderManager by lazy { viewModel.orderManager }
     private val paymentManager by lazy { viewModel.paymentManager }
 
+    private lateinit var ui: FragmentOrderBinding
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_order, container, false)
+        ui = FragmentOrderBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
-        orderManager.currentOrderId.observe(viewLifecycleOwner, Observer { 
orderId ->
+        orderManager.currentOrderId.observe(viewLifecycleOwner, { orderId ->
             val liveOrder = orderManager.getOrder(orderId)
             onOrderSwitched(orderId, liveOrder)
             // add a new OrderStateFragment for each order
@@ -72,39 +74,39 @@ class OrderFragment : Fragment() {
 
     private fun onOrderSwitched(orderId: Int, liveOrder: LiveOrder) {
         // order title
-        liveOrder.order.observe(viewLifecycleOwner, Observer { order ->
+        liveOrder.order.observe(viewLifecycleOwner, { order ->
             activity?.title = getString(R.string.order_label_title, 
order.title)
         })
         // restart button
-        restartButton.setOnClickListener { liveOrder.restartOrUndo() }
-        liveOrder.restartState.observe(viewLifecycleOwner, Observer { state ->
+        ui.restartButton.setOnClickListener { liveOrder.restartOrUndo() }
+        liveOrder.restartState.observe(viewLifecycleOwner, { state ->
             beginDelayedTransition(view as ViewGroup)
             if (state == UNDO) {
-                restartButton.setText(R.string.order_undo)
-                restartButton.isEnabled = true
-                completeButton.isEnabled = false
+                ui.restartButton.setText(R.string.order_undo)
+                ui.restartButton.isEnabled = true
+                ui.completeButton.isEnabled = false
             } else {
-                restartButton.setText(R.string.order_restart)
-                restartButton.isEnabled = state == ENABLED
-                completeButton.isEnabled = state == ENABLED
+                ui.restartButton.setText(R.string.order_restart)
+                ui.restartButton.isEnabled = state == ENABLED
+                ui.completeButton.isEnabled = state == ENABLED
             }
         })
         // -1 and +1 buttons
-        liveOrder.modifyOrderAllowed.observe(viewLifecycleOwner, Observer { 
allowed ->
-            minusButton.isEnabled = allowed
-            plusButton.isEnabled = allowed
+        liveOrder.modifyOrderAllowed.observe(viewLifecycleOwner, { allowed ->
+            ui.minusButton.isEnabled = allowed
+            ui.plusButton.isEnabled = allowed
         })
-        minusButton.setOnClickListener { liveOrder.decreaseSelectedOrderLine() 
}
-        plusButton.setOnClickListener { liveOrder.increaseSelectedOrderLine() }
+        ui.minusButton.setOnClickListener { 
liveOrder.decreaseSelectedOrderLine() }
+        ui.plusButton.setOnClickListener { 
liveOrder.increaseSelectedOrderLine() }
         // previous and next button
-        prevButton.isEnabled = orderManager.hasPreviousOrder(orderId)
-        orderManager.hasNextOrder(orderId).observe(viewLifecycleOwner, 
Observer { hasNextOrder ->
-            nextButton.isEnabled = hasNextOrder
+        ui.prevButton.isEnabled = orderManager.hasPreviousOrder(orderId)
+        orderManager.hasNextOrder(orderId).observe(viewLifecycleOwner, { 
hasNextOrder ->
+            ui.nextButton.isEnabled = hasNextOrder
         })
-        prevButton.setOnClickListener { orderManager.previousOrder() }
-        nextButton.setOnClickListener { orderManager.nextOrder() }
+        ui.prevButton.setOnClickListener { orderManager.previousOrder() }
+        ui.nextButton.setOnClickListener { orderManager.nextOrder() }
         // complete button
-        completeButton.setOnClickListener {
+        ui.completeButton.setOnClickListener {
             val order = liveOrder.order.value ?: return@setOnClickListener
             paymentManager.createPayment(order)
             navigate(actionOrderToProcessPayment())
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 b60f3a5..93c4f97 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
@@ -22,16 +22,15 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.recyclerview.selection.SelectionPredicates
 import androidx.recyclerview.selection.SelectionTracker
 import androidx.recyclerview.selection.StorageStrategy
 import androidx.recyclerview.widget.LinearLayoutManager
-import kotlinx.android.synthetic.main.fragment_order_state.*
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentOrderStateBinding
 import net.taler.merchantpos.order.OrderAdapter.OrderLineLookup
 
 class OrderStateFragment : Fragment() {
@@ -39,6 +38,8 @@ class OrderStateFragment : Fragment() {
     private val viewModel: MainViewModel by activityViewModels()
     private val orderManager by lazy { viewModel.orderManager }
     private val liveOrder by lazy { 
orderManager.getOrder(orderManager.currentOrderId.value!!) }
+
+    private lateinit var ui: FragmentOrderStateBinding
     private val adapter = OrderAdapter()
     private var tracker: SelectionTracker<String>? = null
 
@@ -47,18 +48,19 @@ class OrderStateFragment : Fragment() {
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_order_state, container, 
false)
+        ui = FragmentOrderStateBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        orderList.apply {
+        ui.orderList.apply {
             adapter = this@OrderStateFragment.adapter
             layoutManager = LinearLayoutManager(requireContext())
         }
-        val detailsLookup = OrderLineLookup(orderList)
+        val detailsLookup = OrderLineLookup(ui.orderList)
         val tracker = SelectionTracker.Builder(
             "order-selection-id",
-            orderList,
+            ui.orderList,
             adapter.keyProvider,
             detailsLookup,
             StorageStrategy.createStringStorage()
@@ -80,16 +82,16 @@ class OrderStateFragment : Fragment() {
                 liveOrder.selectOrderLine(item)
             }
         })
-        liveOrder.order.observe(viewLifecycleOwner, Observer { order ->
+        liveOrder.order.observe(viewLifecycleOwner, { order ->
             onOrderChanged(order, tracker)
         })
-        liveOrder.orderTotal.observe(viewLifecycleOwner, Observer { orderTotal 
->
+        liveOrder.orderTotal.observe(viewLifecycleOwner, { orderTotal ->
             if (orderTotal.isZero()) {
-                totalView.fadeOut()
-                totalView.text = null
+                ui.totalView.fadeOut()
+                ui.totalView.text = null
             } else {
-                totalView.text = getString(R.string.order_total, orderTotal)
-                totalView.fadeIn()
+                ui.totalView.text = getString(R.string.order_total, orderTotal)
+                ui.totalView.fadeIn()
             }
         })
     }
@@ -104,9 +106,8 @@ class OrderStateFragment : Fragment() {
             liveOrder.lastAddedProduct?.let {
                 val position = adapter.findPosition(it)
                 if (position >= 0) {
-                    // orderList can be null m(
-                    orderList?.scrollToPosition(position)
-                    orderList?.post { this.tracker?.select(it.id) }
+                    ui.orderList.scrollToPosition(position)
+                    ui.orderList.post { this.tracker?.select(it.id) }
                 }
             }
             // workaround for bug: SelectionObserver doesn't update when 
removing selected item
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
index 00eb509..a3898fd 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/order/ProductsFragment.kt
@@ -24,14 +24,13 @@ import android.view.ViewGroup
 import android.widget.TextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.RecyclerView.Adapter
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import kotlinx.android.synthetic.main.fragment_products.*
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
 import net.taler.merchantpos.config.ConfigProduct
+import net.taler.merchantpos.databinding.FragmentProductsBinding
 import net.taler.merchantpos.order.ProductAdapter.ProductViewHolder
 
 interface ProductSelectionListener {
@@ -44,27 +43,30 @@ class ProductsFragment : Fragment(), 
ProductSelectionListener {
     private val orderManager by lazy { viewModel.orderManager }
     private val adapter = ProductAdapter(this)
 
+    private lateinit var ui: FragmentProductsBinding
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_products, container, false)
+        ui = FragmentProductsBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        productsList.apply {
+        ui.productsList.apply {
             adapter = this@ProductsFragment.adapter
             layoutManager = GridLayoutManager(requireContext(), 3)
         }
 
-        orderManager.products.observe(viewLifecycleOwner, Observer { products 
->
+        orderManager.products.observe(viewLifecycleOwner, { products ->
             if (products == null) {
                 adapter.setItems(emptyList())
             } else {
                 adapter.setItems(products)
             }
-            progressBar.visibility = INVISIBLE
+            ui.progressBar.visibility = INVISIBLE
         })
     }
 
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
index 10d538d..5b95dea 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentSuccessFragment.kt
@@ -22,21 +22,23 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_payment_success.*
-import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentPaymentSuccessBinding
 
 class PaymentSuccessFragment : Fragment() {
 
+    private lateinit var ui: FragmentPaymentSuccessBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_payment_success, container, 
false)
+        ui = FragmentPaymentSuccessBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
-        paymentButton.setOnClickListener {
+        ui.paymentButton.setOnClickListener {
             findNavController().navigateUp()
         }
     }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
index 27ef366..5c0a894 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/ProcessPaymentFragment.kt
@@ -22,10 +22,8 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
-import kotlinx.android.synthetic.main.fragment_process_payment.*
 import net.taler.common.NfcManager.Companion.hasNfc
 import net.taler.common.QrCodeManager.makeQrCode
 import net.taler.common.fadeIn
@@ -33,6 +31,7 @@ import net.taler.common.fadeOut
 import net.taler.common.navigate
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentProcessPaymentBinding
 import 
net.taler.merchantpos.payment.ProcessPaymentFragmentDirections.Companion.actionProcessPaymentToPaymentSuccess
 import net.taler.merchantpos.topSnackbar
 
@@ -41,21 +40,24 @@ class ProcessPaymentFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private val paymentManager by lazy { model.paymentManager }
 
+    private lateinit var ui: FragmentProcessPaymentBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_process_payment, container, 
false)
+        ui = FragmentProcessPaymentBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         val introRes =
             if (hasNfc(requireContext())) R.string.payment_intro_nfc else 
R.string.payment_intro
-        payIntroView.setText(introRes)
-        paymentManager.payment.observe(viewLifecycleOwner, Observer { payment 
->
+        ui.payIntroView.setText(introRes)
+        paymentManager.payment.observe(viewLifecycleOwner, { payment ->
             onPaymentStateChanged(payment)
         })
-        cancelPaymentButton.setOnClickListener {
+        ui.cancelPaymentButton.setOnClickListener {
             onPaymentCancel()
         }
     }
@@ -76,17 +78,17 @@ class ProcessPaymentFragment : Fragment() {
             navigate(actionProcessPaymentToPaymentSuccess())
             return
         }
-        payIntroView.fadeIn()
-        amountView.text = payment.order.total.toString()
+        ui.payIntroView.fadeIn()
+        ui.amountView.text = payment.order.total.toString()
         payment.orderId?.let {
-            orderRefView.text = getString(R.string.payment_order_id, it)
-            orderRefView.fadeIn()
+            ui.orderRefView.text = getString(R.string.payment_order_id, it)
+            ui.orderRefView.fadeIn()
         }
         payment.talerPayUri?.let {
             val qrcodeBitmap = makeQrCode(it)
-            qrcodeView.setImageBitmap(qrcodeBitmap)
-            qrcodeView.fadeIn()
-            progressBar.fadeOut()
+            ui.qrcodeView.setImageBitmap(qrcodeBitmap)
+            ui.qrcodeView.fadeIn()
+            ui.progressBar.fadeOut()
         }
     }
 
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 752b7aa..91e68e6 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
@@ -22,11 +22,9 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_LONG
 import com.google.android.material.snackbar.Snackbar
-import kotlinx.android.synthetic.main.fragment_refund.*
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.common.navigate
@@ -35,6 +33,7 @@ import net.taler.lib.common.AmountParserException
 import net.taler.merchantlib.OrderHistoryEntry
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentRefundBinding
 import 
net.taler.merchantpos.refund.RefundFragmentDirections.Companion.actionRefundFragmentToRefundUriFragment
 import net.taler.merchantpos.refund.RefundResult.AlreadyRefunded
 import net.taler.merchantpos.refund.RefundResult.Error
@@ -46,44 +45,47 @@ class RefundFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private val refundManager by lazy { model.refundManager }
 
+    private lateinit var ui: FragmentRefundBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_refund, container, false)
+        ui = FragmentRefundBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         val item = refundManager.toBeRefunded ?: throw IllegalStateException()
-        amountInputView.setText(item.amount.amountStr)
-        currencyView.text = item.amount.currency
-        abortButton.setOnClickListener { findNavController().navigateUp() }
-        refundButton.setOnClickListener { onRefundButtonClicked(item) }
+        ui.amountInputView.setText(item.amount.amountStr)
+        ui.currencyView.text = item.amount.currency
+        ui.abortButton.setOnClickListener { findNavController().navigateUp() }
+        ui.refundButton.setOnClickListener { onRefundButtonClicked(item) }
 
-        refundManager.refundResult.observe(viewLifecycleOwner, Observer { 
result ->
+        refundManager.refundResult.observe(viewLifecycleOwner, { result ->
             onRefundResultChanged(result)
         })
     }
 
     private fun onRefundButtonClicked(item: OrderHistoryEntry) {
         val inputAmount = try {
-            Amount.fromString(item.amount.currency, 
amountInputView.text.toString())
+            Amount.fromString(item.amount.currency, 
ui.amountInputView.text.toString())
         } catch (e: AmountParserException) {
-            amountView.error = getString(R.string.refund_error_invalid_amount)
+            ui.amountView.error = 
getString(R.string.refund_error_invalid_amount)
             return
         }
         if (inputAmount > item.amount) {
-            amountView.error = getString(R.string.refund_error_max_amount, 
item.amount.amountStr)
+            ui.amountView.error = getString(R.string.refund_error_max_amount, 
item.amount.amountStr)
             return
         }
         if (inputAmount.isZero()) {
-            amountView.error = getString(R.string.refund_error_zero)
+            ui.amountView.error = getString(R.string.refund_error_zero)
             return
         }
-        amountView.error = null
-        refundButton.fadeOut()
-        progressBar.fadeIn()
-        refundManager.refund(item, inputAmount, 
reasonInputView.text.toString())
+        ui.amountView.error = null
+        ui.refundButton.fadeOut()
+        ui.progressBar.fadeIn()
+        refundManager.refund(item, inputAmount, 
ui.reasonInputView.text.toString())
     }
 
     private fun onRefundResultChanged(result: RefundResult?): Any = when 
(result) {
@@ -91,8 +93,8 @@ class RefundFragment : Fragment() {
         PastDeadline -> onError(getString(R.string.refund_error_deadline))
         AlreadyRefunded -> 
onError(getString(R.string.refund_error_already_refunded))
         is Success -> {
-            progressBar.fadeOut()
-            refundButton.fadeIn()
+            ui.progressBar.fadeOut()
+            ui.refundButton.fadeIn()
             navigate(actionRefundFragmentToRefundUriFragment())
         }
         null -> { // no-op
@@ -101,8 +103,8 @@ class RefundFragment : Fragment() {
 
     private fun onError(msg: String) {
         Snackbar.make(requireView(), msg, LENGTH_LONG).show()
-        progressBar.fadeOut()
-        refundButton.fadeIn()
+        ui.progressBar.fadeOut()
+        ui.refundButton.fadeIn()
     }
 
 }
diff --git 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
index b8e8997..5e52404 100644
--- 
a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
+++ 
b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundUriFragment.kt
@@ -23,22 +23,25 @@ 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_refund_uri.*
 import net.taler.common.NfcManager.Companion.hasNfc
 import net.taler.common.QrCodeManager.makeQrCode
 import net.taler.merchantpos.MainViewModel
 import net.taler.merchantpos.R
+import net.taler.merchantpos.databinding.FragmentRefundUriBinding
 
 class RefundUriFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val refundManager by lazy { model.refundManager }
 
+    private lateinit var ui: FragmentRefundUriBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_refund_uri, container, false)
+        ui = FragmentRefundUriBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -46,19 +49,19 @@ class RefundUriFragment : Fragment() {
         val result = refundManager.refundResult.value
         if (result !is RefundResult.Success) throw IllegalStateException()
 
-        refundQrcodeView.setImageBitmap(makeQrCode(result.refundUri))
+        ui.refundQrcodeView.setImageBitmap(makeQrCode(result.refundUri))
 
         val introRes =
             if (hasNfc(requireContext())) R.string.refund_intro_nfc else 
R.string.refund_intro
-        refundIntroView.setText(introRes)
+        ui.refundIntroView.setText(introRes)
 
-        refundAmountView.text = result.amount.toString()
+        ui.refundAmountView.text = result.amount.toString()
 
-        refundRefView.text =
+        ui.refundRefView.text =
             getString(R.string.refund_order_ref, result.item.orderId, 
result.reason)
 
-        cancelRefundButton.setOnClickListener { 
findNavController().navigateUp() }
-        completeButton.setOnClickListener { findNavController().navigateUp() }
+        ui.cancelRefundButton.setOnClickListener { 
findNavController().navigateUp() }
+        ui.completeButton.setOnClickListener { 
findNavController().navigateUp() }
     }
 
     override fun onDestroy() {
diff --git a/merchant-terminal/src/main/res/layout/activity_main.xml 
b/merchant-terminal/src/main/res/layout/activity_main.xml
index 1285b19..c801563 100644
--- a/merchant-terminal/src/main/res/layout/activity_main.xml
+++ b/merchant-terminal/src/main/res/layout/activity_main.xml
@@ -24,6 +24,7 @@
     tools:openDrawer="start">
 
     <include
+        android:id="@+id/main"
         layout="@layout/app_bar_main"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
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 d22eaa0..fb30692 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
@@ -16,7 +16,7 @@
 
 package net.taler.common
 
-import androidx.annotation.RequiresApi
+import android.os.Build
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import net.taler.common.TalerUtils.getLocalizedString
@@ -48,8 +48,11 @@ abstract class Product {
     abstract val location: String?
     abstract val image: String?
     val localizedDescription: String
-        @RequiresApi(26)
-        get() = getLocalizedString(descriptionI18n, description)
+        get() = if (Build.VERSION.SDK_INT >= 26) {
+            getLocalizedString(descriptionI18n, description)
+        } else {
+            description
+        }
 }
 
 @Serializable
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 30cf291..47de2dd 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -19,12 +19,11 @@ import com.android.build.gradle.tasks.MergeResources
 plugins {
     id "com.android.application"
     id "kotlin-android"
-    id "kotlin-android-extensions"
     id "kotlinx-serialization"
     id "de.undercouch.download"
 }
 
-def walletCoreVersion = "v0.7.1-dev.23"
+def walletCoreVersion = "v0.7.1-dev.25"
 
 static def versionCodeEpoch() {
     return (new Date().getTime() / 1000).toInteger()
@@ -48,7 +47,7 @@ android {
         minSdkVersion 24
         targetSdkVersion 29
         versionCode 6
-        versionName "0.7.1.dev.23"
+        versionName "0.7.1.dev.25"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         buildConfigField "String", "WALLET_CORE_VERSION", 
"\"$walletCoreVersion\""
     }
@@ -88,6 +87,10 @@ android {
         jvmTarget = "1.8"
     }
 
+    buildFeatures {
+        viewBinding = true
+    }
+
     packagingOptions {
         exclude("META-INF/*.kotlin_module")
     }
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index 838ed2d..0605976 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -47,8 +47,6 @@ import 
com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
 import com.google.android.material.snackbar.Snackbar
 import com.google.zxing.integration.android.IntentIntegrator
 import 
com.google.zxing.integration.android.IntentIntegrator.parseActivityResult
-import kotlinx.android.synthetic.main.activity_main.*
-import kotlinx.android.synthetic.main.app_bar_main.*
 import net.taler.common.isOnline
 import net.taler.wallet.BuildConfig.VERSION_CODE
 import net.taler.wallet.BuildConfig.VERSION_NAME
@@ -56,6 +54,7 @@ import 
net.taler.wallet.HostCardEmulatorService.Companion.HTTP_TUNNEL_RESPONSE
 import 
net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_CONNECTED
 import 
net.taler.wallet.HostCardEmulatorService.Companion.MERCHANT_NFC_DISCONNECTED
 import 
net.taler.wallet.HostCardEmulatorService.Companion.TRIGGER_PAYMENT_ACTION
+import net.taler.wallet.databinding.ActivityMainBinding
 import net.taler.wallet.refund.RefundStatus
 import java.util.Locale.ROOT
 
@@ -64,35 +63,37 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
 
     private val model: MainViewModel by viewModels()
 
+    private lateinit var ui: ActivityMainBinding
     private lateinit var nav: NavController
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        setContentView(R.layout.activity_main)
+        ui = ActivityMainBinding.inflate(layoutInflater)
+        setContentView(ui.root)
 
         val navHostFragment =
             supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as 
NavHostFragment
         nav = navHostFragment.navController
-        nav_view.setupWithNavController(nav)
-        nav_view.setNavigationItemSelectedListener(this)
+        ui.navView.setupWithNavController(nav)
+        ui.navView.setNavigationItemSelectedListener(this)
         if (savedInstanceState == null) {
-            nav_view.menu.getItem(0).isChecked = true
+            ui.navView.menu.getItem(0).isChecked = true
         }
 
-        setSupportActionBar(toolbar)
+        setSupportActionBar(ui.content.toolbar)
         val appBarConfiguration = AppBarConfiguration(
             setOf(R.id.nav_main, R.id.nav_settings, 
R.id.nav_pending_operations),
-            drawer_layout
+            ui.drawerLayout
         )
-        toolbar.setupWithNavController(nav, appBarConfiguration)
+        ui.content.toolbar.setupWithNavController(nav, appBarConfiguration)
 
-        model.showProgressBar.observe(this, Observer { show ->
-            progress_bar.visibility = if (show) VISIBLE else INVISIBLE
+        model.showProgressBar.observe(this, { show ->
+            ui.content.progressBar.visibility = if (show) VISIBLE else 
INVISIBLE
         })
 
-        val versionView: TextView = 
nav_view.getHeaderView(0).findViewById(R.id.versionView)
-        model.devMode.observe(this, Observer { enabled ->
-            nav_view.menu.findItem(R.id.nav_dev).isVisible = enabled
+        val versionView: TextView = 
ui.navView.getHeaderView(0).findViewById(R.id.versionView)
+        model.devMode.observe(this, { enabled ->
+            ui.navView.menu.findItem(R.id.nav_dev).isVisible = enabled
             if (enabled) {
                 @SuppressLint("SetTextI18n")
                 versionView.text = "$VERSION_NAME ($VERSION_CODE)"
@@ -113,7 +114,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
     }
 
     override fun onBackPressed() {
-        if (drawer_layout.isDrawerOpen(START)) drawer_layout.closeDrawer(START)
+        if (ui.drawerLayout.isDrawerOpen(START)) 
ui.drawerLayout.closeDrawer(START)
         else super.onBackPressed()
     }
 
@@ -123,7 +124,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
             R.id.nav_settings -> nav.navigate(R.id.nav_settings)
             R.id.nav_pending_operations -> 
nav.navigate(R.id.nav_pending_operations)
         }
-        drawer_layout.closeDrawer(START)
+        ui.drawerLayout.closeDrawer(START)
         return true
     }
 
@@ -167,7 +168,7 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
             }
             else -> {
                 Snackbar.make(
-                    nav_view,
+                    ui.navView,
                     "URL from $from doesn't contain a supported Taler Uri.",
                     LENGTH_SHORT
                 ).show()
@@ -179,13 +180,13 @@ class MainActivity : AppCompatActivity(), 
OnNavigationItemSelectedListener,
         model.showProgressBar.value = false
         when (status) {
             is RefundStatus.Error -> {
-                Snackbar.make(nav_view, R.string.refund_error, 
LENGTH_LONG).show()
+                Snackbar.make(ui.navView, R.string.refund_error, 
LENGTH_LONG).show()
             }
             is RefundStatus.Success -> {
                 val amount = status.response.amountRefundGranted
                 model.showTransactions(amount.currency)
                 val str = getString(R.string.refund_success, amount.amountStr)
-                Snackbar.make(nav_view, str, LENGTH_LONG).show()
+                Snackbar.make(ui.navView, str, LENGTH_LONG).show()
             }
         }
     }
diff --git a/wallet/src/main/java/net/taler/wallet/MainFragment.kt 
b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
index d5bd3fc..1479bc0 100644
--- a/wallet/src/main/java/net/taler/wallet/MainFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainFragment.kt
@@ -22,14 +22,13 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_main.*
 import net.taler.common.EventObserver
 import net.taler.wallet.CurrencyMode.MULTI
 import net.taler.wallet.CurrencyMode.SINGLE
 import net.taler.wallet.balances.BalanceItem
 import net.taler.wallet.balances.BalancesFragment
+import net.taler.wallet.databinding.FragmentMainBinding
 import net.taler.wallet.transactions.TransactionsFragment
 
 enum class CurrencyMode { SINGLE, MULTI }
@@ -39,16 +38,19 @@ class MainFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private var currencyMode: CurrencyMode? = null
 
+    private lateinit var ui: FragmentMainBinding
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_main, container, false)
+        ui = FragmentMainBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        model.balances.observe(viewLifecycleOwner, Observer {
+        model.balances.observe(viewLifecycleOwner, {
             onBalancesChanged(it)
         })
         model.transactionsEvent.observe(viewLifecycleOwner, EventObserver { 
currency ->
@@ -59,10 +61,10 @@ class MainFragment : Fragment() {
             }
         })
 
-        mainFab.setOnClickListener {
+        ui.mainFab.setOnClickListener {
             scanQrCode(requireActivity())
         }
-        mainFab.setOnLongClickListener {
+        ui.mainFab.setOnLongClickListener {
             findNavController().navigate(R.id.action_nav_main_to_nav_uri_input)
             true
         }
diff --git a/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt 
b/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt
index eaa6d16..d17977b 100644
--- a/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/UriInputFragment.kt
@@ -27,40 +27,43 @@ import android.view.ViewGroup
 import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
 import androidx.fragment.app.Fragment
-import kotlinx.android.synthetic.main.fragment_uri_input.*
+import net.taler.wallet.databinding.FragmentUriInputBinding
 
 class UriInputFragment : Fragment() {
 
+    private lateinit var ui: FragmentUriInputBinding
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_uri_input, container, false)
+        ui = FragmentUriInputBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         val clipboard = 
requireContext().getSystemService(ClipboardManager::class.java)!!
 
-        pasteButton.setOnClickListener {
+        ui.pasteButton.setOnClickListener {
             val item = clipboard.primaryClip?.getItemAt(0)
             if (item?.text != null) {
-                uriView.setText(item.text)
+                ui.uriView.setText(item.text)
             } else {
                 if (item?.uri != null) {
-                    uriView.setText(item.uri.toString())
+                    ui.uriView.setText(item.uri.toString())
                 } else {
                     Toast.makeText(requireContext(), R.string.paste_invalid, 
LENGTH_LONG).show()
                 }
             }
         }
-        okButton.setOnClickListener {
-            if (uriView.text?.startsWith("taler://") == true) {
-                uriLayout.error = null
-                val i = Intent(ACTION_VIEW, Uri.parse(uriView.text.toString()))
+        ui.okButton.setOnClickListener {
+            if (ui.uriView.text?.startsWith("taler://") == true) {
+                ui.uriLayout.error = null
+                val i = Intent(ACTION_VIEW, 
Uri.parse(ui.uriView.text.toString()))
                 startActivity(i)
             } else {
-                uriLayout.error = getString(R.string.uri_invalid)
+                ui.uriLayout.error = getString(R.string.uri_invalid)
             }
         }
     }
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt 
b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
index 2b4d032..afd9a23 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalancesFragment.kt
@@ -26,13 +26,11 @@ import android.view.View.VISIBLE
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_balances.*
 import net.taler.common.fadeIn
 import net.taler.wallet.MainViewModel
-import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentBalancesBinding
 
 interface BalanceClickListener {
     fun onBalanceClick(currency: String)
@@ -43,6 +41,7 @@ class BalancesFragment : Fragment(),
 
     private val model: MainViewModel by activityViewModels()
 
+    private lateinit var ui: FragmentBalancesBinding
     private val balancesAdapter = BalanceAdapter(this)
 
     override fun onCreateView(
@@ -50,16 +49,17 @@ class BalancesFragment : Fragment(),
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_balances, container, false)
+        ui = FragmentBalancesBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        mainList.apply {
+        ui.mainList.apply {
             adapter = balancesAdapter
             addItemDecoration(DividerItemDecoration(context, VERTICAL))
         }
 
-        model.balances.observe(viewLifecycleOwner, Observer {
+        model.balances.observe(viewLifecycleOwner, {
             onBalancesChanged(it)
         })
     }
@@ -67,12 +67,12 @@ class BalancesFragment : Fragment(),
     private fun onBalancesChanged(balances: List<BalanceItem>) {
         beginDelayedTransition(view as ViewGroup)
         if (balances.isEmpty()) {
-            mainEmptyState.visibility = VISIBLE
-            mainList.visibility = GONE
+            ui.mainEmptyState.visibility = VISIBLE
+            ui.mainList.visibility = GONE
         } else {
             balancesAdapter.setItems(balances)
-            mainEmptyState.visibility = INVISIBLE
-            mainList.fadeIn()
+            ui.mainEmptyState.visibility = INVISIBLE
+            ui.mainList.fadeIn()
         }
     }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt
index c7da205..86b2519 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt
@@ -24,43 +24,45 @@ import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_exchange_list.*
 import net.taler.common.EventObserver
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentExchangeListBinding
 
 class ExchangeListFragment : Fragment(), ExchangeClickListener {
 
     private val model: MainViewModel by activityViewModels()
     private val exchangeManager by lazy { model.exchangeManager }
+
+    private lateinit var ui: FragmentExchangeListBinding
     private val exchangeAdapter by lazy { ExchangeAdapter(this) }
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_exchange_list, container, 
false)
+        ui = FragmentExchangeListBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        list.apply {
+        ui.list.apply {
             adapter = exchangeAdapter
             addItemDecoration(DividerItemDecoration(context, VERTICAL))
         }
-        addExchangeFab.setOnClickListener {
+        ui.addExchangeFab.setOnClickListener {
             AddExchangeDialogFragment().show(parentFragmentManager, 
"ADD_EXCHANGE")
         }
 
-        exchangeManager.progress.observe(viewLifecycleOwner, Observer { show ->
-            if (show) progressBar.fadeIn() else progressBar.fadeOut()
+        exchangeManager.progress.observe(viewLifecycleOwner, { show ->
+            if (show) ui.progressBar.fadeIn() else ui.progressBar.fadeOut()
         })
-        exchangeManager.exchanges.observe(viewLifecycleOwner, Observer { 
exchanges ->
+        exchangeManager.exchanges.observe(viewLifecycleOwner, { exchanges ->
             onExchangeUpdate(exchanges)
         })
         exchangeManager.addError.observe(viewLifecycleOwner, EventObserver { 
error ->
@@ -71,11 +73,11 @@ class ExchangeListFragment : Fragment(), 
ExchangeClickListener {
     private fun onExchangeUpdate(exchanges: List<ExchangeItem>) {
         exchangeAdapter.update(exchanges)
         if (exchanges.isEmpty()) {
-            emptyState.fadeIn()
-            list.fadeOut()
+            ui.emptyState.fadeIn()
+            ui.list.fadeOut()
         } else {
-            emptyState.fadeOut()
-            list.fadeIn()
+            ui.emptyState.fadeOut()
+            ui.list.fadeIn()
         }
     }
 
diff --git 
a/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt 
b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
index 9f5a916..a95a51c 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
@@ -27,12 +27,12 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
 import androidx.recyclerview.widget.RecyclerView.Adapter
 import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import kotlinx.android.synthetic.main.fragment_select_exchange.*
-import net.taler.lib.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.FragmentSelectExchangeBinding
 import net.taler.wallet.exchanges.CoinFeeAdapter.CoinFeeViewHolder
 import net.taler.wallet.exchanges.WireFeeAdapter.WireFeeViewHolder
 
@@ -41,28 +41,29 @@ class SelectExchangeFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private val withdrawManager by lazy { model.withdrawManager }
 
+    private lateinit var ui: FragmentSelectExchangeBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_select_exchange, container, 
false)
+        ui = FragmentSelectExchangeBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         val fees = withdrawManager.exchangeFees ?: throw 
IllegalStateException()
         if (fees.withdrawFee.isZero()) {
-            withdrawFeeLabel.visibility = GONE
-            withdrawFeeView.visibility = GONE
-        } else withdrawFeeView.setAmount(fees.withdrawFee)
+            ui.withdrawFeeLabel.visibility = GONE
+            ui.withdrawFeeView.visibility = GONE
+        } else ui.withdrawFeeView.setAmount(fees.withdrawFee)
         if (fees.overhead.isZero()) {
-            overheadLabel.visibility = GONE
-            overheadView.visibility = GONE
-        } else overheadView.setAmount(fees.overhead)
-        expirationView.text = 
fees.earliestDepositExpiration.ms.toRelativeTime(requireContext())
-        coinFeesList.adapter =
-            CoinFeeAdapter(fees.coinFees)
-        wireFeesList.adapter =
-            WireFeeAdapter(fees.wireFees)
+            ui.overheadLabel.visibility = GONE
+            ui.overheadView.visibility = GONE
+        } else ui.overheadView.setAmount(fees.overhead)
+        ui.expirationView.text = 
fees.earliestDepositExpiration.ms.toRelativeTime(requireContext())
+        ui.coinFeesList.adapter = CoinFeeAdapter(fees.coinFees)
+        ui.wireFeesList.adapter = WireFeeAdapter(fees.wireFees)
     }
 
     private fun TextView.setAmount(amount: Amount) {
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
index 33e3a1d..df2b2b8 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/AlreadyPaidFragment.kt
@@ -22,8 +22,7 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_already_paid.*
-import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentAlreadyPaidBinding
 
 /**
  * Display the message that the user already paid for the order
@@ -31,15 +30,18 @@ import net.taler.wallet.R
  */
 class AlreadyPaidFragment : Fragment() {
 
+    private lateinit var ui: FragmentAlreadyPaidBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_already_paid, container, 
false)
+        ui = FragmentAlreadyPaidBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        backButton.setOnClickListener {
+        ui.backButton.setOnClickListener {
             findNavController().navigateUp()
         }
     }
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt 
b/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
index ff70f75..75de93f 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
@@ -22,11 +22,12 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.DialogFragment
-import kotlinx.android.synthetic.main.fragment_product_image.*
-import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentProductImageBinding
 
 class ProductImageFragment private constructor() : DialogFragment() {
 
+    private lateinit var ui: FragmentProductImageBinding
+
     companion object {
         private const val IMAGE = "image"
 
@@ -41,12 +42,13 @@ class ProductImageFragment private constructor() : 
DialogFragment() {
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_product_image, container, 
false)
+        ui = FragmentProductImageBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         val bitmap = requireArguments().getParcelable<Bitmap>(IMAGE)
-        productImageView.setImageBitmap(bitmap)
+        ui.productImageView.setImageBitmap(bitmap)
     }
 
 }
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 99a6ec8..8815408 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -32,14 +32,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.transition.TransitionManager.beginDelayedTransition
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
-import kotlinx.android.synthetic.main.payment_bottom_bar.*
-import kotlinx.android.synthetic.main.payment_details.*
 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
 
 /**
  * Show a payment and ask the user to accept/decline.
@@ -48,13 +47,16 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
 
     private val model: MainViewModel by activityViewModels()
     private val paymentManager by lazy { model.paymentManager }
+
+    private lateinit var ui: FragmentPromptPaymentBinding
     private val adapter = ProductAdapter(this)
 
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_prompt_payment, container, 
false)
+        ui = FragmentPromptPaymentBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -62,14 +64,14 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
         paymentManager.detailsShown.observe(viewLifecycleOwner, Observer { 
shown ->
             beginDelayedTransition(view as ViewGroup)
             val res = if (shown) R.string.payment_hide_details else 
R.string.payment_show_details
-            detailsButton.setText(res)
-            productsList.visibility = if (shown) VISIBLE else GONE
+            ui.details.detailsButton.setText(res)
+            ui.details.productsList.visibility = if (shown) VISIBLE else GONE
         })
 
-        detailsButton.setOnClickListener {
+        ui.details.detailsButton.setOnClickListener {
             paymentManager.toggleDetailsShown()
         }
-        productsList.apply {
+        ui.details.productsList.apply {
             adapter = this@PromptPaymentFragment.adapter
             layoutManager = LinearLayoutManager(requireContext())
         }
@@ -85,9 +87,9 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
     private fun showLoading(show: Boolean) {
         model.showProgressBar.value = show
         if (show) {
-            progressBar.fadeIn()
+            ui.details.progressBar.fadeIn()
         } else {
-            progressBar.fadeOut()
+            ui.details.progressBar.fadeOut()
         }
     }
 
@@ -97,22 +99,22 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
                 showLoading(false)
                 val fees = payStatus.amountEffective - payStatus.amountRaw
                 showOrder(payStatus.contractTerms, payStatus.amountRaw, fees)
-                confirmButton.isEnabled = true
-                confirmButton.setOnClickListener {
+                ui.bottom.confirmButton.isEnabled = true
+                ui.bottom.confirmButton.setOnClickListener {
                     model.showProgressBar.value = true
                     paymentManager.confirmPay(
                         payStatus.proposalId,
                         payStatus.contractTerms.amount.currency
                     )
-                    confirmButton.fadeOut()
-                    confirmProgressBar.fadeIn()
+                    ui.bottom.confirmButton.fadeOut()
+                    ui.bottom.confirmProgressBar.fadeIn()
                 }
             }
             is PayStatus.InsufficientBalance -> {
                 showLoading(false)
                 showOrder(payStatus.contractTerms, payStatus.amountRaw)
-                errorView.setText(R.string.payment_balance_insufficient)
-                errorView.fadeIn()
+                
ui.details.errorView.setText(R.string.payment_balance_insufficient)
+                ui.details.errorView.fadeIn()
             }
             is PayStatus.Success -> {
                 showLoading(false)
@@ -128,8 +130,8 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
             }
             is PayStatus.Error -> {
                 showLoading(false)
-                errorView.text = getString(R.string.payment_error, 
payStatus.error)
-                errorView.fadeIn()
+                ui.details.errorView.text = getString(R.string.payment_error, 
payStatus.error)
+                ui.details.errorView.fadeIn()
             }
             is PayStatus.None -> {
                 // No payment active.
@@ -143,21 +145,21 @@ class PromptPaymentFragment : Fragment(), 
ProductImageClickListener {
     }
 
     private fun showOrder(contractTerms: ContractTerms, amount:Amount, 
totalFees: Amount? = null) {
-        orderView.text = contractTerms.summary
+        ui.details.orderView.text = contractTerms.summary
         adapter.setItems(contractTerms.products)
         if (contractTerms.products.size == 1) 
paymentManager.toggleDetailsShown()
-        totalView.text = amount.toString()
+        ui.bottom.totalView.text = amount.toString()
         if (totalFees != null && !totalFees.isZero()) {
-            feeView.text = getString(R.string.payment_fee, totalFees)
-            feeView.fadeIn()
+            ui.bottom.feeView.text = getString(R.string.payment_fee, totalFees)
+            ui.bottom.feeView.fadeIn()
         } else {
-            feeView.visibility = GONE
+            ui.bottom.feeView.visibility = GONE
         }
-        orderLabelView.fadeIn()
-        orderView.fadeIn()
-        if (contractTerms.products.size > 1) detailsButton.fadeIn()
-        totalLabelView.fadeIn()
-        totalView.fadeIn()
+        ui.details.orderLabelView.fadeIn()
+        ui.details.orderView.fadeIn()
+        if (contractTerms.products.size > 1) ui.details.detailsButton.fadeIn()
+        ui.bottom.totalLabelView.fadeIn()
+        ui.bottom.totalView.fadeIn()
     }
 
     override fun onImageClick(image: Bitmap) {
diff --git 
a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt 
b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt
index 293dbdb..e2f3ca1 100644
--- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsFragment.kt
@@ -30,16 +30,15 @@ import android.widget.LinearLayout
 import android.widget.TextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_SHORT
-import kotlinx.android.synthetic.main.fragment_pending_operations.*
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
 import net.taler.wallet.TAG
+import net.taler.wallet.databinding.FragmentPendingOperationsBinding
 import org.json.JSONObject
 
 interface PendingOperationClickListener {
@@ -52,6 +51,7 @@ class PendingOperationsFragment : Fragment(), 
PendingOperationClickListener {
     private val model: MainViewModel by activityViewModels()
     private val pendingOperationsManager by lazy { 
model.pendingOperationsManager }
 
+    private lateinit var ui: FragmentPendingOperationsBinding
     private val pendingAdapter = PendingOperationsAdapter(emptyList(), this)
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -64,13 +64,14 @@ class PendingOperationsFragment : Fragment(), 
PendingOperationClickListener {
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_pending_operations, 
container, false)
+        ui = FragmentPendingOperationsBinding.inflate(inflater, container, 
false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        list_pending.apply {
+        ui.listPending.apply {
             val myLayoutManager = LinearLayoutManager(requireContext())
             val myItemDecoration =
                 DividerItemDecoration(requireContext(), 
myLayoutManager.orientation)
@@ -79,7 +80,7 @@ class PendingOperationsFragment : Fragment(), 
PendingOperationClickListener {
             addItemDecoration(myItemDecoration)
         }
 
-        pendingOperationsManager.pendingOperations.observe(viewLifecycleOwner, 
Observer {
+        pendingOperationsManager.pendingOperations.observe(viewLifecycleOwner, 
{
             updatePending(it)
         })
     }
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 a52b9d8..63492f4 100644
--- a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
@@ -20,7 +20,6 @@ import android.os.Bundle
 import android.view.View
 import androidx.appcompat.app.AlertDialog
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.preference.Preference
 import androidx.preference.PreferenceFragmentCompat
 import androidx.preference.SwitchPreferenceCompat
@@ -75,12 +74,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        model.lastBackup.observe(viewLifecycleOwner, Observer {
+        model.lastBackup.observe(viewLifecycleOwner, {
             val time = it.toRelativeTime(requireContext())
             prefBackup.summary = getString(R.string.backup_last, time)
         })
 
-        model.devMode.observe(viewLifecycleOwner, Observer { enabled ->
+        model.devMode.observe(viewLifecycleOwner, { enabled ->
             prefDevMode.isChecked = enabled
             if (enabled) {
                 prefVersionApp.summary = "$VERSION_NAME ($FLAVOR 
$VERSION_CODE)"
@@ -95,7 +94,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
             true
         }
 
-        withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, 
Observer { loading ->
+        withdrawManager.testWithdrawalInProgress.observe(viewLifecycleOwner, { 
loading ->
             prefWithdrawTest.isEnabled = !loading
             model.showProgressBar.value = loading
         })
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 f15e34f..302e684 100644
--- 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
@@ -19,68 +19,34 @@ package net.taler.wallet.transactions
 import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
-import android.view.LayoutInflater
 import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuItem
-import android.view.View
-import android.view.View.GONE
-import android.view.ViewGroup
-import android.widget.Toast
-import android.widget.Toast.LENGTH_LONG
-import androidx.core.content.ContextCompat.getColor
+import android.widget.TextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import kotlinx.android.synthetic.main.fragment_transaction_payment.*
-import kotlinx.android.synthetic.main.fragment_transaction_withdrawal.*
-import kotlinx.android.synthetic.main.fragment_transaction_withdrawal.feeView
-import kotlinx.android.synthetic.main.fragment_transaction_withdrawal.timeView
 import net.taler.common.isSafe
-import net.taler.common.toAbsoluteTime
 import net.taler.lib.common.Amount
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
-import net.taler.wallet.cleanExchange
-import net.taler.wallet.transactions.WithdrawalDetails.TalerBankIntegrationApi
 
-class TransactionDetailFragment : Fragment() {
+abstract class TransactionDetailFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val transactionManager by lazy { model.transactionManager }
-    private val transaction by lazy { 
requireNotNull(transactionManager.selectedTransaction) }
+    protected val transaction: Transaction? get() = 
transactionManager.selectedTransaction
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setHasOptionsMenu(model.devMode.value == true)
     }
 
-    override fun onCreateView(
-        inflater: LayoutInflater,
-        container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(transaction.detailPageLayout, container, false)
-    }
-
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
         requireActivity().apply {
-            title = getString(transaction.generalTitleRes)
-        }
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        timeView.text = 
transaction.timestamp.ms.toAbsoluteTime(requireContext())
-        when (val e = transaction) {
-            is TransactionWithdrawal -> bind(e)
-            is TransactionPayment -> bind(e)
-            is TransactionRefund -> bind(e)
-            is TransactionRefresh -> bind(e)
-            else -> Toast.makeText(
-                requireContext(),
-                "Transaction ${e.javaClass.simpleName} not implemented.",
-                LENGTH_LONG
-            ).show()
+            transaction?.generalTitleRes?.let {
+                title = getString(it)
+            }
         }
     }
 
@@ -94,54 +60,15 @@ class TransactionDetailFragment : Fragment() {
         }
     }
 
-    private fun bind(t: TransactionWithdrawal) {
-        effectiveAmountLabel.text = getString(R.string.withdraw_total)
-        effectiveAmountView.text = t.amountEffective.toString()
-        if (t.pending && t.withdrawalDetails is TalerBankIntegrationApi &&
-            !t.confirmed && t.withdrawalDetails.bankConfirmationUrl != null
-        ) {
-            val i = Intent().apply {
-                data = Uri.parse(t.withdrawalDetails.bankConfirmationUrl)
-            }
-            if (i.isSafe(requireContext())) {
-                confirmWithdrawalButton.setOnClickListener { startActivity(i) }
-            }
-        } else confirmWithdrawalButton.visibility = GONE
-        chosenAmountLabel.text = getString(R.string.amount_chosen)
-        chosenAmountView.text =
-            getString(R.string.amount_positive, t.amountRaw.toString())
-        val fee = t.amountRaw - t.amountEffective
-        feeView.text = getString(R.string.amount_negative, fee.toString())
-        exchangeView.text = cleanExchange(t.exchangeBaseUrl)
-    }
-
-    private fun bind(t: TransactionPayment) {
-        amountPaidWithFeesView.text = t.amountEffective.toString()
-        val fee = t.amountEffective - t.amountRaw
-        bindOrderAndFee(t.info, t.amountRaw, fee)
-    }
-
-    private fun bind(t: TransactionRefund) {
-        amountPaidWithFeesLabel.text = getString(R.string.transaction_refund)
-        amountPaidWithFeesView.setTextColor(getColor(requireContext(), 
R.color.green))
-        amountPaidWithFeesView.text =
-            getString(R.string.amount_positive, t.amountEffective.toString())
-        val fee = t.amountRaw - t.amountEffective
-        bindOrderAndFee(t.info, t.amountRaw, fee)
-    }
-
-    private fun bind(t: TransactionRefresh) {
-        effectiveAmountLabel.visibility = GONE
-        effectiveAmountView.visibility = GONE
-        confirmWithdrawalButton.visibility = GONE
-        chosenAmountLabel.visibility = GONE
-        chosenAmountView.visibility = GONE
-        val fee = t.amountEffective
-        feeView.text = getString(R.string.amount_negative, fee.toString())
-        exchangeView.text = cleanExchange(t.exchangeBaseUrl)
-    }
-
-    private fun bindOrderAndFee(info: TransactionInfo, raw: Amount, fee: 
Amount) {
+    protected fun bindOrderAndFee(
+        orderSummaryView: TextView,
+        orderAmountView: TextView,
+        orderIdView: TextView,
+        feeView: TextView,
+        info: TransactionInfo,
+        raw: Amount,
+        fee: Amount
+    ) {
         orderAmountView.text = raw.toString()
         feeView.text = getString(R.string.amount_negative, fee.toString())
         orderSummaryView.text = if (info.fulfillmentMessage == null) {
diff --git 
a/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt
similarity index 53%
copy from wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
copy to 
wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt
index ff70f75..84c5c77 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/ProductImageFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionPaymentFragment.kt
@@ -14,39 +14,43 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.payment
+package net.taler.wallet.transactions
 
-import android.graphics.Bitmap
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import androidx.fragment.app.DialogFragment
-import kotlinx.android.synthetic.main.fragment_product_image.*
-import net.taler.wallet.R
+import net.taler.common.toAbsoluteTime
+import net.taler.wallet.databinding.FragmentTransactionPaymentBinding
 
-class ProductImageFragment private constructor() : DialogFragment() {
+class TransactionPaymentFragment : TransactionDetailFragment() {
 
-    companion object {
-        private const val IMAGE = "image"
-
-        fun new(image: Bitmap) = ProductImageFragment().apply {
-            arguments = Bundle().apply {
-                putParcelable(IMAGE, image)
-            }
-        }
-    }
+    private lateinit var ui: FragmentTransactionPaymentBinding
 
     override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
+        inflater: LayoutInflater,
+        container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_product_image, container, 
false)
+        ui = FragmentTransactionPaymentBinding.inflate(inflater, container, 
false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        val bitmap = requireArguments().getParcelable<Bitmap>(IMAGE)
-        productImageView.setImageBitmap(bitmap)
+        val t = transaction as TransactionPayment
+        ui.timeView.text = t.timestamp.ms.toAbsoluteTime(requireContext())
+
+        ui.amountPaidWithFeesView.text = t.amountEffective.toString()
+        val fee = t.amountEffective - t.amountRaw
+        bindOrderAndFee(
+            ui.orderSummaryView,
+            ui.orderAmountView,
+            ui.orderIdView,
+            ui.feeView,
+            t.info,
+            t.amountRaw,
+            fee
+        )
     }
 
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
new file mode 100644
index 0000000..717dd33
--- /dev/null
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefreshFragment.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.wallet.transactions
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.GONE
+import android.view.ViewGroup
+import net.taler.common.toAbsoluteTime
+import net.taler.wallet.R
+import net.taler.wallet.cleanExchange
+import net.taler.wallet.databinding.FragmentTransactionWithdrawalBinding
+
+class TransactionRefreshFragment : TransactionDetailFragment() {
+
+    private lateinit var ui: FragmentTransactionWithdrawalBinding
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        ui = FragmentTransactionWithdrawalBinding.inflate(inflater, container, 
false)
+        return ui.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val t = transaction as TransactionRefresh
+        ui.timeView.text = t.timestamp.ms.toAbsoluteTime(requireContext())
+
+        ui.effectiveAmountLabel.visibility = GONE
+        ui.effectiveAmountView.visibility = GONE
+        ui.confirmWithdrawalButton.visibility = GONE
+        ui.chosenAmountLabel.visibility = GONE
+        ui.chosenAmountView.visibility = GONE
+        val fee = t.amountEffective
+        ui.feeView.text = getString(R.string.amount_negative, fee.toString())
+        ui. exchangeView.text = cleanExchange(t.exchangeBaseUrl)
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt
new file mode 100644
index 0000000..6628d6c
--- /dev/null
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionRefundFragment.kt
@@ -0,0 +1,61 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.transactions
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat.getColor
+import net.taler.common.toAbsoluteTime
+import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentTransactionPaymentBinding
+
+class TransactionRefundFragment : TransactionDetailFragment() {
+
+    private lateinit var ui: FragmentTransactionPaymentBinding
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        ui = FragmentTransactionPaymentBinding.inflate(inflater, container, 
false)
+        return ui.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val t = transaction as TransactionRefund
+        ui.timeView.text = t.timestamp.ms.toAbsoluteTime(requireContext())
+
+        ui.amountPaidWithFeesLabel.text = 
getString(R.string.transaction_refund)
+        ui.amountPaidWithFeesView.setTextColor(getColor(requireContext(), 
R.color.green))
+        ui.amountPaidWithFeesView.text =
+            getString(R.string.amount_positive, t.amountEffective.toString())
+        val fee = t.amountRaw - t.amountEffective
+        bindOrderAndFee(
+            ui.orderSummaryView,
+            ui.orderAmountView,
+            ui.orderIdView,
+            ui.feeView,
+            t.info,
+            t.amountRaw,
+            fee
+        )
+    }
+
+}
diff --git 
a/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
new file mode 100644
index 0000000..26965ef
--- /dev/null
+++ 
b/wallet/src/main/java/net/taler/wallet/transactions/TransactionWithdrawalFragment.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.wallet.transactions
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import net.taler.common.isSafe
+import net.taler.common.toAbsoluteTime
+import net.taler.wallet.R
+import net.taler.wallet.cleanExchange
+import net.taler.wallet.databinding.FragmentTransactionWithdrawalBinding
+
+class TransactionWithdrawalFragment : TransactionDetailFragment() {
+
+    private lateinit var ui: FragmentTransactionWithdrawalBinding
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        ui = FragmentTransactionWithdrawalBinding.inflate(inflater, container, 
false)
+        return ui.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val t = transaction as TransactionWithdrawal
+        ui.timeView.text = t.timestamp.ms.toAbsoluteTime(requireContext())
+
+        ui.effectiveAmountLabel.text = getString(R.string.withdraw_total)
+        ui.effectiveAmountView.text = t.amountEffective.toString()
+        if (t.pending && t.withdrawalDetails is 
WithdrawalDetails.TalerBankIntegrationApi &&
+            !t.confirmed && t.withdrawalDetails.bankConfirmationUrl != null
+        ) {
+            val i = Intent().apply {
+                data = Uri.parse(t.withdrawalDetails.bankConfirmationUrl)
+            }
+            if (i.isSafe(requireContext())) {
+                ui.confirmWithdrawalButton.setOnClickListener { 
startActivity(i) }
+            }
+        } else ui.confirmWithdrawalButton.visibility = View.GONE
+        ui.chosenAmountLabel.text = getString(R.string.amount_chosen)
+        ui.chosenAmountView.text =
+            getString(R.string.amount_positive, t.amountRaw.toString())
+        val fee = t.amountRaw - t.amountEffective
+        ui.feeView.text = getString(R.string.amount_negative, fee.toString())
+        ui.exchangeView.text = cleanExchange(t.exchangeBaseUrl)
+    }
+
+}
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 db3f283..50181c5 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -18,7 +18,7 @@ package net.taler.wallet.transactions
 
 import android.content.Context
 import androidx.annotation.DrawableRes
-import androidx.annotation.LayoutRes
+import androidx.annotation.IdRes
 import androidx.annotation.StringRes
 import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
@@ -48,8 +48,8 @@ sealed class Transaction {
     @get:DrawableRes
     abstract val icon: Int
 
-    @get:LayoutRes
-    abstract val detailPageLayout: Int
+    @get:IdRes
+    abstract val detailPageNav: Int
 
     abstract val amountType: AmountType
 
@@ -78,7 +78,8 @@ class TransactionWithdrawal(
     override val amountEffective: Amount
 ) : Transaction() {
     override val icon = R.drawable.transaction_withdrawal
-    override val detailPageLayout = R.layout.fragment_transaction_withdrawal
+
+    override val detailPageNav = R.id.action_nav_transactions_detail_withdrawal
 
     @Transient
     override val amountType = AmountType.Positive
@@ -135,7 +136,7 @@ class TransactionPayment(
     override val amountEffective: Amount
 ) : Transaction() {
     override val icon = R.drawable.ic_cash_usd_outline
-    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val detailPageNav = R.id.action_nav_transactions_detail_payment
 
     @Transient
     override val amountType = AmountType.Negative
@@ -190,13 +191,11 @@ class TransactionRefund(
      */
     val amountInvalid: Amount? = null,
     override val error: TalerErrorInfo? = null,
-    @SerialName("amountEffective") // TODO remove when fixed in wallet-core
     override val amountRaw: Amount,
-    @SerialName("amountRaw") // TODO remove when fixed in wallet-core
     override val amountEffective: Amount
 ) : Transaction() {
     override val icon = R.drawable.transaction_refund
-    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val detailPageNav = R.id.action_nav_transactions_detail_refund
 
     @Transient
     override val amountType = AmountType.Positive
@@ -221,7 +220,7 @@ class TransactionTip(
     override val amountEffective: Amount
 ) : Transaction() {
     override val icon = R.drawable.transaction_tip_accepted // TODO different 
when declined
-    override val detailPageLayout = R.layout.fragment_transaction_payment
+    override val detailPageNav = 0
 
     @Transient
     override val amountType = AmountType.Positive
@@ -244,7 +243,7 @@ class TransactionRefresh(
     override val amountEffective: Amount
 ) : Transaction() {
     override val icon = R.drawable.transaction_refresh
-    override val detailPageLayout = R.layout.fragment_transaction_withdrawal
+    override val detailPageNav = R.id.action_nav_transactions_detail_refresh
 
     @Transient
     override val amountType = AmountType.Negative
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 8d47a3f..90510e6 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -30,18 +30,17 @@ import androidx.appcompat.widget.SearchView
 import androidx.appcompat.widget.SearchView.OnQueryTextListener
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.selection.SelectionPredicates
 import androidx.recyclerview.selection.SelectionTracker
 import androidx.recyclerview.selection.StorageStrategy
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
-import kotlinx.android.synthetic.main.fragment_transactions.*
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentTransactionsBinding
 
 interface OnTransactionClickListener {
     fun onTransactionClicked(transaction: Transaction)
@@ -52,6 +51,7 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
     private val model: MainViewModel by activityViewModels()
     private val transactionManager by lazy { model.transactionManager }
 
+    private lateinit var ui: FragmentTransactionsBinding
     private val transactionAdapter by lazy { TransactionAdapter(this) }
     private val currency by lazy { transactionManager.selectedCurrency!! }
     private var tracker: SelectionTracker<String>? = null
@@ -66,19 +66,20 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_transactions, container, 
false)
+        ui = FragmentTransactionsBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        list.apply {
+        ui.list.apply {
             adapter = transactionAdapter
             addItemDecoration(DividerItemDecoration(context, VERTICAL))
         }
         val tracker = SelectionTracker.Builder(
             "transaction-selection-id",
-            list,
+            ui.list,
             transactionAdapter.keyProvider,
-            TransactionLookup(list, transactionAdapter),
+            TransactionLookup(ui.list, transactionAdapter),
             StorageStrategy.createStringStorage()
         ).withSelectionPredicate(
             SelectionPredicates.createSelectAnything()
@@ -101,17 +102,17 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
             }
         })
 
-        transactionManager.progress.observe(viewLifecycleOwner, Observer { 
show ->
-            if (show) progressBar.fadeIn() else progressBar.fadeOut()
+        transactionManager.progress.observe(viewLifecycleOwner, { show ->
+            if (show) ui.progressBar.fadeIn() else ui.progressBar.fadeOut()
         })
-        transactionManager.transactions.observe(viewLifecycleOwner, Observer { 
result ->
+        transactionManager.transactions.observe(viewLifecycleOwner, { result ->
             onTransactionsResult(result)
         })
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
-        model.balances.observe(viewLifecycleOwner, Observer { balances ->
+        model.balances.observe(viewLifecycleOwner, { balances ->
             balances.find { it.currency == currency }?.available?.let { amount 
->
                 requireActivity().title =
                     getString(R.string.transactions_detail_title_balance, 
amount)
@@ -153,35 +154,35 @@ class TransactionsFragment : Fragment(), 
OnTransactionClickListener, ActionMode.
 
     override fun onTransactionClicked(transaction: Transaction) {
         if (actionMode != null) return // don't react on clicks while in 
action mode
-        if (transaction.detailPageLayout != 0) {
+        if (transaction.detailPageNav != 0) {
             transactionManager.selectedTransaction = transaction
-            findNavController().navigate(R.id.action_nav_transaction_detail)
+            findNavController().navigate(transaction.detailPageNav)
         }
     }
 
     private fun onTransactionsResult(result: TransactionsResult) = when 
(result) {
         is TransactionsResult.Error -> {
-            list.fadeOut()
-            emptyState.text = getString(R.string.transactions_error, 
result.msg)
-            emptyState.fadeIn()
+            ui.list.fadeOut()
+            ui.emptyState.text = getString(R.string.transactions_error, 
result.msg)
+            ui.emptyState.fadeIn()
         }
         is TransactionsResult.Success -> {
             if (result.transactions.isEmpty()) {
                 val isSearch = transactionManager.searchQuery.value != null
-                emptyState.setText(if (isSearch) 
R.string.transactions_empty_search else R.string.transactions_empty)
-                emptyState.fadeIn()
-                list.fadeOut()
+                ui.emptyState.setText(if (isSearch) 
R.string.transactions_empty_search else R.string.transactions_empty)
+                ui.emptyState.fadeIn()
+                ui.list.fadeOut()
             } else {
-                emptyState.fadeOut()
+                ui.emptyState.fadeOut()
                 transactionAdapter.update(result.transactions)
-                list.fadeIn()
+                ui.list.fadeIn()
             }
         }
     }
 
     private fun onSearch(query: String) {
-        list.fadeOut()
-        progressBar.fadeIn()
+        ui.list.fadeOut()
+        ui.progressBar.fadeIn()
         transactionManager.searchQuery.value = query
     }
 
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt
index fa5ab2f..14389c4 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ErrorFragment.kt
@@ -25,38 +25,46 @@ 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_error.*
-import net.taler.wallet.R
+import net.taler.common.isOnline
 import net.taler.wallet.MainViewModel
+import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentErrorBinding
 
 class ErrorFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val withdrawManager by lazy { model.withdrawManager }
 
+    private lateinit var ui: FragmentErrorBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_error, container, false)
+        ui = FragmentErrorBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        errorTitle.setText(R.string.withdraw_error_title)
-        errorMessage.setText(R.string.withdraw_error_message)
+        ui.errorTitle.setText(R.string.withdraw_error_title)
+        if (requireContext().isOnline()) {
+            ui.errorMessage.setText(R.string.withdraw_error_message)
+        } else {
+            ui.errorMessage.setText(R.string.offline)
+        }
 
         // show dev error message if dev mode is on
         val status = withdrawManager.withdrawStatus.value
         if (model.devMode.value == true && status is WithdrawStatus.Error) {
-            errorDevMessage.visibility = VISIBLE
-            errorDevMessage.text = status.message
+            ui.errorDevMessage.visibility = VISIBLE
+            ui.errorDevMessage.text = status.message
         } else {
-            errorDevMessage.visibility = GONE
+            ui.errorDevMessage.visibility = GONE
         }
 
-        backButton.setOnClickListener {
+        ui.backButton.setOnClickListener {
             findNavController().navigateUp()
         }
     }
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 fbee6ae..4b56dd0 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -25,11 +25,11 @@ import android.widget.Toast
 import android.widget.Toast.LENGTH_SHORT
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import kotlinx.android.synthetic.main.fragment_manual_withdraw.*
 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
 import net.taler.wallet.scanQrCode
 import java.util.Locale
 
@@ -40,28 +40,38 @@ class ManualWithdrawFragment : Fragment() {
     private val exchangeItem by lazy { 
requireNotNull(exchangeManager.withdrawalExchange) }
     private val withdrawManager by lazy { model.withdrawManager }
 
+    private lateinit var ui: FragmentManualWithdrawBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_manual_withdraw, container, 
false)
+        ui = FragmentManualWithdrawBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        qrCodeButton.setOnClickListener { scanQrCode(requireActivity()) }
-        currencyView.text = exchangeItem.currency
+        ui.qrCodeButton.setOnClickListener { scanQrCode(requireActivity()) }
+        ui.currencyView.text = exchangeItem.currency
         val paymentOptions = exchangeItem.paytoUris.mapNotNull {paytoUri ->
             Uri.parse(paytoUri).authority?.toUpperCase(Locale.getDefault())
         }.joinToString(separator = "\n", prefix = "• ")
-        paymentOptionsLabel.text =
+        ui.paymentOptionsLabel.text =
             getString(R.string.withdraw_manual_payment_options, 
exchangeItem.name, paymentOptions)
-        checkFeesButton.setOnClickListener {
-            val value = amountView.text.toString().toLong()
-            val amount = Amount(exchangeItem.currency, value, 0)
-            amountView.hideKeyboard()
-            Toast.makeText(view.context, "Not implemented: $amount", 
LENGTH_SHORT).show()
-            withdrawManager.getWithdrawalDetails(exchangeItem.exchangeBaseUrl, 
amount)
+        ui.checkFeesButton.setOnClickListener { onCheckFees() }
+    }
+
+    private fun onCheckFees() {
+        if (ui.amountView.text?.isEmpty() != false) {
+            ui.amountLayout.error = getString(R.string.withdraw_amount_error)
+            return
         }
+        ui.amountLayout.error = null
+        val value = ui.amountView.text.toString().toLong()
+        val amount = Amount(exchangeItem.currency, value, 0)
+        ui.amountView.hideKeyboard()
+        Toast.makeText(requireContext(), "Not implemented: $amount", 
LENGTH_SHORT).show()
+        withdrawManager.getWithdrawalDetails(exchangeItem.exchangeBaseUrl, 
amount)
     }
 
 }
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 ffc64d4..0c7687c 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -24,17 +24,16 @@ import android.widget.Toast
 import android.widget.Toast.LENGTH_SHORT
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import com.google.android.material.snackbar.Snackbar
 import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
-import kotlinx.android.synthetic.main.fragment_prompt_withdraw.*
 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
+import net.taler.wallet.databinding.FragmentPromptWithdrawBinding
 import net.taler.wallet.withdraw.WithdrawStatus.Loading
 import net.taler.wallet.withdraw.WithdrawStatus.TosReviewRequired
 import net.taler.wallet.withdraw.WithdrawStatus.Withdrawing
@@ -44,17 +43,20 @@ class PromptWithdrawFragment : Fragment() {
     private val model: MainViewModel by activityViewModels()
     private val withdrawManager by lazy { model.withdrawManager }
 
+    private lateinit var ui: FragmentPromptWithdrawBinding
+
     override fun onCreateView(
         inflater: LayoutInflater, container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_prompt_withdraw, container, 
false)
+        ui = FragmentPromptWithdrawBinding.inflate(inflater, container, false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
+        withdrawManager.withdrawStatus.observe(viewLifecycleOwner, {
             showWithdrawStatus(it)
         })
     }
@@ -62,11 +64,11 @@ class PromptWithdrawFragment : Fragment() {
     private fun showWithdrawStatus(status: WithdrawStatus?): Any = when 
(status) {
         is WithdrawStatus.ReceivedDetails -> {
             showContent(status.amountRaw, status.amountEffective, 
status.exchangeBaseUrl)
-            confirmWithdrawButton.apply {
+            ui.confirmWithdrawButton.apply {
                 text = getString(R.string.withdraw_button_confirm)
                 setOnClickListener {
                     it.fadeOut()
-                    confirmProgressBar.fadeIn()
+                    ui.confirmProgressBar.fadeIn()
                     withdrawManager.acceptWithdrawal()
                 }
                 isEnabled = true
@@ -87,7 +89,7 @@ class PromptWithdrawFragment : Fragment() {
         }
         is TosReviewRequired -> {
             showContent(status.amountRaw, status.amountEffective, 
status.exchangeBaseUrl)
-            confirmWithdrawButton.apply {
+            ui.confirmWithdrawButton.apply {
                 text = getString(R.string.withdraw_button_tos)
                 setOnClickListener {
                     
findNavController().navigate(R.id.action_promptWithdraw_to_reviewExchangeTOS)
@@ -104,29 +106,29 @@ class PromptWithdrawFragment : Fragment() {
 
     private fun showContent(amountRaw: Amount, amountEffective: Amount, 
exchange: String) {
         model.showProgressBar.value = false
-        progressBar.fadeOut()
+        ui.progressBar.fadeOut()
 
-        introView.fadeIn()
-        effectiveAmountView.text = amountEffective.toString()
-        effectiveAmountView.fadeIn()
+        ui.introView.fadeIn()
+        ui.effectiveAmountView.text = amountEffective.toString()
+        ui.effectiveAmountView.fadeIn()
 
-        chosenAmountLabel.fadeIn()
-        chosenAmountView.text = amountRaw.toString()
-        chosenAmountView.fadeIn()
+        ui.chosenAmountLabel.fadeIn()
+        ui.chosenAmountView.text = amountRaw.toString()
+        ui.chosenAmountView.fadeIn()
 
-        feeLabel.fadeIn()
-        feeView.text = getString(R.string.amount_negative, (amountRaw - 
amountEffective).toString())
-        feeView.fadeIn()
+        ui.feeLabel.fadeIn()
+        ui.feeView.text = getString(R.string.amount_negative, (amountRaw - 
amountEffective).toString())
+        ui.feeView.fadeIn()
 
-        exchangeIntroView.fadeIn()
-        withdrawExchangeUrl.text = cleanExchange(exchange)
-        withdrawExchangeUrl.fadeIn()
-        selectExchangeButton.fadeIn()
-        selectExchangeButton.setOnClickListener {
+        ui.exchangeIntroView.fadeIn()
+        ui.withdrawExchangeUrl.text = cleanExchange(exchange)
+        ui.withdrawExchangeUrl.fadeIn()
+        ui.selectExchangeButton.fadeIn()
+        ui.selectExchangeButton.setOnClickListener {
             Toast.makeText(context, "Not yet implemented", LENGTH_SHORT).show()
         }
 
-        withdrawCard.fadeIn()
+        ui.withdrawCard.fadeIn()
     }
 
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
index db1f326..73fe760 100644
--- 
a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
+++ 
b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt
@@ -25,17 +25,19 @@ import androidx.fragment.app.activityViewModels
 import androidx.lifecycle.Observer
 import androidx.navigation.fragment.findNavController
 import io.noties.markwon.Markwon
-import kotlinx.android.synthetic.main.fragment_review_exchange_tos.*
 import net.taler.common.fadeIn
 import net.taler.common.fadeOut
 import net.taler.wallet.MainViewModel
 import net.taler.wallet.R
+import net.taler.wallet.databinding.FragmentReviewExchangeTosBinding
 import java.text.ParseException
 
 class ReviewExchangeTosFragment : Fragment() {
 
     private val model: MainViewModel by activityViewModels()
     private val withdrawManager by lazy { model.withdrawManager }
+
+    private lateinit var ui: FragmentReviewExchangeTosBinding
     private val markwon by lazy { Markwon.builder(requireContext()).build() }
     private val adapter by lazy { TosAdapter(markwon) }
 
@@ -44,13 +46,14 @@ class ReviewExchangeTosFragment : Fragment() {
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View? {
-        return inflater.inflate(R.layout.fragment_review_exchange_tos, 
container, false)
+        ui = FragmentReviewExchangeTosBinding.inflate(inflater, container, 
false)
+        return ui.root
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        acceptTosCheckBox.isChecked = false
-        acceptTosCheckBox.setOnCheckedChangeListener { _, _ ->
+        ui.acceptTosCheckBox.isChecked = false
+        ui.acceptTosCheckBox.setOnCheckedChangeListener { _, _ ->
             withdrawManager.acceptCurrentTermsOfService()
         }
         withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer {
@@ -65,11 +68,11 @@ class ReviewExchangeTosFragment : Fragment() {
                         return@Observer
                     }
                     adapter.setSections(sections)
-                    tosList.adapter = adapter
-                    tosList.fadeIn()
+                    ui.tosList.adapter = adapter
+                    ui.tosList.fadeIn()
 
-                    acceptTosCheckBox.fadeIn()
-                    progressBar.fadeOut()
+                    ui.acceptTosCheckBox.fadeIn()
+                    ui.progressBar.fadeOut()
                 }
                 is WithdrawStatus.Loading -> {
                     
findNavController().navigate(R.id.action_reviewExchangeTOS_to_promptWithdraw)
@@ -82,11 +85,11 @@ class ReviewExchangeTosFragment : Fragment() {
     }
 
     private fun onTosError(msg: String) {
-        tosList.fadeIn()
-        progressBar.fadeOut()
-        buttonCard.fadeOut()
-        errorView.text = getString(R.string.exchange_tos_error, "\n\n$msg")
-        errorView.fadeIn()
+        ui.tosList.fadeIn()
+        ui.progressBar.fadeOut()
+        ui.buttonCard.fadeOut()
+        ui.errorView.text = getString(R.string.exchange_tos_error, "\n\n$msg")
+        ui.errorView.fadeIn()
     }
 
 }
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 b6b4285..25c5b72 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -24,8 +24,8 @@ import kotlinx.coroutines.launch
 import kotlinx.serialization.Serializable
 import net.taler.lib.common.Amount
 import net.taler.wallet.TAG
-import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.backend.TalerErrorInfo
+import net.taler.wallet.backend.WalletBackendApi
 import net.taler.wallet.exchanges.ExchangeFees
 import net.taler.wallet.exchanges.ExchangeItem
 import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
@@ -89,14 +89,11 @@ class WithdrawManager(
 
     fun getWithdrawalDetails(uri: String) = scope.launch {
         withdrawStatus.value = WithdrawStatus.Loading(uri)
-        val response =
-            api.request("getWithdrawalDetailsForUri", 
WithdrawalDetailsForUri.serializer()) {
-                put("talerWithdrawUri", uri)
-            }
-        response.onError { error ->
+        api.request("getWithdrawalDetailsForUri", 
WithdrawalDetailsForUri.serializer()) {
+            put("talerWithdrawUri", uri)
+        }.onError { error ->
             handleError("getWithdrawalDetailsForUri", error)
-        }
-        response.onSuccess { details ->
+        }.onSuccess { details ->
             if (details.defaultExchangeBaseUrl == null) {
                 // TODO go to exchange selection screen instead
                 val chosenExchange = 
details.possibleExchanges[0].exchangeBaseUrl
@@ -113,15 +110,12 @@ class WithdrawManager(
         uri: String? = null
     ) = scope.launch {
         withdrawStatus.value = WithdrawStatus.Loading(uri)
-        val response =
-            api.request("getWithdrawalDetailsForAmount", 
WithdrawalDetails.serializer()) {
-                put("exchangeBaseUrl", exchangeBaseUrl)
-                put("amount", amount.toJSONString())
-            }
-        response.onError { error ->
+        api.request("getWithdrawalDetailsForAmount", 
WithdrawalDetails.serializer()) {
+            put("exchangeBaseUrl", exchangeBaseUrl)
+            put("amount", amount.toJSONString())
+        }.onError { error ->
             handleError("getWithdrawalDetailsForAmount", error)
-        }
-        response.onSuccess { details ->
+        }.onSuccess { details ->
             if (details.tosAccepted) {
                 withdrawStatus.value = ReceivedDetails(
                     talerWithdrawUri = uri,
@@ -138,13 +132,11 @@ class WithdrawManager(
         details: WithdrawalDetails,
         uri: String?
     ) = scope.launch {
-        val response = api.request("getExchangeTos", TosResponse.serializer()) 
{
+        api.request("getExchangeTos", TosResponse.serializer()) {
             put("exchangeBaseUrl", exchangeBaseUrl)
-        }
-        response.onError {
+        }.onError {
             handleError("getExchangeTos", it)
-        }
-        response.onSuccess {
+        }.onSuccess {
             withdrawStatus.value = WithdrawStatus.TosReviewRequired(
                 talerWithdrawUri = uri,
                 exchangeBaseUrl = exchangeBaseUrl,
diff --git a/wallet/src/main/res/layout/activity_main.xml 
b/wallet/src/main/res/layout/activity_main.xml
index 3879490..15e11fe 100644
--- a/wallet/src/main/res/layout/activity_main.xml
+++ b/wallet/src/main/res/layout/activity_main.xml
@@ -24,7 +24,8 @@
     tools:openDrawer="start">
 
     <include
-        layout="@layout/app_bar_main"
+        android:id="@+id/content"
+        layout="@layout/app_content_main"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
diff --git a/wallet/src/main/res/layout/app_bar_main.xml 
b/wallet/src/main/res/layout/app_content_main.xml
similarity index 100%
rename from wallet/src/main/res/layout/app_bar_main.xml
rename to wallet/src/main/res/layout/app_content_main.xml
diff --git a/wallet/src/main/res/layout/fragment_prompt_payment.xml 
b/wallet/src/main/res/layout/fragment_prompt_payment.xml
index 8d8954d..8cfa3dd 100644
--- a/wallet/src/main/res/layout/fragment_prompt_payment.xml
+++ b/wallet/src/main/res/layout/fragment_prompt_payment.xml
@@ -22,23 +22,23 @@
     tools:context=".payment.PromptPaymentFragment">
 
     <include
-        android:id="@+id/scrollView"
+        android:id="@+id/details"
         layout="@layout/payment_details"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        app:layout_constraintBottom_toTopOf="@+id/bottomView"
+        app:layout_constraintBottom_toTopOf="@+id/bottom"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
     <include
-        android:id="@+id/bottomView"
+        android:id="@+id/bottom"
         layout="@layout/payment_bottom_bar"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/scrollView" />
+        app:layout_constraintTop_toBottomOf="@+id/details" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/wallet/src/main/res/layout/payment_bottom_bar.xml 
b/wallet/src/main/res/layout/payment_bottom_bar.xml
index dbc60ae..496f2f3 100644
--- a/wallet/src/main/res/layout/payment_bottom_bar.xml
+++ b/wallet/src/main/res/layout/payment_bottom_bar.xml
@@ -17,6 +17,7 @@
 <com.google.android.material.card.MaterialCardView 
xmlns:android="http://schemas.android.com/apk/res/android";
     xmlns:app="http://schemas.android.com/apk/res-auto";
     xmlns:tools="http://schemas.android.com/tools";
+    android:id="@+id/bottomView"
     style="@style/BottomCard"
     android:layout_width="0dp"
     android:layout_height="wrap_content"
diff --git a/wallet/src/main/res/navigation/nav_graph.xml 
b/wallet/src/main/res/navigation/nav_graph.xml
index 285fac9..d8ce5b2 100644
--- a/wallet/src/main/res/navigation/nav_graph.xml
+++ b/wallet/src/main/res/navigation/nav_graph.xml
@@ -89,8 +89,26 @@
         tools:layout="@layout/fragment_transactions" />
 
     <fragment
-        android:id="@+id/nav_transactions_detail"
-        android:name="net.taler.wallet.transactions.TransactionDetailFragment"
+        android:id="@+id/nav_transactions_detail_withdrawal"
+        
android:name="net.taler.wallet.transactions.TransactionWithdrawalFragment"
+        android:label="@string/transactions_detail_title"
+        tools:layout="@layout/fragment_transaction_withdrawal" />
+
+    <fragment
+        android:id="@+id/nav_transactions_detail_payment"
+        android:name="net.taler.wallet.transactions.TransactionPaymentFragment"
+        android:label="@string/transactions_detail_title"
+        tools:layout="@layout/fragment_transaction_payment" />
+
+    <fragment
+        android:id="@+id/nav_transactions_detail_refund"
+        android:name="net.taler.wallet.transactions.TransactionRefundFragment"
+        android:label="@string/transactions_detail_title"
+        tools:layout="@layout/fragment_transaction_payment" />
+
+    <fragment
+        android:id="@+id/nav_transactions_detail_refresh"
+        android:name="net.taler.wallet.transactions.TransactionRefreshFragment"
         android:label="@string/transactions_detail_title"
         tools:layout="@layout/fragment_transaction_withdrawal" />
 
@@ -168,7 +186,19 @@
         app:destination="@id/nav_pending_operations" />
 
     <action
-        android:id="@+id/action_nav_transaction_detail"
-        app:destination="@id/nav_transactions_detail" />
+        android:id="@+id/action_nav_transactions_detail_withdrawal"
+        app:destination="@id/nav_transactions_detail_withdrawal" />
+
+    <action
+        android:id="@+id/action_nav_transactions_detail_payment"
+        app:destination="@id/nav_transactions_detail_payment" />
+
+    <action
+        android:id="@+id/action_nav_transactions_detail_refund"
+        app:destination="@id/nav_transactions_detail_refund" />
+
+    <action
+        android:id="@+id/action_nav_transactions_detail_refresh"
+        app:destination="@id/nav_transactions_detail_refresh" />
 
 </navigation>
diff --git a/wallet/src/main/res/values/strings.xml 
b/wallet/src/main/res/values/strings.xml
index a9f6c73..d49f5f7 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -54,6 +54,8 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="menu">Menu</string>
     <string name="or">or</string>
 
+    <string name="offline">Operation requires internet access. Please ensure 
your internet connection works and try again.</string>
+
     <string name="menu_settings">Settings</string>
     <string name="menu_retry_pending_operations">Retry Pending 
Operations</string>
 
@@ -113,6 +115,7 @@ GNU Taler is immune against many types of fraud, such as 
phishing of credit card
     <string name="withdraw_waiting_confirm">Waiting for confirmation</string>
     <string name="withdraw_manual_title">Make a manual transfer to the 
exchange</string>
     <string name="withdraw_amount">How much to withdraw?</string>
+    <string name="withdraw_amount_error">Enter valid amount</string>
     <string name="withdraw_manual_payment_options">Payment options supported 
by %1$s:\n\n%2$s</string>
     <string name="withdraw_manual_check_fees">Check fees</string>
     <string name="withdraw_error_title">Withdrawal Error</string>

-- 
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]