From 056be53a8d51fb6e45167f5d1ec317513b87a906 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 30 Apr 2020 14:33:08 -0300 Subject: [wallet] show ToS markdown in expandable sections --- wallet/build.gradle | 8 +- .../wallet/withdraw/ReviewExchangeTosFragment.kt | 29 ++++++- .../java/net/taler/wallet/withdraw/TosAdapter.kt | 90 ++++++++++++++++++++++ .../java/net/taler/wallet/withdraw/TosSection.kt | 65 ++++++++++++++++ .../main/res/drawable/ic_keyboard_arrow_down.xml | 9 +++ .../src/main/res/drawable/ic_keyboard_arrow_up.xml | 9 +++ .../res/layout/fragment_review_exchange_tos.xml | 37 +++++---- wallet/src/main/res/layout/list_item_tos.xml | 69 +++++++++++++++++ wallet/src/main/res/values/strings.xml | 1 + 9 files changed, 297 insertions(+), 20 deletions(-) create mode 100644 wallet/src/main/java/net/taler/wallet/withdraw/TosAdapter.kt create mode 100644 wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt create mode 100644 wallet/src/main/res/drawable/ic_keyboard_arrow_down.xml create mode 100644 wallet/src/main/res/drawable/ic_keyboard_arrow_up.xml create mode 100644 wallet/src/main/res/layout/list_item_tos.xml (limited to 'wallet') diff --git a/wallet/build.gradle b/wallet/build.gradle index a872e8c..28431b3 100644 --- a/wallet/build.gradle +++ b/wallet/build.gradle @@ -67,7 +67,7 @@ dependencies { implementation project(":taler-kotlin-common") implementation 'net.taler:akono:0.1' - implementation 'androidx.preference:preference:1.1.0' + implementation 'androidx.preference:preference:1.1.1' implementation 'com.google.android.material:material:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' @@ -89,6 +89,12 @@ dependencies { // Nicer ProgressBar implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1' + // Markdown rendering + final def markwon_version = '4.3.1' + implementation "io.noties.markwon:core:$markwon_version" + implementation "io.noties.markwon:ext-tables:$markwon_version" + implementation "io.noties.markwon:recycler:$markwon_version" + // JSON parsing and serialization implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.10.2' 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 47b6f14..ffaef5a 100644 --- a/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/withdraw/ReviewExchangeTosFragment.kt @@ -16,7 +16,6 @@ package net.taler.wallet.withdraw - import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -25,16 +24,20 @@ import androidx.fragment.app.Fragment 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.R import net.taler.wallet.MainViewModel +import net.taler.wallet.R +import java.text.ParseException class ReviewExchangeTosFragment : Fragment() { private val model: MainViewModel by activityViewModels() private val withdrawManager by lazy { model.withdrawManager } + private val markwon by lazy { Markwon.builder(requireContext()).build() } + private val adapter by lazy { TosAdapter(markwon) } override fun onCreateView( inflater: LayoutInflater, @@ -53,8 +56,18 @@ class ReviewExchangeTosFragment : Fragment() { withdrawManager.withdrawStatus.observe(viewLifecycleOwner, Observer { when (it) { is WithdrawStatus.TermsOfServiceReviewRequired -> { - tosTextView.text = it.tosText - tosTextView.fadeIn() + val sections = try { + // TODO remove next line once exchange delivers proper markdown + val text = it.tosText.replace("****************", "================") + parseTos(markwon, text) + } catch (e: ParseException) { + onTosError(e.message ?: "Unknown Error") + return@Observer + } + adapter.setSections(sections) + tosList.adapter = adapter + tosList.fadeIn() + acceptTosCheckBox.fadeIn() progressBar.fadeOut() } @@ -68,4 +81,12 @@ 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() + } + } diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TosAdapter.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TosAdapter.kt new file mode 100644 index 0000000..74a798f --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/TosAdapter.kt @@ -0,0 +1,90 @@ +/* + * 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 + */ + +package net.taler.wallet.withdraw + +import android.transition.TransitionManager.beginDelayedTransition +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import io.noties.markwon.Markwon +import net.taler.wallet.R + +class TosAdapter( + private val markwon: Markwon +) : RecyclerView.Adapter() { + + private val items = ArrayList() + + init { + setHasStableIds(true) + } + + override fun getItemCount() = items.size + + override fun getItemId(position: Int): Long { + return items[position].node.hashCode().toLong() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TosSectionViewHolder { + val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_tos, parent, false) + return TosSectionViewHolder(v) + } + + override fun onBindViewHolder(holder: TosSectionViewHolder, position: Int) { + holder.bind(items[position]) + } + + fun setSections(sections: List) { + items.clear() + items.addAll(sections) + notifyDataSetChanged() + } + + inner class TosSectionViewHolder(private val v: View) : RecyclerView.ViewHolder(v) { + private val sectionTitle: TextView = v.findViewById(R.id.sectionTitle) + private val expandButton: ImageView = v.findViewById(R.id.expandButton) + private val sectionText: TextView = v.findViewById(R.id.sectionText) + + fun bind(item: TosSection) { + sectionTitle.text = item.title + showSection(item, item.expanded) + val onClickListener = View.OnClickListener { + if (!item.expanded) beginDelayedTransition(v as ViewGroup) + item.expanded = !item.expanded + showSection(item, item.expanded) + } + sectionTitle.setOnClickListener(onClickListener) + } + + private fun showSection(item: TosSection, show: Boolean) { + if (show) { + expandButton.setImageResource(R.drawable.ic_keyboard_arrow_up) + markwon.setParsedMarkdown(sectionText, markwon.render(item.node)) + sectionText.visibility = VISIBLE + } else { + expandButton.setImageResource(R.drawable.ic_keyboard_arrow_down) + sectionText.visibility = GONE + } + } + } + +} diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt b/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt new file mode 100644 index 0000000..72a9e34 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/withdraw/TosSection.kt @@ -0,0 +1,65 @@ +/* + * This file is part of GNU Taler + * (C) 2020 Taler Systems S.A. + * + * GNU Taler is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3, or (at your option) any later version. + * + * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * GNU Taler; see the file COPYING. If not, see + */ + +package net.taler.wallet.withdraw + +import io.noties.markwon.Markwon +import org.commonmark.node.Document +import org.commonmark.node.Heading +import org.commonmark.node.Node +import org.commonmark.node.Text +import java.text.ParseException + +data class TosSection( + val title: String, + val node: Node, + var expanded: Boolean = false +) + +@Throws(ParseException::class) +internal fun parseTos(markwon: Markwon, text: String): List { + var node: Node? = + markwon.parse(text).firstChild ?: throw ParseException("Invalid markdown", 0) + var lastHeading: String? = null + var section = Document() + val sections = ArrayList() + while (node != null) { + val next: Node? = node.next + if (node is Heading && node.level == 1) { + // if lastHeading exists, close previous section + if (lastHeading != null) { + sections.add(TosSection(lastHeading, section)) + section = Document() + } + // check that this is a plain heading + if (node.firstChild !is Text || node.firstChild.next != null) { + throw ParseException( + "Primary heading includes more than just text", sections.size + ) + } + // start new section + lastHeading = (node.firstChild as Text).literal + } else if (lastHeading == null) { + throw ParseException("Found text before first primary heading", 0) + } else { + section.appendChild(node) + } + node = next + } + check(lastHeading != null) + sections.add(TosSection(lastHeading, section)) + return sections +} diff --git a/wallet/src/main/res/drawable/ic_keyboard_arrow_down.xml b/wallet/src/main/res/drawable/ic_keyboard_arrow_down.xml new file mode 100644 index 0000000..c7ba402 --- /dev/null +++ b/wallet/src/main/res/drawable/ic_keyboard_arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/wallet/src/main/res/drawable/ic_keyboard_arrow_up.xml b/wallet/src/main/res/drawable/ic_keyboard_arrow_up.xml new file mode 100644 index 0000000..328e17c --- /dev/null +++ b/wallet/src/main/res/drawable/ic_keyboard_arrow_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/wallet/src/main/res/layout/fragment_review_exchange_tos.xml b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml index 2587c1a..ec8d996 100644 --- a/wallet/src/main/res/layout/fragment_review_exchange_tos.xml +++ b/wallet/src/main/res/layout/fragment_review_exchange_tos.xml @@ -21,36 +21,43 @@ android:layout_height="match_parent" tools:context=".withdraw.ReviewExchangeTosFragment"> - - - - - + app:layout_constraintTop_toTopOf="parent" + tools:listitem="@layout/list_item_tos" /> + + + + + + + + + + + + + + + + diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml index cc846cd..dd3a810 100644 --- a/wallet/src/main/res/values/strings.xml +++ b/wallet/src/main/res/values/strings.xml @@ -128,6 +128,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card Wire Fee: %s Closing Fee: %s Accept Terms of Service + Error showing Terms of Service: %s Pending Operations Refuse Proposal -- cgit v1.2.3