taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit cc9de0a930827883596f0467a86f55e885b1c486
parent 1f73fe833d1ae040fddb413311f896d1c6859bd7
Author: Iván Ávalos <avalos@disroot.org>
Date:   Sat, 23 Nov 2024 23:58:30 +0100

[wallet] QC: language selector for ToS

Diffstat:
Mwallet/src/main/java/net/taler/wallet/balances/BalancesComposable.kt | 4+++-
Mwallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt | 6+++++-
Mwallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mwallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt | 4+++-
Mwallet/src/main/res/layout/fragment_review_exchange_tos.xml | 14+++++++++++++-
5 files changed, 98 insertions(+), 22 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalancesComposable.kt b/wallet/src/main/java/net/taler/wallet/balances/BalancesComposable.kt @@ -104,7 +104,9 @@ fun BalancesComposable( onTransactionsDelete = onTransactionsDelete, onShowBalancesClicked = onShowBalancesClicked, ) - } ?: error("no balance matching scopeInfo") + } ?: run { + onShowBalancesClicked() + } } } else { EmptyBalancesComposable( diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeManager.kt @@ -163,9 +163,13 @@ class ExchangeManager( /** * Fetch exchange terms of service. */ - suspend fun getExchangeTos(exchangeBaseUrl: String): TosResponse? { + suspend fun getExchangeTos( + exchangeBaseUrl: String, + language: String? = null, + ): TosResponse? { var result: TosResponse? = null api.request("getExchangeTos", TosResponse.serializer()) { + language?.let { put("acceptLanguage", it) } put("exchangeBaseUrl", exchangeBaseUrl) }.onError { error -> Log.d(TAG, "Error getExchangeTos: $error") diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt @@ -23,6 +23,8 @@ import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams +import android.widget.AdapterView +import android.widget.ArrayAdapter import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.marginBottom @@ -44,8 +46,9 @@ import net.taler.wallet.MainViewModel import net.taler.wallet.R import net.taler.wallet.databinding.FragmentReviewExchangeTosBinding import java.text.ParseException +import java.util.Locale -class ReviewExchangeTosFragment : Fragment() { +class ReviewExchangeTosFragment : Fragment(), AdapterView.OnItemSelectedListener { private val model: MainViewModel by activityViewModels() private val exchangeManager by lazy { model.exchangeManager } @@ -55,6 +58,10 @@ class ReviewExchangeTosFragment : Fragment() { private val adapter by lazy { TosAdapter(markwon) } private var tos: TosResponse? = null + private var exchangeBaseUrl: String? = null + private var langAdapter: ArrayAdapter<String>? = null + private var selectedLang: String? = null + private var manualSelect: Boolean = true override fun onCreateView( inflater: LayoutInflater, @@ -69,17 +76,22 @@ class ReviewExchangeTosFragment : Fragment() { super.onViewCreated(view, savedInstanceState) setupInsets() - val exchangeBaseUrl = arguments?.getString("exchangeBaseUrl") + exchangeBaseUrl = arguments?.getString("exchangeBaseUrl") ?: error("no exchangeBaseUrl passed") val readOnly = arguments?.getBoolean("readOnly") ?: false + langAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item) + langAdapter?.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + ui.langSpinner.adapter = langAdapter + ui.langSpinner.onItemSelectedListener = this + ui.buttonCard.visibility = if (readOnly) GONE else VISIBLE ui.acceptTosCheckBox.isChecked = false ui.acceptTosCheckBox.setOnCheckedChangeListener { _, _ -> tos?.let { viewLifecycleOwner.lifecycleScope.launch { if (exchangeManager.acceptCurrentTos( - exchangeBaseUrl = exchangeBaseUrl, + exchangeBaseUrl = exchangeBaseUrl!!, currentEtag = it.currentEtag, )) { findNavController().navigateUp() @@ -90,24 +102,54 @@ class ReviewExchangeTosFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - tos = exchangeManager.getExchangeTos(exchangeBaseUrl) - // FIXME: better null handling! - tos?.let { - val sections = try { - parseTos(markwon, it.content) - } catch (e: ParseException) { - onTosError(e.message ?: "Unknown Error") - return@repeatOnLifecycle - } + renderTos(exchangeBaseUrl!!, selectedLang) + } + } + } - adapter.setSections(sections) - ui.tosList.adapter = adapter - ui.tosList.fadeIn() + private suspend fun renderTos( + exchangeBaseUrl: String, + language: String? = null, + ) { + val lc = Locale.getDefault().language + selectedLang = language ?: lc + tos = exchangeManager.getExchangeTos(exchangeBaseUrl, selectedLang) + + // Setup language adapter + val languages = tos?.tosAvailableLanguages ?: emptyList() + langAdapter?.clear() + langAdapter?.addAll(languages.map { lang -> + Locale(lang).displayLanguage + }) + langAdapter?.notifyDataSetChanged() + + // Setup language spinner + if (languages.size > 1) { + ui.langSpinner.visibility = VISIBLE + val i = languages.indexOf(selectedLang) + if (i >= 0) { + manualSelect = false + ui.langSpinner.setSelection(i) + } + } else { + ui.langSpinner.visibility = GONE + } - ui.acceptTosCheckBox.fadeIn() - ui.progressBar.fadeOut() - } + // FIXME: better null handling! + tos?.let { + val sections = try { + parseTos(markwon, it.content) + } catch (e: ParseException) { + onTosError(e.message ?: "Unknown Error") + return } + + adapter.setSections(sections) + ui.tosList.adapter = adapter + ui.tosList.fadeIn() + + ui.acceptTosCheckBox.fadeIn() + ui.progressBar.fadeOut() } } @@ -145,4 +187,18 @@ class ReviewExchangeTosFragment : Fragment() { ui.errorView.fadeIn() } + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + if (manualSelect) { + tos?.tosAvailableLanguages?.get(position)?.let { lang -> + viewLifecycleOwner.lifecycleScope.launch { + renderTos(exchangeBaseUrl!!, lang) + } + } + } else { + manualSelect = true + } + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt @@ -79,5 +79,7 @@ private fun getNodeText(rootNode: Node): String { @Serializable data class TosResponse( val content: String, - val currentEtag: String + val currentEtag: String, + val contentLanguage: String? = null, + val tosAvailableLanguages: List<String> = emptyList(), ) diff --git a/wallet/src/main/res/layout/fragment_review_exchange_tos.xml b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml @@ -21,6 +21,18 @@ android:layout_height="match_parent" tools:context=".withdraw.ReviewExchangeTosFragment"> + <Spinner + android:id="@+id/langSpinner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="10dp" + app:layout_constraintHorizontal_bias="1" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + android:visibility="gone" + tools:visibility="visible"/> + <androidx.recyclerview.widget.RecyclerView android:id="@+id/tosList" android:layout_width="0dp" @@ -29,7 +41,7 @@ app:layout_constraintBottom_toTopOf="@+id/buttonCard" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toBottomOf="@id/langSpinner" android:clipToPadding="false" tools:listitem="@layout/list_item_tos" />