summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler/wallet/exchanges
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/exchanges')
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/AddExchangeDialogFragment.kt3
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt36
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt5
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt10
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt85
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt128
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt38
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeDialogFragment.kt111
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt48
9 files changed, 383 insertions, 81 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/AddExchangeDialogFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/AddExchangeDialogFragment.kt
index 5ea763a..fd942b0 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/AddExchangeDialogFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/AddExchangeDialogFragment.kt
@@ -22,6 +22,7 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
@@ -32,7 +33,7 @@ class AddExchangeDialogFragment : DialogFragment() {
private val exchangeManager by lazy { model.exchangeManager }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- return AlertDialog.Builder(requireContext(), R.style.DialogTheme)
+ return MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
.setIcon(R.drawable.ic_account_balance)
.setTitle(R.string.exchange_list_add)
.setView(R.layout.dialog_exchange_add)
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt
index e315632..674632e 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeAdapter.kt
@@ -26,23 +26,15 @@ import android.widget.TextView
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
-import kotlinx.serialization.Serializable
import net.taler.wallet.R
-import net.taler.wallet.cleanExchange
import net.taler.wallet.exchanges.ExchangeAdapter.ExchangeItemViewHolder
-@Serializable
-data class ExchangeItem(
- val exchangeBaseUrl: String,
- val currency: String,
- val paytoUris: List<String>
-) {
- val name: String get() = cleanExchange(exchangeBaseUrl)
-}
-
interface ExchangeClickListener {
fun onExchangeSelected(item: ExchangeItem)
fun onManualWithdraw(item: ExchangeItem)
+ fun onPeerReceive(item: ExchangeItem)
+ fun onExchangeReload(item: ExchangeItem)
+ fun onExchangeDelete(item: ExchangeItem)
}
internal class ExchangeAdapter(
@@ -78,14 +70,20 @@ internal class ExchangeAdapter(
fun bind(item: ExchangeItem) {
urlView.text = item.name
- currencyView.text = context.getString(R.string.exchange_list_currency, item.currency)
+ // If currency is null, it's because we have no data from the exchange...
+ currencyView.text = if (item.currency == null) {
+ context.getString(R.string.exchange_not_contacted)
+ } else {
+ context.getString(R.string.exchange_list_currency, item.currency)
+ }
if (selectOnly) {
itemView.setOnClickListener { listener.onExchangeSelected(item) }
overflowIcon.visibility = GONE
} else {
itemView.setOnClickListener(null)
itemView.isClickable = false
- overflowIcon.visibility = VISIBLE
+ // ...thus, we should prevent the user from interacting with it.
+ overflowIcon.visibility = if (item.currency != null) VISIBLE else GONE
}
overflowIcon.setOnClickListener { openMenu(overflowIcon, item) }
}
@@ -98,6 +96,18 @@ internal class ExchangeAdapter(
listener.onManualWithdraw(item)
true
}
+ R.id.action_receive_peer -> {
+ listener.onPeerReceive(item)
+ true
+ }
+ R.id.action_reload -> {
+ listener.onExchangeReload(item)
+ true
+ }
+ R.id.action_delete -> {
+ listener.onExchangeDelete(item)
+ true
+ }
else -> false
}
}
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index 1da9b49..7824d1e 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -16,8 +16,9 @@
package net.taler.wallet.exchanges
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Amount
+import net.taler.common.Timestamp
+
data class CoinFee(
val coin: Amount,
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
index c59fffe..d42ab2c 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
@@ -22,19 +22,19 @@ import android.view.View
import android.view.View.GONE
import android.view.ViewGroup
import android.widget.TextView
-import androidx.core.content.ContextCompat.getColor
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.common.Amount
import net.taler.common.toRelativeTime
import net.taler.common.toShortDate
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.databinding.FragmentExchangeFeesBinding
import net.taler.wallet.exchanges.CoinFeeAdapter.CoinFeeViewHolder
import net.taler.wallet.exchanges.WireFeeAdapter.WireFeeViewHolder
+import net.taler.wallet.getAttrColor
class ExchangeFeesFragment : Fragment() {
@@ -71,7 +71,7 @@ class ExchangeFeesFragment : Fragment() {
if (amount.isZero()) text = amount.toString()
else {
text = getString(R.string.amount_negative, amount)
- setTextColor(getColor(context, R.color.red))
+ setText(requireContext().getAttrColor(R.attr.colorError))
}
}
@@ -89,7 +89,7 @@ private class CoinFeeAdapter(private val items: List<CoinFee>) : Adapter<CoinFee
holder.bind(items[position])
}
- private class CoinFeeViewHolder(private val v: View) : ViewHolder(v) {
+ class CoinFeeViewHolder(private val v: View) : ViewHolder(v) {
private val res = v.context.resources
private val coinView: TextView = v.findViewById(R.id.coinView)
private val withdrawFeeView: TextView = v.findViewById(R.id.withdrawFeeView)
@@ -127,7 +127,7 @@ private class WireFeeAdapter(private val items: List<WireFee>) : Adapter<WireFee
holder.bind(items[position])
}
- private class WireFeeViewHolder(private val v: View) : ViewHolder(v) {
+ class WireFeeViewHolder(private val v: View) : ViewHolder(v) {
private val validityView: TextView = v.findViewById(R.id.validityView)
private val wireFeeView: TextView = v.findViewById(R.id.wireFeeView)
private val closingFeeView: TextView = v.findViewById(R.id.closingFeeView)
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 9a96b59..8a40bff 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeListFragment.kt
@@ -18,26 +18,35 @@ package net.taler.wallet.exchanges
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.ViewGroup
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
+import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle.State.RESUMED
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import net.taler.common.EventObserver
import net.taler.common.fadeIn
import net.taler.common.fadeOut
+import net.taler.common.showError
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.databinding.FragmentExchangeListBinding
+import net.taler.wallet.showError
open class ExchangeListFragment : Fragment(), ExchangeClickListener {
protected val model: MainViewModel by activityViewModels()
private val exchangeManager by lazy { model.exchangeManager }
+ private val transactionManager get() = model.transactionManager
protected lateinit var ui: FragmentExchangeListBinding
protected open val isSelectOnly = false
@@ -53,6 +62,21 @@ open class ExchangeListFragment : Fragment(), ExchangeClickListener {
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ requireActivity().addMenuProvider(object : MenuProvider {
+ override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
+ if (model.devMode.value == true) {
+ menuInflater.inflate(R.menu.exchange_list, menu)
+ }
+ }
+
+ override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
+ if (menuItem.itemId == R.id.action_add_dev_exchanges) {
+ exchangeManager.addDevExchanges()
+ }
+ return true
+ }
+ }, viewLifecycleOwner, RESUMED)
+
ui.list.apply {
adapter = exchangeAdapter
addItemDecoration(DividerItemDecoration(context, VERTICAL))
@@ -61,14 +85,37 @@ open class ExchangeListFragment : Fragment(), ExchangeClickListener {
AddExchangeDialogFragment().show(parentFragmentManager, "ADD_EXCHANGE")
}
- exchangeManager.progress.observe(viewLifecycleOwner, { show ->
+ exchangeManager.progress.observe(viewLifecycleOwner) { show ->
if (show) ui.progressBar.fadeIn() else ui.progressBar.fadeOut()
- })
- exchangeManager.exchanges.observe(viewLifecycleOwner, { exchanges ->
+ }
+ exchangeManager.exchanges.observe(viewLifecycleOwner) { exchanges ->
onExchangeUpdate(exchanges)
- })
+ }
exchangeManager.addError.observe(viewLifecycleOwner, EventObserver { error ->
- if (error) onAddExchangeFailed()
+ onAddExchangeFailed()
+ if (model.devMode.value == true) {
+ showError(error)
+ }
+ })
+ exchangeManager.listError.observe(viewLifecycleOwner, EventObserver { error ->
+ onListExchangeFailed()
+ if (model.devMode.value == true) {
+ showError(error)
+ }
+ })
+ exchangeManager.deleteError.observe(viewLifecycleOwner, EventObserver { error ->
+ if (model.devMode.value == true) {
+ showError(error)
+ } else {
+ showError(error.userFacingMsg)
+ }
+ })
+ exchangeManager.reloadError.observe(viewLifecycleOwner, EventObserver { error ->
+ if (model.devMode.value == true) {
+ showError(error)
+ } else {
+ showError(error.userFacingMsg)
+ }
})
}
@@ -87,6 +134,10 @@ open class ExchangeListFragment : Fragment(), ExchangeClickListener {
Toast.makeText(requireContext(), R.string.exchange_add_error, LENGTH_LONG).show()
}
+ private fun onListExchangeFailed() {
+ Toast.makeText(requireContext(), R.string.exchange_list_error, LENGTH_LONG).show()
+ }
+
override fun onExchangeSelected(item: ExchangeItem) {
throw AssertionError("must not get triggered here")
}
@@ -96,4 +147,28 @@ open class ExchangeListFragment : Fragment(), ExchangeClickListener {
findNavController().navigate(R.id.action_nav_settings_exchanges_to_nav_exchange_manual_withdrawal)
}
+ override fun onPeerReceive(item: ExchangeItem) {
+ transactionManager.selectedScope = item.scopeInfo
+ findNavController().navigate(R.id.action_global_receiveFunds)
+ }
+
+ override fun onExchangeReload(item: ExchangeItem) {
+ exchangeManager.reload(item.exchangeBaseUrl)
+ }
+
+ override fun onExchangeDelete(item: ExchangeItem) {
+ val optionsArray = arrayOf(getString(R.string.exchange_delete_force))
+ val checkedArray = BooleanArray(1) { false }
+
+ MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
+ .setTitle(R.string.exchange_delete)
+ .setMultiChoiceItems(optionsArray, checkedArray) { _, which, isChecked ->
+ checkedArray[which] = isChecked
+ }
+ .setNegativeButton(R.string.transactions_delete) { _, _ ->
+ exchangeManager.delete(item.exchangeBaseUrl, checkedArray[0])
+ }
+ .setPositiveButton(R.string.cancel) { _, _ -> }
+ .show()
+ }
}
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
index 8205eb7..fa357b5 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt
@@ -17,24 +17,34 @@
package net.taler.wallet.exchanges
import android.util.Log
+import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import net.taler.common.Event
import net.taler.common.toEvent
import net.taler.wallet.TAG
+import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
@Serializable
data class ExchangeListResponse(
- val exchanges: List<ExchangeItem>
+ val exchanges: List<ExchangeItem>,
+)
+
+@Serializable
+data class ExchangeDetailedResponse(
+ val exchange: ExchangeItem,
)
class ExchangeManager(
private val api: WalletBackendApi,
- private val scope: CoroutineScope
+ private val scope: CoroutineScope,
) {
private val mProgress = MutableLiveData<Boolean>()
@@ -43,8 +53,17 @@ class ExchangeManager(
private val mExchanges = MutableLiveData<List<ExchangeItem>>()
val exchanges: LiveData<List<ExchangeItem>> get() = list()
- private val mAddError = MutableLiveData<Event<Boolean>>()
- val addError: LiveData<Event<Boolean>> = mAddError
+ private val mAddError = MutableLiveData<Event<TalerErrorInfo>>()
+ val addError: LiveData<Event<TalerErrorInfo>> = mAddError
+
+ private val mListError = MutableLiveData<Event<TalerErrorInfo>>()
+ val listError: LiveData<Event<TalerErrorInfo>> = mListError
+
+ private val mDeleteError = MutableLiveData<Event<TalerErrorInfo>>()
+ val deleteError: LiveData<Event<TalerErrorInfo>> = mDeleteError
+
+ private val mReloadError = MutableLiveData<Event<TalerErrorInfo>>()
+ val reloadError: LiveData<Event<TalerErrorInfo>> = mReloadError
var withdrawalExchange: ExchangeItem? = null
@@ -53,7 +72,8 @@ class ExchangeManager(
scope.launch {
val response = api.request("listExchanges", ExchangeListResponse.serializer())
response.onError {
- throw AssertionError("Wallet core failed to return exchanges!")
+ mProgress.value = false
+ mListError.value = it.toEvent()
}.onSuccess {
Log.d(TAG, "Exchange list: ${it.exchanges}")
mProgress.value = false
@@ -68,9 +88,9 @@ class ExchangeManager(
api.request<Unit>("addExchange") {
put("exchangeBaseUrl", exchangeUrl)
}.onError {
- mProgress.value = false
Log.e(TAG, "Error adding exchange: $it")
- mAddError.value = true.toEvent()
+ mProgress.value = false
+ mAddError.value = it.toEvent()
}.onSuccess {
mProgress.value = false
Log.d(TAG, "Exchange $exchangeUrl added")
@@ -78,4 +98,98 @@ class ExchangeManager(
}
}
+ fun reload(exchangeUrl: String, force: Boolean = true) = scope.launch {
+ mProgress.value = true
+ api.request<Unit>("updateExchangeEntry") {
+ put("exchangeBaseUrl", exchangeUrl)
+ put("force", force)
+ }.onError {
+ Log.e(TAG, "Error reloading exchange: $it")
+ mProgress.value = false
+ mReloadError.value = it.toEvent()
+ }.onSuccess {
+ mProgress.value = false
+ Log.d(TAG, "Exchange $exchangeUrl reloaded")
+ list()
+ }
+ }
+
+ fun delete(exchangeUrl: String, purge: Boolean = false) = scope.launch {
+ mProgress.value = true
+ api.request<Unit>("deleteExchange") {
+ put("exchangeBaseUrl", exchangeUrl)
+ put("purge", purge)
+ }.onError {
+ Log.e(TAG, "Error deleting exchange: $it")
+ mProgress.value = false
+ mDeleteError.value = it.toEvent()
+ }.onSuccess {
+ mProgress.value = false
+ Log.d(TAG, "Exchange $exchangeUrl deleted")
+ list()
+ }
+ }
+
+ fun findExchangeForCurrency(currency: String): Flow<ExchangeItem?> = flow {
+ emit(findExchange(currency))
+ }
+
+ @WorkerThread
+ suspend fun findExchange(currency: String): ExchangeItem? {
+ var exchange: ExchangeItem? = null
+ api.request(
+ operation = "listExchanges",
+ serializer = ExchangeListResponse.serializer()
+ ).onSuccess { exchangeListResponse ->
+ // just pick the first for now
+ exchange = exchangeListResponse.exchanges.find { it.currency == currency }
+ }
+ return exchange
+ }
+
+ @WorkerThread
+ suspend fun findExchangeByUrl(exchangeUrl: String): ExchangeItem? {
+ var exchange: ExchangeItem? = null
+ api.request("getExchangeDetailedInfo", ExchangeDetailedResponse.serializer()) {
+ put("exchangeBaseUrl", exchangeUrl)
+ }.onError {
+ Log.e(TAG, "Error getExchangeDetailedInfo: $it")
+ }.onSuccess {
+ exchange = it.exchange
+ }
+ return exchange
+ }
+
+ fun addDevExchanges() {
+ scope.launch {
+ listOf(
+ "https://exchange.demo.taler.net/",
+ "https://exchange.test.taler.net/",
+ "https://exchange.head.taler.net/",
+ "https://exchange.taler.ar/",
+ "https://exchange.taler.fdold.eu/",
+ "https://exchange.taler.grothoff.org/",
+ ).forEach { exchangeUrl ->
+ add(exchangeUrl)
+ delay(100)
+ }
+ exchanges.value?.let { exs ->
+ exs.find {
+ it.exchangeBaseUrl.startsWith("https://exchange.taler.fdold.eu")
+ }?.let { fDoldExchange ->
+ api.request<Unit>("addGlobalCurrencyExchange") {
+ put("currency", fDoldExchange.currency)
+ put("exchangeBaseUrl", fDoldExchange.exchangeBaseUrl)
+ put("exchangeMasterPub",
+ "7ER30ZWJEXAG026H5KG9M19NGTFC2DKKFPV79GVXA6DK5DCNSWXG")
+ }.onError {
+ Log.e(TAG, "Error addGlobalCurrencyExchange: $it")
+ }.onSuccess {
+ Log.i(TAG, "fdold is global now!")
+ }
+ }
+ }
+ }
+ }
+
}
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
new file mode 100644
index 0000000..0015e1c
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/Exchanges.kt
@@ -0,0 +1,38 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2024 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.exchanges
+
+import kotlinx.serialization.Serializable
+import net.taler.wallet.balances.ScopeInfo
+import net.taler.wallet.cleanExchange
+
+@Serializable
+data class BuiltinExchange(
+ val exchangeBaseUrl: String,
+ val currencyHint: String? = null,
+)
+
+@Serializable
+data class ExchangeItem(
+ val exchangeBaseUrl: String,
+ // can be null before exchange info in wallet-core was fully loaded
+ val currency: String? = null,
+ val paytoUris: List<String>,
+ val scopeInfo: ScopeInfo? = null,
+) {
+ val name: String get() = cleanExchange(exchangeBaseUrl)
+} \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeDialogFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeDialogFragment.kt
new file mode 100644
index 0000000..136738b
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeDialogFragment.kt
@@ -0,0 +1,111 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2024 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.exchanges
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.ListItemDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.stringResource
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.asFlow
+import com.google.accompanist.themeadapter.material3.Mdc3Theme
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import net.taler.common.Event
+import net.taler.common.toEvent
+import net.taler.wallet.R
+import net.taler.wallet.cleanExchange
+import net.taler.wallet.compose.collectAsStateLifecycleAware
+
+class SelectExchangeDialogFragment: DialogFragment() {
+ private var exchangeList = MutableLiveData<List<ExchangeItem>>()
+
+ private var mExchangeSelection = MutableLiveData<Event<ExchangeItem>>()
+ val exchangeSelection: LiveData<Event<ExchangeItem>> = mExchangeSelection
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val view = ComposeView(requireContext()).apply {
+ setContent {
+ val exchanges = exchangeList.asFlow().collectAsStateLifecycleAware(initial = emptyList())
+ SelectExchangeComposable(exchanges.value) {
+ onExchangeSelected(it)
+ }
+ }
+ }
+
+ return MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
+ .setIcon(R.drawable.ic_account_balance)
+ .setTitle(R.string.exchange_list_select)
+ .setView(view)
+ .setNegativeButton(R.string.cancel) { _, _ ->
+ dismiss()
+ }
+ .create()
+ }
+
+ fun setExchanges(exchanges: List<ExchangeItem>) {
+ exchangeList.value = exchanges
+ }
+
+ private fun onExchangeSelected(exchange: ExchangeItem) {
+ mExchangeSelection.value = exchange.toEvent()
+ dismiss()
+ }
+}
+
+@Composable
+fun SelectExchangeComposable(
+ exchanges: List<ExchangeItem>,
+ onExchangeSelected: (exchange: ExchangeItem) -> Unit,
+) {
+ Mdc3Theme {
+ LazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ items(exchanges) {
+ ExchangeItemComposable(it) {
+ onExchangeSelected(it)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ExchangeItemComposable(exchange: ExchangeItem, onSelected: () -> Unit) {
+ ListItem(
+ modifier = Modifier.clickable { onSelected() },
+ headlineContent = { Text(cleanExchange(exchange.exchangeBaseUrl)) },
+ supportingContent = exchange.currency?.let {
+ { Text(stringResource(R.string.exchange_list_currency, it)) }
+ },
+ colors = ListItemDefaults.colors(
+ containerColor = Color.Transparent,
+ )
+ )
+} \ No newline at end of file
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
deleted file mode 100644
index 61e0db5..0000000
--- a/wallet/src/main/java/net/taler/wallet/exchanges/SelectExchangeFragment.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.exchanges
-
-import androidx.navigation.fragment.findNavController
-import net.taler.common.fadeOut
-
-class SelectExchangeFragment : ExchangeListFragment() {
-
- private val withdrawManager by lazy { model.withdrawManager }
-
- override val isSelectOnly = true
- private val exchangeSelection by lazy {
- requireNotNull(withdrawManager.exchangeSelection.value?.getEvenIfConsumedAlready())
- }
-
- override fun onExchangeUpdate(exchanges: List<ExchangeItem>) {
- ui.progressBar.fadeOut()
- super.onExchangeUpdate(exchanges.filter { exchangeItem ->
- exchangeItem.currency == exchangeSelection.amount.currency
- })
- }
-
- override fun onExchangeSelected(item: ExchangeItem) {
- withdrawManager.getWithdrawalDetails(
- exchangeBaseUrl = item.exchangeBaseUrl,
- amount = exchangeSelection.amount,
- showTosImmediately = true,
- uri = exchangeSelection.talerWithdrawUri,
- )
- findNavController().navigateUp()
- }
-
-}