From 9ebf60c045fdf01d6f8d1243061e69232c2841ea Mon Sep 17 00:00:00 2001 From: Damyan Ivanov Date: Thu, 11 Mar 2021 17:01:09 +0200 Subject: [PATCH] replace custom autocompletion adapter hooking with specialised adapters ... which use Room --- .../ktnx/mobileledger/dao/TransactionDAO.java | 77 +++++++++++++++++++ .../java/net/ktnx/mobileledger/db/DB.java | 3 + ...sactionDescriptionAutocompleteAdapter.java | 76 ++++++++++++++++++ .../NewTransactionHeaderItemHolder.java | 18 +++-- .../TransactionListFragment.java | 25 +++--- .../net/ktnx/mobileledger/utils/MLDB.java | 74 ------------------ 6 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/net/ktnx/mobileledger/dao/TransactionDAO.java create mode 100644 app/src/main/java/net/ktnx/mobileledger/db/TransactionDescriptionAutocompleteAdapter.java diff --git a/app/src/main/java/net/ktnx/mobileledger/dao/TransactionDAO.java b/app/src/main/java/net/ktnx/mobileledger/dao/TransactionDAO.java new file mode 100644 index 00000000..0bee7713 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/dao/TransactionDAO.java @@ -0,0 +1,77 @@ +/* + * Copyright © 2021 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe 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 terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.dao; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.room.ColumnInfo; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import net.ktnx.mobileledger.db.Transaction; + +import java.util.ArrayList; +import java.util.List; + +@Dao +public abstract class TransactionDAO extends BaseDAO { + static public List unbox(List list) { + ArrayList result = new ArrayList<>(list.size()); + for (DescriptionContainer item : list) { + result.add(item.description); + } + + return result; + } + @Insert + public abstract void insertSync(Transaction item); + + @Update + public abstract void updateSync(Transaction item); + + @Delete + public abstract void deleteSync(Transaction item); + + @Query("SELECT * FROM transactions") + public abstract LiveData> getAll(); + + // not useful for now +// @Transaction +// @Query("SELECT * FROM patterns") +// List getPatternsWithAccounts(); + @Query("SELECT * FROM transactions WHERE profile = :profileUUID AND id = :id") + public abstract LiveData getById(@NonNull String profileUUID, long id); + + @Query("SELECT DISTINCT description, CASE WHEN description_upper LIKE :term||'%%' THEN 1 " + + " WHEN description_upper LIKE '%%:'||:term||'%%' THEN 2 " + + " WHEN description_upper LIKE '%% '||:term||'%%' THEN 3 " + + " ELSE 9 END AS ordering " + "FROM description_history " + + "WHERE description_upper LIKE '%%'||:term||'%%' " + + "ORDER BY ordering, description_upper, rowid ") + public abstract List lookupDescriptionSync(@NonNull String term); + + static public class DescriptionContainer { + @ColumnInfo + public String description; + @ColumnInfo + public int ordering; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/db/DB.java b/app/src/main/java/net/ktnx/mobileledger/db/DB.java index 173d730e..32e8b216 100644 --- a/app/src/main/java/net/ktnx/mobileledger/db/DB.java +++ b/app/src/main/java/net/ktnx/mobileledger/db/DB.java @@ -32,6 +32,7 @@ import net.ktnx.mobileledger.dao.AccountDAO; import net.ktnx.mobileledger.dao.CurrencyDAO; import net.ktnx.mobileledger.dao.TemplateAccountDAO; import net.ktnx.mobileledger.dao.TemplateHeaderDAO; +import net.ktnx.mobileledger.dao.TransactionDAO; import java.io.BufferedReader; import java.io.IOException; @@ -165,4 +166,6 @@ abstract public class DB extends RoomDatabase { public abstract CurrencyDAO getCurrencyDAO(); public abstract AccountDAO getAccountDAO(); + + public abstract TransactionDAO getTransactionDAO(); } diff --git a/app/src/main/java/net/ktnx/mobileledger/db/TransactionDescriptionAutocompleteAdapter.java b/app/src/main/java/net/ktnx/mobileledger/db/TransactionDescriptionAutocompleteAdapter.java new file mode 100644 index 00000000..f5c10caf --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/db/TransactionDescriptionAutocompleteAdapter.java @@ -0,0 +1,76 @@ +/* + * Copyright © 2021 Damyan Ivanov. + * This file is part of MoLe. + * MoLe is free software: you can distribute it and/or modify it + * under the term of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your opinion), any later version. + * + * MoLe 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 terms for details. + * + * You should have received a copy of the GNU General Public License + * along with MoLe. If not, see . + */ + +package net.ktnx.mobileledger.db; + +import android.content.Context; +import android.widget.ArrayAdapter; +import android.widget.Filter; + +import androidx.annotation.NonNull; + +import net.ktnx.mobileledger.dao.TransactionDAO; +import net.ktnx.mobileledger.utils.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class TransactionDescriptionAutocompleteAdapter extends ArrayAdapter { + private final TransactionFilter filter = new TransactionFilter(); + private final TransactionDAO dao = DB.get() + .getTransactionDAO(); + public TransactionDescriptionAutocompleteAdapter(Context context) { + super(context, android.R.layout.simple_dropdown_item_1line, new ArrayList<>()); + } + @NonNull + @Override + public Filter getFilter() { + return filter; + } + class TransactionFilter extends Filter { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + FilterResults results = new FilterResults(); + if (constraint == null) { + results.count = 0; + return results; + } + + Logger.debug("acc", String.format("Looking for account '%s'", constraint)); + final List matches = TransactionDAO.unbox(dao.lookupDescriptionSync( + String.valueOf(constraint) + .toUpperCase())); + results.values = matches; + results.count = matches.size(); + + return results; + } + @Override + @SuppressWarnings("unchecked") + protected void publishResults(CharSequence constraint, FilterResults results) { + if (results.values == null) { + notifyDataSetInvalidated(); + } + else { + setNotifyOnChange(false); + clear(); + addAll((List) results.values); + notifyDataSetChanged(); + } + } + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionHeaderItemHolder.java b/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionHeaderItemHolder.java index 90c4718f..81afa05d 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionHeaderItemHolder.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionHeaderItemHolder.java @@ -24,7 +24,7 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.view.View; import android.widget.EditText; -import android.widget.SimpleCursorAdapter; +import android.widget.ListAdapter; import android.widget.TextView; import androidx.annotation.ColorInt; @@ -32,10 +32,10 @@ import androidx.annotation.NonNull; import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.databinding.NewTransactionHeaderRowBinding; +import net.ktnx.mobileledger.db.TransactionDescriptionAutocompleteAdapter; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.ui.DatePickerFragment; import net.ktnx.mobileledger.utils.Logger; -import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.Misc; import net.ktnx.mobileledger.utils.SimpleDate; @@ -96,8 +96,12 @@ class NewTransactionHeaderItemHolder extends NewTransactionItemViewHolder NewTransactionActivity activity = (NewTransactionActivity) b.getRoot() .getContext(); - MLDB.hookAutocompletionAdapter(activity, b.newTransactionDescription, - MLDB.DESCRIPTION_HISTORY_TABLE, "description", false, activity, mProfile); + b.newTransactionDescription.setAdapter( + new TransactionDescriptionAutocompleteAdapter(activity)); + b.newTransactionDescription.setOnItemClickListener( + (parent, view, position, id) -> activity.descriptionSelected( + parent.getItemAtPosition(position) + .toString())); decimalSeparator = ""; Data.locale.observe(activity, locale -> decimalSeparator = String.valueOf( @@ -299,14 +303,14 @@ class NewTransactionHeaderItemHolder extends NewTransactionItemViewHolder b.newTransactionDate.setText(head.getFormattedDate()); // avoid triggering completion pop-up - SimpleCursorAdapter a = - (SimpleCursorAdapter) b.newTransactionDescription.getAdapter(); + ListAdapter a = b.newTransactionDescription.getAdapter(); try { b.newTransactionDescription.setAdapter(null); b.newTransactionDescription.setText(head.getDescription()); } finally { - b.newTransactionDescription.setAdapter(a); + b.newTransactionDescription.setAdapter( + (TransactionDescriptionAutocompleteAdapter) a); } b.transactionComment.setText(head.getComment()); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java index e6820cf8..037661f1 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java @@ -1,5 +1,5 @@ /* - * Copyright © 2020 Damyan Ivanov. + * Copyright © 2021 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,7 +17,6 @@ package net.ktnx.mobileledger.ui.transaction_list; -import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; @@ -37,6 +36,7 @@ import androidx.recyclerview.widget.RecyclerView; import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.async.TransactionDateFinder; +import net.ktnx.mobileledger.db.AccountAutocompleteAdapter; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.ui.DatePickerFragment; @@ -47,7 +47,6 @@ import net.ktnx.mobileledger.ui.activity.MainActivity; import net.ktnx.mobileledger.utils.Colors; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.Logger; -import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.SimpleDate; import org.jetbrains.annotations.NotNull; @@ -130,12 +129,13 @@ public class TransactionListFragment extends MobileLedgerListFragment vAccountFilter = view.findViewById(R.id.transaction_list_account_name_filter); accNameFilter = view.findViewById(R.id.transaction_filter_account_name); - MLDB.hookAutocompletionAdapter(mainActivity, accNameFilter, "accounts", "name"); + MobileLedgerProfile profile = Data.getProfile(); + accNameFilter.setAdapter(new AccountAutocompleteAdapter(mainActivity, profile)); accNameFilter.setOnItemClickListener((parent, v, position, id) -> { // debug("tmp", "direct onItemClick"); - Cursor c = (Cursor) parent.getItemAtPosition(position); model.getAccountFilter() - .setValue(c.getString(1)); + .setValue(parent.getItemAtPosition(position) + .toString()); Globals.hideSoftKeyboard(mainActivity); }); @@ -144,18 +144,17 @@ public class TransactionListFragment extends MobileLedgerListFragment model.getUpdatingFlag() .observe(getViewLifecycleOwner(), (flag) -> refreshLayout.setRefreshing(flag)); - MobileLedgerProfile profile = Data.getProfile(); model.getDisplayedTransactions() .observe(getViewLifecycleOwner(), list -> modelAdapter.setTransactions(list)); view.findViewById(R.id.clearAccountNameFilter) .setOnClickListener(v -> { - model.getAccountFilter() - .setValue(null); - vAccountFilter.setVisibility(View.GONE); - menuTransactionListFilter.setVisible(true); - Globals.hideSoftKeyboard(mainActivity); - }); + model.getAccountFilter() + .setValue(null); + vAccountFilter.setVisibility(View.GONE); + menuTransactionListFilter.setVisible(true); + Globals.hideSoftKeyboard(mainActivity); + }); model.foundTransactionItemIndex.observe(getViewLifecycleOwner(), pos -> { Logger.debug("go-to-date", String.format(Locale.US, "Found pos %d", pos)); diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java b/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java index 16e46e68..e11044de 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/MLDB.java @@ -17,23 +17,14 @@ package net.ktnx.mobileledger.utils; -import android.annotation.TargetApi; -import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; -import android.os.Build; -import android.widget.AutoCompleteTextView; -import android.widget.FilterQueryProvider; -import android.widget.SimpleCursorAdapter; import androidx.annotation.NonNull; import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.async.DbOpQueue; -import net.ktnx.mobileledger.async.DescriptionSelectedCallback; -import net.ktnx.mobileledger.model.Data; -import net.ktnx.mobileledger.model.MobileLedgerProfile; import org.jetbrains.annotations.NonNls; @@ -134,71 +125,6 @@ public final class MLDB { static public void setLongOption(String name, long value) { setOption(name, String.valueOf(value)); } - @TargetApi(Build.VERSION_CODES.N) - public static void hookAutocompletionAdapter(final Context context, - final AutoCompleteTextView view, - final String table, final String field) { - hookAutocompletionAdapter(context, view, table, field, true, null, null); - } - @TargetApi(Build.VERSION_CODES.N) - public static void hookAutocompletionAdapter(final Context context, - final AutoCompleteTextView view, - final String table, final String field, - final boolean profileSpecific, - final DescriptionSelectedCallback callback, - final MobileLedgerProfile profile) { - String[] from = {field}; - int[] to = {android.R.id.text1}; - SimpleCursorAdapter adapter = - new SimpleCursorAdapter(context, android.R.layout.simple_dropdown_item_1line, null, - from, to, 0); - adapter.setStringConversionColumn(1); - - FilterQueryProvider provider = constraint -> { - if (constraint == null) - return null; - - String str = constraint.toString() - .toUpperCase(); - debug("autocompletion", "Looking for " + str); - - String sql; - String[] params; - if (profileSpecific) { - MobileLedgerProfile p = (profile == null) ? Data.getProfile() : profile; - sql = String.format( - "SELECT rowid as _id, %s, CASE WHEN %s_upper LIKE ?||'%%' THEN 1 " + - "WHEN %s_upper LIKE '%%:'||?||'%%' then 2 " + - "WHEN %s_upper LIKE '%% '||?||'%%' THEN 3 " + "ELSE 9 END " + "FROM %s " + - "WHERE profile=? AND %s_upper LIKE '%%'||?||'%%' " + - "ORDER BY 3, %s_upper, 1;", field, field, field, field, table, field, - field); - params = new String[]{str, str, str, p.getUuid(), str}; - } - else { - sql = String.format( - "SELECT rowid as _id, %s, CASE WHEN %s_upper LIKE ?||'%%' THEN 1 " + - "WHEN %s_upper LIKE '%%:'||?||'%%' THEN 2 " + - "WHEN %s_upper LIKE '%% '||?||'%%' THEN 3 " + "ELSE 9 END " + "FROM %s " + - "WHERE %s_upper LIKE '%%'||?||'%%' " + "ORDER BY 3, %s_upper, 1;", field, - field, field, field, table, field, field); - params = new String[]{str, str, str, str}; - } - debug("autocompletion", sql); - SQLiteDatabase db = App.getDatabase(); - - return db.rawQuery(sql, params); - }; - - adapter.setFilterQueryProvider(provider); - - view.setAdapter(adapter); - - if (callback != null) - view.setOnItemClickListener( - (parent, itemView, position, id) -> callback.descriptionSelected( - String.valueOf(view.getText()))); - } public static void queryInBackground(@NonNull String statement, String[] params, @NonNull final CallbackHelper callbackHelper) { /* All callbacks are called in the new (asynchronous) thread! */ -- 2.39.2