summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Grote <t@grobox.de>2020-04-16 15:06:06 -0300
committerTorsten Grote <t@grobox.de>2020-04-16 15:06:06 -0300
commitf920fa7fa12db5d6fd40844ffb8402426d0a2b07 (patch)
treebff6056188d6424c63eba240d67f9875ea866daa
parentbe8c9a25fe8df7d5bf8e55b38103522f90737e92 (diff)
downloadtaler-android-f920fa7fa12db5d6fd40844ffb8402426d0a2b07.tar.gz
taler-android-f920fa7fa12db5d6fd40844ffb8402426d0a2b07.tar.bz2
taler-android-f920fa7fa12db5d6fd40844ffb8402426d0a2b07.zip
[wallet] allow transactions to be selected by long tap
-rw-r--r--merchant-terminal/src/main/res/drawable/selectable_background.xml5
-rw-r--r--merchant-terminal/src/main/res/values/colors.xml6
-rw-r--r--taler-kotlin-common/src/main/res/drawable/selectable_background.xml21
-rw-r--r--taler-kotlin-common/src/main/res/values-night/colors.xml (renamed from merchant-terminal/src/main/res/values-night/colors.xml)0
-rw-r--r--taler-kotlin-common/src/main/res/values/colors.xml24
-rw-r--r--wallet/build.gradle4
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt47
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt85
-rw-r--r--wallet/src/main/res/drawable/ic_delete.xml10
-rw-r--r--wallet/src/main/res/drawable/ic_select_all.xml10
-rw-r--r--wallet/src/main/res/layout/list_item_transaction.xml3
-rw-r--r--wallet/src/main/res/menu/transactions_action_mode.xml26
-rw-r--r--wallet/src/main/res/values/colors.xml3
-rw-r--r--wallet/src/main/res/values/strings.xml2
-rw-r--r--wallet/src/main/res/values/styles.xml2
15 files changed, 221 insertions, 27 deletions
diff --git a/merchant-terminal/src/main/res/drawable/selectable_background.xml b/merchant-terminal/src/main/res/drawable/selectable_background.xml
deleted file mode 100644
index b82de92..0000000
--- a/merchant-terminal/src/main/res/drawable/selectable_background.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:drawable="@color/selectedBackground" android:state_activated="true" />
- <item android:drawable="@android:color/transparent" />
-</selector> \ No newline at end of file
diff --git a/merchant-terminal/src/main/res/values/colors.xml b/merchant-terminal/src/main/res/values/colors.xml
index bf0c849..950c107 100644
--- a/merchant-terminal/src/main/res/values/colors.xml
+++ b/merchant-terminal/src/main/res/values/colors.xml
@@ -4,11 +4,5 @@
<color name="colorPrimaryDark">#5D4037</color>
<color name="colorAccent">#FFEB3B</color>
- <color name="highlightedBackground">#E4E4E4</color>
- <color name="selectedBackground">#DADADA</color>
<color name="bottomButtons">#9E9D24</color>
-
- <color name="green">#388E3C</color>
- <color name="red">#C62828</color>
-
</resources>
diff --git a/taler-kotlin-common/src/main/res/drawable/selectable_background.xml b/taler-kotlin-common/src/main/res/drawable/selectable_background.xml
new file mode 100644
index 0000000..3c383a8
--- /dev/null
+++ b/taler-kotlin-common/src/main/res/drawable/selectable_background.xml
@@ -0,0 +1,21 @@
+<?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/>
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@color/selectedBackground" android:state_activated="true" />
+ <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/merchant-terminal/src/main/res/values-night/colors.xml b/taler-kotlin-common/src/main/res/values-night/colors.xml
index 10bdbb9..10bdbb9 100644
--- a/merchant-terminal/src/main/res/values-night/colors.xml
+++ b/taler-kotlin-common/src/main/res/values-night/colors.xml
diff --git a/taler-kotlin-common/src/main/res/values/colors.xml b/taler-kotlin-common/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c916442
--- /dev/null
+++ b/taler-kotlin-common/src/main/res/values/colors.xml
@@ -0,0 +1,24 @@
+<?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/>
+ -->
+
+<resources>
+ <color name="highlightedBackground">#E4E4E4</color>
+ <color name="selectedBackground">#DADADA</color>
+
+ <color name="green">#388E3C</color>
+ <color name="red">#C62828</color>
+</resources>
diff --git a/wallet/build.gradle b/wallet/build.gradle
index 0095b27..a872e8c 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -71,6 +71,10 @@ dependencies {
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ // Lists and Selection
+ implementation "androidx.recyclerview:recyclerview:1.1.0"
+ implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc01"
+
// Navigation Library
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
index 809f6a9..a72b8a8 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt
@@ -18,13 +18,17 @@ package net.taler.wallet.transactions
import android.content.Context
import android.view.LayoutInflater
+import android.view.MotionEvent
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.annotation.CallSuper
+import androidx.recyclerview.selection.ItemDetailsLookup
+import androidx.recyclerview.selection.ItemKeyProvider
+import androidx.recyclerview.selection.SelectionTracker
+import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import net.taler.common.exhaustive
@@ -39,6 +43,9 @@ internal class TransactionAdapter(
private var transactions: Transactions = Transactions()
) : Adapter<TransactionViewHolder>() {
+ lateinit var tracker: SelectionTracker<String>
+ val keyProvider = TransactionKeyProvider()
+
init {
setHasStableIds(false)
}
@@ -52,8 +59,8 @@ internal class TransactionAdapter(
override fun getItemCount(): Int = transactions.size
override fun onBindViewHolder(holder: TransactionViewHolder, position: Int) {
- val event = transactions[position]
- holder.bind(event)
+ val transaction = transactions[position]
+ holder.bind(transaction, tracker.isSelected(transaction.eventId))
}
fun update(updatedTransactions: Transactions) {
@@ -61,6 +68,10 @@ internal class TransactionAdapter(
this.notifyDataSetChanged()
}
+ fun selectAll() = transactions.forEach {
+ tracker.select(it.eventId)
+ }
+
internal open inner class TransactionViewHolder(private val v: View) : ViewHolder(v) {
protected val context: Context = v.context
@@ -73,15 +84,15 @@ internal class TransactionAdapter(
private val selectableBackground = v.background
private val amountColor = amount.currentTextColor
- @CallSuper
- open fun bind(transaction: Transaction) {
+ open fun bind(transaction: Transaction, selected: Boolean) {
if (devMode || transaction.detailPageLayout != 0) {
v.background = selectableBackground
- v.setOnClickListener { listener.onEventClicked(transaction) }
+ v.setOnClickListener { listener.onTransactionClicked(transaction) }
} else {
v.background = null
v.setOnClickListener(null)
}
+ v.isActivated = selected
icon.setImageResource(transaction.icon)
title.text = if (transaction.title == null) {
@@ -140,4 +151,28 @@ internal class TransactionAdapter(
}
+ internal inner class TransactionKeyProvider : ItemKeyProvider<String>(SCOPE_MAPPED) {
+ override fun getKey(position: Int) = transactions[position].eventId
+ override fun getPosition(key: String): Int {
+ return transactions.indexOfFirst { it.eventId == key }
+ }
+ }
+
+}
+
+internal class TransactionLookup(
+ private val list: RecyclerView,
+ private val adapter: TransactionAdapter
+) : ItemDetailsLookup<String>() {
+ override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
+ list.findChildViewUnder(e.x, e.y)?.let { view ->
+ val holder = list.getChildViewHolder(view)
+ val position = holder.adapterPosition
+ return object : ItemDetails<String>() {
+ override fun getPosition(): Int = position
+ override fun getSelectionKey(): String = adapter.keyProvider.getKey(position)
+ }
+ }
+ return null
+ }
}
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 0d6e9ce..e7adaf1 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -17,6 +17,7 @@
package net.taler.wallet.transactions
import android.os.Bundle
+import android.view.ActionMode
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@@ -25,10 +26,15 @@ import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
+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.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.*
@@ -38,16 +44,18 @@ import net.taler.wallet.MainViewModel
import net.taler.wallet.R
interface OnEventClickListener {
- fun onEventClicked(event: Transaction)
+ fun onTransactionClicked(transaction: Transaction)
}
-class TransactionsFragment : Fragment(), OnEventClickListener {
+class TransactionsFragment : Fragment(), OnEventClickListener, ActionMode.Callback {
private val model: MainViewModel by activityViewModels()
private val transactionManager by lazy { model.transactionManager }
private val transactionAdapter by lazy { TransactionAdapter(model.devMode.value == true, this) }
private val currency by lazy { transactionManager.selectedCurrency!! }
+ private var tracker: SelectionTracker<String>? = null
+ private var actionMode: ActionMode? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -66,6 +74,32 @@ class TransactionsFragment : Fragment(), OnEventClickListener {
adapter = transactionAdapter
addItemDecoration(DividerItemDecoration(context, VERTICAL))
}
+ val tracker = SelectionTracker.Builder(
+ "transaction-selection-id",
+ list,
+ transactionAdapter.keyProvider,
+ TransactionLookup(list, transactionAdapter),
+ StorageStrategy.createStringStorage()
+ ).withSelectionPredicate(
+ SelectionPredicates.createSelectAnything()
+ ).build()
+ savedInstanceState?.let { tracker.onRestoreInstanceState(it) }
+ transactionAdapter.tracker = tracker
+ this.tracker = tracker
+ tracker.addObserver(object : SelectionTracker.SelectionObserver<String>() {
+ override fun onItemStateChanged(key: String, selected: Boolean) {
+ if (selected && actionMode == null) {
+ actionMode = requireActivity().startActionMode(this@TransactionsFragment)
+ updateActionModeTitle()
+ } else if (actionMode != null) {
+ if (selected || tracker.hasSelection()) {
+ updateActionModeTitle()
+ } else {
+ actionMode!!.finish()
+ }
+ }
+ }
+ })
transactionManager.progress.observe(viewLifecycleOwner, Observer { show ->
progressBar.visibility = if (show) VISIBLE else INVISIBLE
@@ -88,6 +122,11 @@ class TransactionsFragment : Fragment(), OnEventClickListener {
})
}
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ tracker?.onSaveInstanceState(outState)
+ }
+
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.transactions, menu)
}
@@ -98,12 +137,13 @@ class TransactionsFragment : Fragment(), OnEventClickListener {
}
}
- override fun onEventClicked(event: Transaction) {
- if (event.detailPageLayout != 0) {
- transactionManager.selectedEvent = event
+ override fun onTransactionClicked(transaction: Transaction) {
+ if (actionMode != null) return // don't react on clicks while in action mode
+ if (transaction.detailPageLayout != 0) {
+ transactionManager.selectedEvent = transaction
findNavController().navigate(R.id.action_nav_transaction_detail)
} else if (model.devMode.value == true) {
- JsonDialogFragment.new(event.json.toString(2))
+ JsonDialogFragment.new(transaction.json.toString(2))
.show(parentFragmentManager, null)
}
}
@@ -121,4 +161,37 @@ class TransactionsFragment : Fragment(), OnEventClickListener {
}
}
+ override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+ val inflater = mode.menuInflater
+ inflater.inflate(R.menu.transactions_action_mode, menu)
+ return true
+ }
+
+ override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
+ return false // no update needed
+ }
+
+ override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.transaction_delete -> {
+ val s = "Not yet implemented. Pester Florian! ;)"
+ Toast.makeText(requireContext(), s, LENGTH_LONG).show()
+ mode.finish()
+ }
+ R.id.transaction_select_all -> transactionAdapter.selectAll()
+ }
+ return true
+ }
+
+ override fun onDestroyActionMode(mode: ActionMode) {
+ tracker?.clearSelection()
+ actionMode = null
+ }
+
+ private fun updateActionModeTitle() {
+ tracker?.selection?.size()?.toString()?.let { num ->
+ actionMode?.title = num
+ }
+ }
+
}
diff --git a/wallet/src/main/res/drawable/ic_delete.xml b/wallet/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..88caaa1
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
+</vector>
diff --git a/wallet/src/main/res/drawable/ic_select_all.xml b/wallet/src/main/res/drawable/ic_select_all.xml
new file mode 100644
index 0000000..56adb23
--- /dev/null
+++ b/wallet/src/main/res/drawable/ic_select_all.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />
+</vector>
diff --git a/wallet/src/main/res/layout/list_item_transaction.xml b/wallet/src/main/res/layout/list_item_transaction.xml
index a3ac980..2fabe1d 100644
--- a/wallet/src/main/res/layout/list_item_transaction.xml
+++ b/wallet/src/main/res/layout/list_item_transaction.xml
@@ -19,7 +19,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/selectableItemBackground"
+ android:background="@drawable/selectable_background"
+ android:foreground="?attr/selectableItemBackground"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
diff --git a/wallet/src/main/res/menu/transactions_action_mode.xml b/wallet/src/main/res/menu/transactions_action_mode.xml
new file mode 100644
index 0000000..b290b9e
--- /dev/null
+++ b/wallet/src/main/res/menu/transactions_action_mode.xml
@@ -0,0 +1,26 @@
+<?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/>
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/transaction_select_all"
+ android:icon="@drawable/ic_select_all"
+ android:title="@string/transactions_select_all" />
+ <item
+ android:id="@+id/transaction_delete"
+ android:icon="@drawable/ic_delete"
+ android:title="@string/transactions_delete" />
+</menu>
diff --git a/wallet/src/main/res/values/colors.xml b/wallet/src/main/res/values/colors.xml
index a6b1731..6413bb8 100644
--- a/wallet/src/main/res/values/colors.xml
+++ b/wallet/src/main/res/values/colors.xml
@@ -18,7 +18,4 @@
<color name="colorPrimary">#283593</color>
<color name="colorPrimaryDark">#1A237E</color>
<color name="colorAccent">#AE1010</color>
-
- <color name="red">#C62828</color>
- <color name="green">#558B2F</color>
</resources>
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index b4af3b8..4531785 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -49,6 +49,8 @@
<string name="transactions_detail_title">Transaction</string>
<string name="transactions_detail_title_balance">Balance: %s</string>
<string name="transactions_detail_json">Show JSON</string>
+ <string name="transactions_delete">Delete</string>
+ <string name="transactions_select_all">Select All</string>
<!-- Transactions -->
<string name="transaction_reserve_balance_updated">Reserve Balance Updated</string>
diff --git a/wallet/src/main/res/values/styles.xml b/wallet/src/main/res/values/styles.xml
index c8a2c3b..093f43f 100644
--- a/wallet/src/main/res/values/styles.xml
+++ b/wallet/src/main/res/values/styles.xml
@@ -21,6 +21,8 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorOnPrimary">@android:color/white</item>
+
+ <item name="windowActionModeOverlay">true</item>
</style>
<style name="AppTheme.NoActionBar">