From b0869289dc9fa2f983991915e77ba0260e59ed8b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 20 May 2020 11:47:26 -0300 Subject: [wallet] implement transaction search --- .../src/main/java/net/taler/common/AndroidUtils.kt | 2 +- .../wallet/transactions/TransactionManager.kt | 35 ++++++++----- .../wallet/transactions/TransactionsFragment.kt | 57 +++++++++++++++++----- wallet/src/main/res/drawable/ic_search.xml | 10 ++++ wallet/src/main/res/layout/app_bar_main.xml | 1 + wallet/src/main/res/menu/transactions.xml | 6 +++ wallet/src/main/res/values/strings.xml | 2 + 7 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 wallet/src/main/res/drawable/ic_search.xml diff --git a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt index 3ab45ad..fda537b 100644 --- a/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt +++ b/taler-kotlin-common/src/main/java/net/taler/common/AndroidUtils.kt @@ -43,7 +43,7 @@ import androidx.navigation.NavDirections import androidx.navigation.fragment.findNavController fun View.fadeIn(endAction: () -> Unit = {}) { - if (visibility == VISIBLE) return + if (visibility == VISIBLE && alpha == 1f) return alpha = 0f visibility = VISIBLE animate().alpha(1f).withEndAction { diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt index d5cee16..882b29b 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionManager.kt @@ -20,6 +20,7 @@ import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.switchMap import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import kotlinx.coroutines.CoroutineScope @@ -46,26 +47,36 @@ class TransactionManager( var selectedCurrency: String? = null var selectedTransaction: Transaction? = null + val searchQuery = MutableLiveData(null) + private val allTransactions = HashMap>() private val mTransactions = HashMap>() val transactions: LiveData @UiThread - get() { + get() = searchQuery.switchMap { query -> val currency = selectedCurrency check(currency != null) { "Did not select currency before getting transactions" } - return mTransactions.getOrPut(currency) { MutableLiveData() } + loadTransactions(query) + mTransactions[currency]!! // non-null because filled in [loadTransactions] } @UiThread - fun loadTransactions() { + fun loadTransactions(searchQuery: String? = null) { val currency = selectedCurrency ?: return - val liveData = mTransactions.getOrPut(currency) { - MutableLiveData() + val liveData = mTransactions.getOrPut(currency) { MutableLiveData() } + if (searchQuery == null && allTransactions.containsKey(currency)) { + liveData.value = TransactionsResult.Success(allTransactions[currency]!!) } if (liveData.value == null) mProgress.value = true val request = JSONObject(mapOf("currency" to currency)) + searchQuery?.let { request.put("search", it) } walletBackendApi.sendRequest("getTransactions", request) { isError, result -> - scope.launch(Dispatchers.Default) { - onTransactionsLoaded(liveData, isError, result) + if (isError) { + liveData.postValue(TransactionsResult.Error) + } else { + val currencyToUpdate = if (searchQuery == null) currency else null + scope.launch(Dispatchers.Default) { + onTransactionsLoaded(liveData, currencyToUpdate, result) + } } } } @@ -73,13 +84,9 @@ class TransactionManager( @WorkerThread private fun onTransactionsLoaded( liveData: MutableLiveData, - isError: Boolean, + currency: String?, // only non-null if we should update all transactions cache result: JSONObject ) { - if (isError) { - liveData.postValue(TransactionsResult.Error) - return - } val transactionsArray = result.getString("transactions") val transactions: LinkedList = mapper.readValue(transactionsArray) // TODO remove when fixed in wallet-core @@ -87,6 +94,10 @@ class TransactionManager( transactions.reverse() // show latest first mProgress.postValue(false) liveData.postValue(TransactionsResult.Success(transactions)) + // update all transactions on UiThread if there was a currency + currency?.let { + scope.launch(Dispatchers.Main) { allTransactions[currency] = transactions } + } } @UiThread 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 dfd00ea..526aa94 100644 --- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt +++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt @@ -23,11 +23,11 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem 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.appcompat.widget.SearchView +import androidx.appcompat.widget.SearchView.OnQueryTextListener import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer @@ -102,14 +102,11 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. }) transactionManager.progress.observe(viewLifecycleOwner, Observer { show -> - progressBar.visibility = if (show) VISIBLE else INVISIBLE + if (show) progressBar.fadeIn() else progressBar.fadeOut() }) transactionManager.transactions.observe(viewLifecycleOwner, Observer { result -> onTransactionsResult(result) }) - - // kicks off initial load, needs to be adapted if showAll state is ever saved - if (savedInstanceState == null) transactionManager.loadTransactions() } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -129,12 +126,29 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.transactions, menu) + setupSearch(menu.findItem(R.id.action_search)) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - else -> super.onOptionsItemSelected(item) - } + private fun setupSearch(item: MenuItem) { + item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem) = true + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + onSearchClosed() + return true + } + }) + val searchView = item.actionView as SearchView + searchView.setOnQueryTextListener(object : OnQueryTextListener { + override fun onQueryTextChange(newText: String) = false + override fun onQueryTextSubmit(query: String): Boolean { + // workaround to avoid issues with some emulators and keyboard devices + // firing twice if a keyboard enter is used + // see https://code.google.com/p/android/issues/detail?id=24599 + item.actionView.clearFocus() + onSearch(query) + return true + } + }) } override fun onTransactionClicked(transaction: Transaction) { @@ -152,12 +166,29 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode. emptyState.fadeIn() } is TransactionsResult.Success -> { - emptyState.visibility = if (result.transactions.isEmpty()) VISIBLE else INVISIBLE - transactionAdapter.update(result.transactions) - list.fadeIn() + if (result.transactions.isEmpty()) { + val isSearch = transactionManager.searchQuery.value != null + emptyState.setText(if (isSearch) R.string.transactions_empty_search else R.string.transactions_empty) + emptyState.fadeIn() + list.fadeOut() + } else { + emptyState.fadeOut() + transactionAdapter.update(result.transactions) + list.fadeIn() + } } } + private fun onSearch(query: String) { + list.fadeOut() + progressBar.fadeIn() + transactionManager.searchQuery.value = query + } + + private fun onSearchClosed() { + transactionManager.searchQuery.value = null + } + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { val inflater = mode.menuInflater inflater.inflate(R.menu.transactions_action_mode, menu) diff --git a/wallet/src/main/res/drawable/ic_search.xml b/wallet/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..13e67fe --- /dev/null +++ b/wallet/src/main/res/drawable/ic_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/wallet/src/main/res/layout/app_bar_main.xml b/wallet/src/main/res/layout/app_bar_main.xml index 5ee55b2..6937e59 100644 --- a/wallet/src/main/res/layout/app_bar_main.xml +++ b/wallet/src/main/res/layout/app_bar_main.xml @@ -36,6 +36,7 @@ style="@style/AppTheme.Toolbar" android:layout_width="0dp" android:layout_height="wrap_content" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/wallet/src/main/res/menu/transactions.xml b/wallet/src/main/res/menu/transactions.xml index d4568d4..2ea8a23 100644 --- a/wallet/src/main/res/menu/transactions.xml +++ b/wallet/src/main/res/menu/transactions.xml @@ -16,4 +16,10 @@ + diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml index 7d76806..167ab53 100644 --- a/wallet/src/main/res/values/strings.xml +++ b/wallet/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card Go Back Scan Taler QR Code + Search Settings Retry Pending Operations @@ -61,6 +62,7 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card Transactions You don\'t have any transactions + No transactions found. Try a different search. Could not load transactions Transaction Balance: %s -- cgit v1.2.3