diff options
author | Torsten Grote <t@grobox.de> | 2020-04-16 15:06:06 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-04-16 15:06:06 -0300 |
commit | f920fa7fa12db5d6fd40844ffb8402426d0a2b07 (patch) | |
tree | bff6056188d6424c63eba240d67f9875ea866daa /wallet | |
parent | be8c9a25fe8df7d5bf8e55b38103522f90737e92 (diff) | |
download | taler-android-f920fa7fa12db5d6fd40844ffb8402426d0a2b07.tar.gz taler-android-f920fa7fa12db5d6fd40844ffb8402426d0a2b07.tar.bz2 taler-android-f920fa7fa12db5d6fd40844ffb8402426d0a2b07.zip |
[wallet] allow transactions to be selected by long tap
Diffstat (limited to 'wallet')
-rw-r--r-- | wallet/build.gradle | 4 | ||||
-rw-r--r-- | wallet/src/main/java/net/taler/wallet/transactions/TransactionAdapter.kt | 47 | ||||
-rw-r--r-- | wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt | 85 | ||||
-rw-r--r-- | wallet/src/main/res/drawable/ic_delete.xml | 10 | ||||
-rw-r--r-- | wallet/src/main/res/drawable/ic_select_all.xml | 10 | ||||
-rw-r--r-- | wallet/src/main/res/layout/list_item_transaction.xml | 3 | ||||
-rw-r--r-- | wallet/src/main/res/menu/transactions_action_mode.xml | 26 | ||||
-rw-r--r-- | wallet/src/main/res/values/colors.xml | 3 | ||||
-rw-r--r-- | wallet/src/main/res/values/strings.xml | 2 | ||||
-rw-r--r-- | wallet/src/main/res/values/styles.xml | 2 |
10 files changed, 176 insertions, 16 deletions
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"> |