From f920fa7fa12db5d6fd40844ffb8402426d0a2b07 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 16 Apr 2020 15:06:06 -0300 Subject: [wallet] allow transactions to be selected by long tap --- wallet/build.gradle | 4 + .../wallet/transactions/TransactionAdapter.kt | 47 ++++++++++-- .../wallet/transactions/TransactionsFragment.kt | 85 ++++++++++++++++++++-- wallet/src/main/res/drawable/ic_delete.xml | 10 +++ wallet/src/main/res/drawable/ic_select_all.xml | 10 +++ .../src/main/res/layout/list_item_transaction.xml | 3 +- .../src/main/res/menu/transactions_action_mode.xml | 26 +++++++ wallet/src/main/res/values/colors.xml | 3 - wallet/src/main/res/values/strings.xml | 2 + wallet/src/main/res/values/styles.xml | 2 + 10 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 wallet/src/main/res/drawable/ic_delete.xml create mode 100644 wallet/src/main/res/drawable/ic_select_all.xml create mode 100644 wallet/src/main/res/menu/transactions_action_mode.xml (limited to 'wallet') 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() { + lateinit var tracker: SelectionTracker + 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(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() { + override fun getItemDetails(e: MotionEvent): ItemDetails? { + list.findChildViewUnder(e.x, e.y)?.let { view -> + val holder = list.getChildViewHolder(view) + val position = holder.adapterPosition + return object : ItemDetails() { + 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? = 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() { + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + + 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 @@ #283593 #1A237E #AE1010 - - #C62828 - #558B2F 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 @@ Transaction Balance: %s Show JSON + Delete + Select All Reserve Balance Updated 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 @@ @color/colorPrimaryDark @color/colorAccent @android:color/white + + true