diff options
author | Torsten Grote <t@grobox.de> | 2020-04-30 14:33:08 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-04-30 14:33:08 -0300 |
commit | 056be53a8d51fb6e45167f5d1ec317513b87a906 (patch) | |
tree | cb14e2ca3e10c29cc7ad82fb8499bb375c70dd5a /wallet | |
parent | 0d4d6358ac6db7e0a1e936ecfd70ec4bb3262846 (diff) | |
download | taler-android-056be53a8d51fb6e45167f5d1ec317513b87a906.tar.gz taler-android-056be53a8d51fb6e45167f5d1ec317513b87a906.tar.bz2 taler-android-056be53a8d51fb6e45167f5d1ec317513b87a906.zip |
[wallet] show ToS markdown in expandable sections
Diffstat (limited to 'wallet')
9 files changed, 297 insertions, 20 deletions
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 <http://www.gnu.org/licenses/> + */ + +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<TosAdapter.TosSectionViewHolder>() { + + private val items = ArrayList<TosSection>() + + 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<TosSection>) { + 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 <http://www.gnu.org/licenses/> + */ + +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<TosSection> { + var node: Node? = + markwon.parse(text).firstChild ?: throw ParseException("Invalid markdown", 0) + var lastHeading: String? = null + var section = Document() + val sections = ArrayList<TosSection>() + 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z" /> +</vector> 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" /> +</vector> 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"> - <ScrollView - android:id="@+id/tosScrollView" + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/tosList" android:layout_width="0dp" android:layout_height="0dp" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toTopOf="@+id/buttonCard" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"> - - <TextView - android:id="@+id/tosTextView" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:padding="16dp" - android:visibility="invisible" - tools:text="@tools:sample/lorem/random" - tools:visibility="visible" /> - - </ScrollView> + app:layout_constraintTop_toTopOf="parent" + tools:listitem="@layout/list_item_tos" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="@+id/tosScrollView" + app:layout_constraintBottom_toBottomOf="@+id/tosList" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + <TextView + android:id="@+id/errorView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:gravity="center" + android:textColor="@color/red" + android:textSize="16sp" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/exchange_tos_error" + tools:visibility="visible" /> + <com.google.android.material.card.MaterialCardView android:id="@+id/buttonCard" style="@style/BottomCard" diff --git a/wallet/src/main/res/layout/list_item_tos.xml b/wallet/src/main/res/layout/list_item_tos.xml new file mode 100644 index 0000000..7a584dc --- /dev/null +++ b/wallet/src/main/res/layout/list_item_tos.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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/> + --> + +<androidx.cardview.widget.CardView 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:layout_width="match_parent" + android:layout_height="wrap_content" + app:cardUseCompatPadding="true"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/sectionTitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:paddingStart="8dp" + android:paddingTop="8dp" + android:paddingEnd="56dp" + android:paddingBottom="8dp" + android:textAppearance="@style/TextAppearance.AppCompat.Headline" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="A Terms of Service section title that might be rather long in some cases" /> + + <ImageView + android:id="@+id/expandButton" + android:layout_width="48dp" + android:layout_height="0dp" + android:padding="8dp" + android:scaleType="center" + android:src="@drawable/ic_keyboard_arrow_up" + app:layout_constraintBottom_toBottomOf="@+id/sectionTitle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/sectionTitle" + app:tint="?android:attr/textColorPrimary" + tools:ignore="ContentDescription" /> + + <TextView + android:id="@+id/sectionText" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:padding="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/sectionTitle" + tools:text="@tools:sample/lorem/random" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</androidx.cardview.widget.CardView> 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 <string name="exchange_fee_wire_fee_wire_fee">Wire Fee: %s</string> <string name="exchange_fee_wire_fee_closing_fee">Closing Fee: %s</string> <string name="exchange_tos_accept">Accept Terms of Service</string> + <string name="exchange_tos_error">Error showing Terms of Service: %s</string> <string name="pending_operations_title">Pending Operations</string> <string name="pending_operations_refuse">Refuse Proposal</string> |