From: Damyan Ivanov Date: Fri, 4 Jan 2019 17:56:37 +0000 (+0000) Subject: another major rework, transaction list is fully asynchronous X-Git-Tag: v0.3~137 X-Git-Url: https://git.ktnx.net/?a=commitdiff_plain;h=a1d55154e1c9fb72fcd60de31d6e64e8d046f96d;p=mobile-ledger.git another major rework, transaction list is fully asynchronous --- diff --git a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveAccountsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveAccountsTask.java index 94eaa1bd..a27b1c8c 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/RetrieveAccountsTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/RetrieveAccountsTask.java @@ -24,7 +24,7 @@ import android.util.Log; import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.model.LedgerAccount; -import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment; +import net.ktnx.mobileledger.ui.activity.MainActivity; import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.NetworkUtil; @@ -42,10 +42,10 @@ import java.util.regex.Pattern; public class RetrieveAccountsTask extends android.os.AsyncTask { int error; - WeakReference mContext; + WeakReference mContext; private SharedPreferences pref; - public RetrieveAccountsTask(WeakReference context) { + public RetrieveAccountsTask(WeakReference context) { mContext = context; error = 0; } @@ -82,9 +82,9 @@ public class RetrieveAccountsTask extends android.os.AsyncTask\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?"); + Pattern tr_end_re = Pattern.compile(""); + Pattern descriptions_line_re = Pattern.compile("\\bdescriptionsSuggester\\s*=\\s*new\\b"); + Pattern description_items_re = Pattern.compile("\"value\":\"([^\"]+)\""); private boolean success; - public RetrieveTransactionsTask(WeakReference contextRef) { + public RetrieveTransactionsTask(WeakReference contextRef) { this.contextRef = contextRef; } private static final void L(String msg) { -// Log.d("transaction-parser", msg); + Log.d("transaction-parser", msg); } @Override protected void onProgressUpdate(Progress... values) { super.onProgressUpdate(values); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveProgress(values[0]); } @Override protected void onPreExecute() { super.onPreExecute(); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveStart(); } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveDone(success); } @Override protected void onCancelled() { super.onCancelled(); - TransactionListFragment context = getContext(); + MainActivity context = getContext(); if (context == null) return; context.onRetrieveDone(false); } @@ -95,12 +109,15 @@ public class RetrieveTransactionsTask extends Progress progress = new Progress(); int maxTransactionId = Progress.INDETERMINATE; success = false; + List accountList = new ArrayList<>(); + LedgerAccount lastAccount = null; + Data.backgroundTaskCount.incrementAndGet(); try { HttpURLConnection http = NetworkUtil.prepare_connection(params[0].getBackendPref(), "journal"); http.setAllowUserInteraction(false); publishProgress(progress); - TransactionListFragment ctx = getContext(); + MainActivity ctx = getContext(); if (ctx == null) return null; try (SQLiteDatabase db = MLDB.getWritableDatabase()) { try (InputStream resp = http.getInputStream()) { @@ -109,8 +126,10 @@ public class RetrieveTransactionsTask extends db.beginTransaction(); try { db.execSQL("UPDATE transactions set keep=0"); + db.execSQL("update account_values set keep=0;"); + db.execSQL("update accounts set keep=0;"); - ParserState state = ParserState.EXPECTING_JOURNAL; + ParserState state = ParserState.EXPECTING_ACCOUNT; String line; BufferedReader buf = new BufferedReader(new InputStreamReader(resp, "UTF-8")); @@ -121,17 +140,61 @@ public class RetrieveTransactionsTask extends LedgerTransaction transaction = null; LINES: while ((line = buf.readLine()) != null) { - if (isCancelled()) break; + throwIfCancelled(); Matcher m; - //L(String.format("State is %d", state)); + //L(String.format("State is %d", updating)); switch (state) { - case EXPECTING_JOURNAL: - if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; + case EXPECTING_ACCOUNT: if (line.equals("

General Journal

")) { state = ParserState.EXPECTING_TRANSACTION; L("→ expecting transaction"); + Data.accounts.set(accountList); + continue; + } + m = account_name_re.matcher(line); + if (m.find()) { + String acct_encoded = m.group(1); + String acct_name = URLDecoder.decode(acct_encoded, "UTF-8"); + acct_name = acct_name.replace("\"", ""); + L(String.format("found account: %s", acct_name)); + + addAccount(db, acct_name); + lastAccount = new LedgerAccount(acct_name); + accountList.add(lastAccount); + + state = ParserState.EXPECTING_ACCOUNT_AMOUNT; + L("→ expecting account amount"); + } + break; + + case EXPECTING_ACCOUNT_AMOUNT: + m = account_value_re.matcher(line); + boolean match_found = false; + while (m.find()) { + throwIfCancelled(); + + match_found = true; + String value = m.group(1); + String currency = m.group(2); + if (currency == null) currency = ""; + value = value.replace(',', '.'); + L("curr=" + currency + ", value=" + value); + db.execSQL( + "insert or replace into account_values(account, currency, value, keep) values(?, ?, ?, 1);", + new Object[]{lastAccount.getName(), + currency, + Float.valueOf(value) + }); + lastAccount.addAmount(Float.parseFloat(value), currency); } + + if (match_found) { + state = ParserState.EXPECTING_ACCOUNT; + L("→ expecting account"); + } + break; + case EXPECTING_TRANSACTION: if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; m = transactionStartPattern.matcher(line); @@ -139,7 +202,7 @@ public class RetrieveTransactionsTask extends transactionId = Integer.valueOf(m.group(1)); state = ParserState.EXPECTING_TRANSACTION_DESCRIPTION; L(String.format( - "found transaction %d → expecting " + "description", + "found transaction %d → expecting description", transactionId)); progress.setProgress(++processedTransactionCount); if (maxTransactionId < transactionId) @@ -151,11 +214,12 @@ public class RetrieveTransactionsTask extends } m = endPattern.matcher(line); if (m.find()) { - L("--- transaction list complete ---"); + L("--- transaction value complete ---"); success = true; break LINES; } break; + case EXPECTING_TRANSACTION_DESCRIPTION: if (!line.isEmpty() && (line.charAt(0) == ' ')) continue; m = transactionDescriptionPattern.matcher(line); @@ -174,6 +238,7 @@ public class RetrieveTransactionsTask extends m.group(1), m.group(2))); } break; + case EXPECTING_TRANSACTION_DETAILS: if (line.isEmpty()) { // transaction data collected @@ -205,7 +270,7 @@ public class RetrieveTransactionsTask extends state = ParserState.EXPECTING_TRANSACTION; L(String.format( - "transaction %s saved → expecting " + "transaction", + "transaction %s saved → expecting transaction", transaction.getId())); // sounds like a good idea, but transaction-1 may not be the first one chronologically @@ -235,7 +300,7 @@ public class RetrieveTransactionsTask extends break; default: throw new RuntimeException( - String.format("Unknown parser state %s", state.name())); + String.format("Unknown parser updating %s", state.name())); } } if (!isCancelled()) { @@ -250,9 +315,9 @@ public class RetrieveTransactionsTask extends } if (success && !isCancelled()) { - Log.d("db", "Updating transaction list stamp"); + Log.d("db", "Updating transaction value stamp"); MLDB.set_option_value(MLDB.OPT_TRANSACTION_LIST_STAMP, new Date().getTime()); - ctx.model.reloadTransactions(ctx); + ((TransactionListViewModel)ctx.currentFragment.model).scheduleTransactionListReload(ctx); } } catch (MalformedURLException e) { @@ -267,15 +332,32 @@ public class RetrieveTransactionsTask extends error = R.string.err_net_io_error; e.printStackTrace(); } + finally { + Data.backgroundTaskCount.decrementAndGet(); + } return null; } - TransactionListFragment getContext() { + private MainActivity getContext() { return contextRef.get(); } + private void addAccount(SQLiteDatabase db, String name) { + do { + LedgerAccount acc = new LedgerAccount(name); + db.execSQL("update accounts set level = ?, keep = 1 where name = ?", + new Object[]{acc.getLevel(), name}); + db.execSQL("insert into accounts(name, name_upper, parent_name, level) select ?,?," + + "?,? " + "where (select changes() = 0)", + new Object[]{name, name.toUpperCase(), acc.getParentName(), acc.getLevel()}); + name = acc.getParentName(); + } while (name != null); + } + private void throwIfCancelled() { + if (isCancelled()) throw new OperationCanceledException(null); + } private enum ParserState { - EXPECTING_JOURNAL, EXPECTING_TRANSACTION, EXPECTING_TRANSACTION_DESCRIPTION, - EXPECTING_TRANSACTION_DETAILS + EXPECTING_ACCOUNT, EXPECTING_ACCOUNT_AMOUNT, EXPECTING_JOURNAL, EXPECTING_TRANSACTION, + EXPECTING_TRANSACTION_DESCRIPTION, EXPECTING_TRANSACTION_DETAILS } public static class Params { diff --git a/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java b/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java new file mode 100644 index 00000000..0509f353 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.async; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.AsyncTask; +import android.util.Log; + +import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.model.LedgerTransaction; +import net.ktnx.mobileledger.utils.MLDB; + +import java.util.ArrayList; +import java.util.List; + +public class UpdateTransactionsTask extends AsyncTask> { + protected List doInBackground(String[] filterAccName) { + Data.backgroundTaskCount.incrementAndGet(); + try { + ArrayList newList = new ArrayList<>(); + + boolean hasFilter = (filterAccName != null) && (filterAccName.length > 0) && + (filterAccName[0] != null) && !filterAccName[0].isEmpty(); + + String sql; + String[] params; + + sql = "SELECT id FROM transactions ORDER BY date desc, id desc"; + params = null; + + if (hasFilter) { + sql = "SELECT distinct tr.id from transactions tr JOIN transaction_accounts ta " + + "ON ta.transaction_id=tr.id WHERE ta.account_name LIKE ?||'%' AND ta" + + ".amount <> 0 ORDER BY tr.date desc, tr.id desc"; + params = filterAccName; + } + + Log.d("tmp", sql); + try (SQLiteDatabase db = MLDB.getReadableDatabase()) { + try (Cursor cursor = db.rawQuery(sql, params)) { + while (cursor.moveToNext()) { + if (isCancelled()) return null; + + newList.add(new LedgerTransaction(cursor.getInt(0))); + } + Data.transactions.set(newList); + Log.d("transactions", "transaction value updated"); + } + } + + return newList; + } + finally { + Data.backgroundTaskCount.decrementAndGet(); + } + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/model/Data.java b/app/src/main/java/net/ktnx/mobileledger/model/Data.java new file mode 100644 index 00000000..89d29c4d --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/model/Data.java @@ -0,0 +1,27 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.model; + +import java.util.List; + +public final class Data { + public static TransactionList transactions = new TransactionList(); + public static ObservableValue> accounts = new ObservableValue<>(); + public static ObservableValue> descriptions = new ObservableValue<>(); + public static ObservableAtomicInteger backgroundTaskCount = new ObservableAtomicInteger(0); +} diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java index 40d1f5fe..9db9f299 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java @@ -126,7 +126,7 @@ public class LedgerTransaction { .rawQuery("SELECT 1 from transactions where data_hash = ?", new String[]{dataHash})) { boolean result = c.moveToFirst(); - Log.d("transactions", String.format("Transaction %d (%s) %s", id, dataHash, + Log.d("db", String.format("Transaction %d (%s) %s", id, dataHash, result ? "already present" : "not present")); return result; } diff --git a/app/src/main/java/net/ktnx/mobileledger/model/ObservableAtomicInteger.java b/app/src/main/java/net/ktnx/mobileledger/model/ObservableAtomicInteger.java new file mode 100644 index 00000000..79a2499c --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/model/ObservableAtomicInteger.java @@ -0,0 +1,140 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.model; + +import android.os.Build; +import android.support.annotation.RequiresApi; + +import java.util.Observable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntBinaryOperator; +import java.util.function.IntUnaryOperator; + +public class ObservableAtomicInteger extends Observable { + private AtomicInteger holder; + ObservableAtomicInteger() { + super(); + holder = new AtomicInteger(); + } + ObservableAtomicInteger(int initialValue) { + this(); + holder.set(initialValue); + } + public int get() { + return holder.get(); + } + public void set(int newValue) { + holder.set(newValue); + forceNotify(); + } + private void forceNotify() { + setChanged(); + notifyObservers(); + } +// public void lazySet(int newValue) { +// holder.lazySet(newValue); +// forceNotify(); +// } + public int getAndSet(int newValue) { + int result = holder.getAndSet(newValue); + forceNotify(); + return result; + } + public boolean compareAndSet(int expect, int update) { + boolean result = holder.compareAndSet(expect, update); + if (result) forceNotify(); + return result; + } +// public boolean weakCompareAndSet(int expect, int update) { +// boolean result = holder.weakCompareAndSet(expect, update); +// if (result) forceNotify(); +// return result; +// } + public int getAndIncrement() { + int result = holder.getAndIncrement(); + forceNotify(); + return result; + } + public int getAndDecrement() { + int result = holder.getAndDecrement(); + forceNotify(); + return result; + } + public int getAndAdd(int delta) { + int result = holder.getAndAdd(delta); + forceNotify(); + return result; + } + public int incrementAndGet() { + int result = holder.incrementAndGet(); + forceNotify(); + return result; + } + public int decrementAndGet() { + int result = holder.decrementAndGet(); + forceNotify(); + return result; + } + public int addAndGet(int delta) { + int result = holder.addAndGet(delta); + forceNotify(); + return result; + } + @RequiresApi(Build.VERSION_CODES.N) + public int getAndUpdate(IntUnaryOperator updateFunction) { + int result = holder.getAndUpdate(updateFunction); + forceNotify(); + return result; + } + @RequiresApi(api = Build.VERSION_CODES.N) + public int updateAndGet(IntUnaryOperator updateFunction) { + int result = holder.updateAndGet(updateFunction); + forceNotify(); + return result; + } + @RequiresApi(api = Build.VERSION_CODES.N) + public int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) { + int result = holder.getAndAccumulate(x, accumulatorFunction); + forceNotify(); + return result; + } + @RequiresApi(api = Build.VERSION_CODES.N) + public int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) { + int result = holder.accumulateAndGet(x, accumulatorFunction); + forceNotify(); + return result; + } + public int intValue() { + return holder.intValue(); + } + public long longValue() { + return holder.longValue(); + } + public float floatValue() { + return holder.floatValue(); + } + public double doubleValue() { + return holder.doubleValue(); + } + public byte byteValue() { + return holder.byteValue(); + } + public short shortValue() { + return holder.shortValue(); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/model/ObservableValue.java b/app/src/main/java/net/ktnx/mobileledger/model/ObservableValue.java new file mode 100644 index 00000000..9a4bb27a --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/model/ObservableValue.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.model; + +import java.util.Observable; +import java.util.Observer; + +public class ObservableValue { + private final ObservableValueImpl impl = new ObservableValueImpl<>(); + public ObservableValue() {} + public ObservableValue(T initialValue) { + impl.setValue(initialValue, false); + } + public void set(T newValue) { + impl.setValue(newValue); + } + public T get() { + return impl.getValue(); + } + public void addObserver(Observer o) { + impl.addObserver(o); + } + public void deleteObserver(Observer o) { + impl.deleteObserver(o); + } + public void notifyObservers() { + impl.notifyObservers(); + } + public void notifyObservers(Object arg) { + impl.notifyObservers(arg); + } + public void deleteObservers() { + impl.deleteObservers(); + } + public boolean hasChanged() { + return impl.hasChanged(); + } + public int countObservers() { + return impl.countObservers(); + } + private class ObservableValueImpl extends Observable { + protected T value; + public void setValue(T newValue) { + setValue(newValue, true); + } + private synchronized void setValue(T newValue, boolean notify) { + if (newValue.equals(value)) return; + + T oldValue = value; + value = newValue; + setChanged(); + if (notify) notifyObservers(oldValue); + } + public T getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/ktnx/mobileledger/model/TransactionList.java b/app/src/main/java/net/ktnx/mobileledger/model/TransactionList.java new file mode 100644 index 00000000..966f3cc2 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/model/TransactionList.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.model; + +import java.util.List; + +public class TransactionList extends ObservableValue> { +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/MobileLedgerListFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/MobileLedgerListFragment.java new file mode 100644 index 00000000..fd21833b --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/MobileLedgerListFragment.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.ui; + +import android.arch.lifecycle.ViewModel; +import android.support.v4.app.Fragment; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; + +import net.ktnx.mobileledger.ui.activity.MainActivity; +import net.ktnx.mobileledger.ui.transaction_list.TransactionListAdapter; + +public class MobileLedgerListFragment extends Fragment { + public ViewModel model; + protected MainActivity mActivity; + public SwipeRefreshLayout swiper; + protected RecyclerView root; + public TransactionListAdapter modelAdapter; +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java index 99cf91fe..d1241601 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java @@ -25,9 +25,6 @@ import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -41,6 +38,7 @@ import android.view.ViewGroup; import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.async.RetrieveAccountsTask; import net.ktnx.mobileledger.model.LedgerAccount; +import net.ktnx.mobileledger.ui.MobileLedgerListFragment; import net.ktnx.mobileledger.ui.RecyclerItemListener; import net.ktnx.mobileledger.ui.activity.MainActivity; import net.ktnx.mobileledger.utils.MLDB; @@ -51,7 +49,7 @@ import java.util.List; import static net.ktnx.mobileledger.ui.activity.SettingsActivity.PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS; -public class AccountSummaryFragment extends Fragment { +public class AccountSummaryFragment extends MobileLedgerListFragment { private static long account_list_last_updated; private static boolean account_list_needs_update = true; @@ -60,9 +58,7 @@ public class AccountSummaryFragment extends Fragment { private AccountSummaryViewModel model; private AccountSummaryAdapter modelAdapter; private Menu optMenu; - private MainActivity mActivity; private FloatingActionButton fab; - private SwipeRefreshLayout swiper; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -101,7 +97,7 @@ public class AccountSummaryFragment extends Fragment { new RecyclerItemListener.RecyclerTouchListener() { @Override public void onClickItem(View v, int position) { - Log.d("list", String.format("item %d clicked", position)); + Log.d("value", String.format("item %d clicked", position)); if (modelAdapter.isSelectionActive()) { modelAdapter.selectItem(position); } @@ -117,7 +113,7 @@ public class AccountSummaryFragment extends Fragment { @Override public void onLongClickItem(View v, int position) { - Log.d("list", String.format("item %d long-clicked", position)); + Log.d("value", String.format("item %d long-clicked", position)); modelAdapter.startSelection(); if (optMenu != null) { optMenu.findItem(R.id.menu_acc_summary_cancel_selection) @@ -168,34 +164,18 @@ public class AccountSummaryFragment extends Fragment { } private void update_accounts() { - RetrieveAccountsTask task = new RetrieveAccountsTask(new WeakReference<>(this)); + RetrieveAccountsTask task = new RetrieveAccountsTask(new WeakReference<>(mActivity)); task.setPref(PreferenceManager.getDefaultSharedPreferences(mActivity)); task.execute(); } - public void onAccountRefreshDone(int error) { - swiper.setRefreshing(false); - if (error != 0) { - String err_text = getResources().getString(error); - Log.d("visual", String.format("showing snackbar: %s", err_text)); - Snackbar.make(swiper, err_text, Snackbar.LENGTH_LONG).show(); - } - else { - MLDB.set_option_value(MLDB.OPT_LAST_REFRESH, new Date().getTime()); - update_account_table(); - } - } private void update_account_table() { if (this.getContext() == null) return; model.reloadAccounts(this.getContext()); modelAdapter.notifyDataSetChanged(); } - public void onRefreshAccountSummaryClicked(MenuItem mi) { - update_accounts(true); - } - public void onShowOnlyStarredClicked(MenuItem mi) { SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mActivity); boolean flag = pref.getBoolean(PREF_KEY_SHOW_ONLY_STARRED_ACCOUNTS, false); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java index b98ccbe0..8d1b2f8b 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java @@ -21,8 +21,8 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.os.Build; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.annotation.ColorInt; -import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.GravityCompat; @@ -35,14 +35,18 @@ import android.view.ContextMenu; import android.view.MenuItem; import android.view.View; import android.widget.LinearLayout; +import android.widget.ProgressBar; import android.widget.TextView; import net.ktnx.mobileledger.R; +import net.ktnx.mobileledger.async.RetrieveTransactionsTask; import net.ktnx.mobileledger.model.LedgerAccount; +import net.ktnx.mobileledger.ui.MobileLedgerListFragment; import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment; import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment; import net.ktnx.mobileledger.utils.MLDB; +import java.lang.ref.WeakReference; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; @@ -51,9 +55,13 @@ public class MainActivity extends AppCompatActivity { DrawerLayout drawer; private AccountSummaryFragment accountSummaryFragment; private TransactionListFragment transactionListFragment; - private Fragment currentFragment = null; + public MobileLedgerListFragment currentFragment = null; private FragmentManager fragmentManager; private TextView tvLastUpdate; + private RetrieveTransactionsTask retrieveTransactionsTask; + private View bTransactionListCancelDownload; + private ProgressBar progressBar; + private LinearLayout progressLayout; @Override protected void onCreate(Bundle savedInstanceState) { @@ -85,6 +93,14 @@ public class MainActivity extends AppCompatActivity { long last_update = MLDB.get_option_value(MLDB.OPT_TRANSACTION_LIST_STAMP, 0L); Log.d("transactions", String.format("Last update = %d", last_update)); + bTransactionListCancelDownload = + findViewById(R.id.transaction_list_cancel_download); + progressBar = findViewById(R.id.transaction_list_progress_bar); + if (progressBar == null) + throw new RuntimeException("Can't get hold on the transaction value progress bar"); + progressLayout = findViewById(R.id.transaction_progress_layout); + if (progressLayout == null) throw new RuntimeException( + "Can't get hold on the transaction value progress bar layout"); fragmentManager = getSupportFragmentManager(); @@ -237,5 +253,47 @@ public class MainActivity extends AppCompatActivity { } } } + public void update_transactions() { + retrieveTransactionsTask = new RetrieveTransactionsTask(new WeakReference<>(this)); + + RetrieveTransactionsTask.Params params = new RetrieveTransactionsTask.Params( + PreferenceManager.getDefaultSharedPreferences(this)); + + retrieveTransactionsTask.execute(params); + bTransactionListCancelDownload.setEnabled(true); + } + public void onStopTransactionRefreshClick(View view) { + Log.d("interactive", "Cancelling transactions refresh"); + if (retrieveTransactionsTask != null) retrieveTransactionsTask.cancel(false); + bTransactionListCancelDownload.setEnabled(false); + } + public void onRetrieveDone(boolean success) { + progressLayout.setVisibility(View.GONE); + updateLastUpdateText(); + } + public void onRetrieveStart() { + progressBar.setIndeterminate(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progressBar.setProgress(0, false); + else progressBar.setProgress(0); + progressLayout.setVisibility(View.VISIBLE); + } + public void onRetrieveProgress(RetrieveTransactionsTask.Progress progress) { + if ((progress.getTotal() == RetrieveTransactionsTask.Progress.INDETERMINATE) || + (progress.getTotal() == 0)) + { + progressBar.setIndeterminate(true); + } + else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + progressBar.setMin(0); + } + progressBar.setMax(progress.getTotal()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + progressBar.setProgress(progress.getProgress(), true); + } + else progressBar.setProgress(progress.getProgress()); + progressBar.setIndeterminate(false); + } + } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java index 5b1e46f4..807c04f1 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java @@ -20,8 +20,8 @@ package net.ktnx.mobileledger.ui.transaction_list; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.graphics.Typeface; +import android.os.AsyncTask; import android.support.annotation.NonNull; -import android.support.constraint.ConstraintLayout; import android.support.v7.widget.AppCompatTextView; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -40,92 +40,20 @@ import net.ktnx.mobileledger.utils.MLDB; import static net.ktnx.mobileledger.utils.DimensionUtils.dp2px; -public class TransactionListAdapter - extends RecyclerView.Adapter { - TransactionListViewModel model; +public class TransactionListAdapter extends RecyclerView.Adapter { private String boldAccountName; - public TransactionListAdapter(TransactionListViewModel model) { - this.model = model; - } public void onBindViewHolder(@NonNull TransactionRowHolder holder, int position) { - LedgerTransaction tr = model.getTransaction(position); - // in a race when transaction list is reduced, but the model hasn't been notified yet + LedgerTransaction tr = TransactionListViewModel.getTransaction(position); + // in a race when transaction value is reduced, but the model hasn't been notified yet // the view will disappear when the notifications reaches the model, so by simply omitting // the out-of-range get() call nothing bad happens - just a to-be-deleted view remains // a bit longer if (tr == null) return; - Context ctx = holder.row.getContext(); - - try (SQLiteDatabase db = MLDB.getReadableDatabase()) { - tr.loadData(db); - holder.tvDescription.setText(tr.getDescription()); - holder.tvDate.setText(tr.getDate()); - - int rowIndex = 0; - for (LedgerTransactionAccount acc : tr.getAccounts()) { - LinearLayout row = (LinearLayout) holder.tableAccounts.getChildAt(rowIndex++); - TextView accName, accAmount; - if (row == null) { - row = new LinearLayout(ctx); - row.setLayoutParams( - new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - row.setGravity(Gravity.CENTER_VERTICAL); - row.setOrientation(LinearLayout.HORIZONTAL); - row.setPaddingRelative(dp2px(ctx, 8), 0, 0, 0); - accName = new AppCompatTextView(ctx); - accName.setLayoutParams( - new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, - 5f)); - accName.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); - row.addView(accName); - accAmount = new AppCompatTextView(ctx); - LinearLayout.LayoutParams llp = - new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - llp.setMarginEnd(0); - accAmount.setLayoutParams(llp); - accAmount.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); - accAmount.setMinWidth(dp2px(ctx, 60)); - row.addView(accAmount); - holder.tableAccounts.addView(row); - } - else { - accName = (TextView) row.getChildAt(0); - accAmount = (TextView) row.getChildAt(1); - } - accName.setText(acc.getAccountName()); - accAmount.setText(acc.toString()); - - if ((boldAccountName != null) && boldAccountName.equals(acc.getAccountName())) { - accName.setTypeface(null, Typeface.BOLD); - accAmount.setTypeface(null, Typeface.BOLD); - accName.setTextColor(Globals.primaryDark); - accAmount.setTextColor(Globals.primaryDark); - } - else { - accName.setTypeface(null, Typeface.NORMAL); - accAmount.setTypeface(null, Typeface.NORMAL); - accName.setTextColor(Globals.defaultTextColor); - accAmount.setTextColor(Globals.defaultTextColor); - } - - } - if (holder.tableAccounts.getChildCount() > rowIndex) { - holder.tableAccounts - .removeViews(rowIndex, holder.tableAccounts.getChildCount() - rowIndex); - } - - if (position % 2 == 0) { - holder.row.setBackgroundColor(Globals.table_row_even_bg); - } - else { - holder.row.setBackgroundColor(Globals.table_row_odd_bg); - } + Log.d("transactions", String.format("Filling position %d", position)); - Log.d("transactions", String.format("Filled position %d", position)); - } + TransactionLoader loader = new TransactionLoader(); + loader.execute(new TransactionLoaderParams(tr, holder, position, boldAccountName)); } @NonNull @@ -139,7 +67,7 @@ public class TransactionListAdapter @Override public int getItemCount() { - return model.getTransactionCount(); + return TransactionListViewModel.getTransactionCount(); } public void setBoldAccountName(String boldAccountName) { this.boldAccountName = boldAccountName; @@ -147,16 +75,126 @@ public class TransactionListAdapter public void resetBoldAccountName() { this.boldAccountName = null; } - class TransactionRowHolder extends RecyclerView.ViewHolder { - TextView tvDescription, tvDate; - LinearLayout tableAccounts; - ConstraintLayout row; - public TransactionRowHolder(@NonNull View itemView) { - super(itemView); - this.row = itemView.findViewById(R.id.transaction_row); - this.tvDescription = itemView.findViewById(R.id.transaction_row_description); - this.tvDate = itemView.findViewById(R.id.transaction_row_date); - this.tableAccounts = itemView.findViewById(R.id.transaction_row_acc_amounts); + + enum LoaderStep {HEAD, ACCOUNTS, DONE} + + private static class TransactionLoader + extends AsyncTask { + @Override + protected Void doInBackground(TransactionLoaderParams... p) { + LedgerTransaction tr = p[0].transaction; + + try (SQLiteDatabase db = MLDB.getReadableDatabase()) { + tr.loadData(db); + + publishProgress(new TransactionLoaderStep(p[0].holder, p[0].position, tr)); + + int rowIndex = 0; + for (LedgerTransactionAccount acc : tr.getAccounts()) { + publishProgress(new TransactionLoaderStep(p[0].holder, acc, rowIndex++, + p[0].boldAccountName)); + } + + publishProgress(new TransactionLoaderStep(p[0].holder, p[0].position, rowIndex)); + } + return null; + } + @Override + protected void onProgressUpdate(TransactionLoaderStep... values) { + super.onProgressUpdate(values); + TransactionLoaderStep step = values[0]; + TransactionRowHolder holder = step.getHolder(); + + switch (step.getStep()) { + case HEAD: + holder.tvDescription.setText(step.getTransaction().getDescription()); + holder.tvDate.setText(step.getTransaction().getDate()); + + if (step.getPosition() % 2 == 0) { + holder.row.setBackgroundColor(Globals.table_row_even_bg); + } + else { + holder.row.setBackgroundColor(Globals.table_row_odd_bg); + } + + break; + case ACCOUNTS: + int rowIndex = step.getAccountPosition(); + Context ctx = holder.row.getContext(); + LinearLayout row = (LinearLayout) holder.tableAccounts.getChildAt(rowIndex); + TextView accName, accAmount; + if (row == null) { + row = new LinearLayout(ctx); + row.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + row.setGravity(Gravity.CENTER_VERTICAL); + row.setOrientation(LinearLayout.HORIZONTAL); + row.setPaddingRelative(dp2px(ctx, 8), 0, 0, 0); + accName = new AppCompatTextView(ctx); + accName.setLayoutParams(new LinearLayout.LayoutParams(0, + LinearLayout.LayoutParams.WRAP_CONTENT, 5f)); + accName.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START); + row.addView(accName); + accAmount = new AppCompatTextView(ctx); + LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + llp.setMarginEnd(0); + accAmount.setLayoutParams(llp); + accAmount.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END); + accAmount.setMinWidth(dp2px(ctx, 60)); + row.addView(accAmount); + holder.tableAccounts.addView(row); + } + else { + accName = (TextView) row.getChildAt(0); + accAmount = (TextView) row.getChildAt(1); + } + LedgerTransactionAccount acc = step.getAccount(); + + accName.setText(acc.getAccountName()); + accAmount.setText(acc.toString()); + + String boldAccountName = step.getBoldAccountName(); + if ((boldAccountName != null) && boldAccountName.equals(acc.getAccountName())) { + accName.setTypeface(null, Typeface.BOLD); + accAmount.setTypeface(null, Typeface.BOLD); + accName.setTextColor(Globals.primaryDark); + accAmount.setTextColor(Globals.primaryDark); + } + else { + accName.setTypeface(null, Typeface.NORMAL); + accAmount.setTypeface(null, Typeface.NORMAL); + accName.setTextColor(Globals.defaultTextColor); + accAmount.setTextColor(Globals.defaultTextColor); + } + + break; + case DONE: + int accCount = step.getAccountCount(); + if (holder.tableAccounts.getChildCount() > accCount) { + holder.tableAccounts.removeViews(accCount, + holder.tableAccounts.getChildCount() - accCount); + } + + Log.d("transactions", + String.format("Position %d fill done", step.getPosition())); + } + } + } + + private class TransactionLoaderParams { + LedgerTransaction transaction; + TransactionRowHolder holder; + int position; + String boldAccountName; + TransactionLoaderParams(LedgerTransaction transaction, TransactionRowHolder holder, + int position, String boldAccountName) { + this.transaction = transaction; + this.holder = holder; + this.position = position; + this.boldAccountName = boldAccountName; } } } \ No newline at end of file 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 f8738a59..c84da792 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 @@ -20,14 +20,10 @@ package net.ktnx.mobileledger.ui.transaction_list; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.database.MatrixCursor; -import android.os.Build; import android.os.Bundle; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; -import android.support.v4.app.Fragment; -import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -40,34 +36,27 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AutoCompleteTextView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; import net.ktnx.mobileledger.R; -import net.ktnx.mobileledger.async.RetrieveTransactionsTask; +import net.ktnx.mobileledger.model.Data; +import net.ktnx.mobileledger.ui.MobileLedgerListFragment; import net.ktnx.mobileledger.ui.activity.MainActivity; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.MLDB; -import java.lang.ref.WeakReference; +import java.util.Observable; +import java.util.Observer; import static android.content.Context.INPUT_METHOD_SERVICE; -public class TransactionListFragment extends Fragment { +public class TransactionListFragment extends MobileLedgerListFragment { public static final String BUNDLE_KEY_FILTER_ACCOUNT_NAME = "filter_account_name"; - public TransactionListViewModel model; private String mShowOnlyAccountName; - private MainActivity mActivity; - private View bTransactionListCancelDownload; private MenuItem menuTransactionListFilter; private View vAccountFilter; - private SwipeRefreshLayout swiper; - private RecyclerView root; - private ProgressBar progressBar; - private LinearLayout progressLayout; - private TransactionListAdapter modelAdapter; - private RetrieveTransactionsTask retrieveTransactionsTask; private AutoCompleteTextView accNameFilter; + private static void update(Observable o, Object arg) { + } public void setShowOnlyAccountName(String mShowOnlyAccountName) { this.mShowOnlyAccountName = mShowOnlyAccountName; if (modelAdapter != null) { @@ -114,15 +103,10 @@ public class TransactionListFragment extends Fragment { swiper = mActivity.findViewById(R.id.transaction_swipe); if (swiper == null) throw new RuntimeException("Can't get hold on the swipe layout"); root = mActivity.findViewById(R.id.transaction_root); - if (root == null) throw new RuntimeException("Can't get hold on the transaction list view"); - progressBar = mActivity.findViewById(R.id.transaction_list_progress_bar); - if (progressBar == null) - throw new RuntimeException("Can't get hold on the transaction list progress bar"); - progressLayout = mActivity.findViewById(R.id.transaction_progress_layout); - if (progressLayout == null) throw new RuntimeException( - "Can't get hold on the transaction list progress bar layout"); + if (root == null) + throw new RuntimeException("Can't get hold on the transaction value view"); model = ViewModelProviders.of(this).get(TransactionListViewModel.class); - modelAdapter = new TransactionListAdapter(model); + modelAdapter = new TransactionListAdapter(); modelAdapter.setBoldAccountName(mShowOnlyAccountName); @@ -149,15 +133,13 @@ public class TransactionListFragment extends Fragment { swiper.setOnRefreshListener(() -> { Log.d("ui", "refreshing transactions via swipe"); - update_transactions(); + mActivity.update_transactions(); }); swiper.setColorSchemeResources(R.color.colorPrimary, R.color.colorAccent); vAccountFilter = mActivity.findViewById(R.id.transaction_list_account_name_filter); accNameFilter = mActivity.findViewById(R.id.transaction_filter_account_name); - bTransactionListCancelDownload = - mActivity.findViewById(R.id.transaction_list_cancel_download); TransactionListFragment me = this; MLDB.hook_autocompletion_adapter(mActivity, accNameFilter, "accounts", "name"); @@ -165,7 +147,7 @@ public class TransactionListFragment extends Fragment { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Log.d("tmp", "direct onItemClick"); - model.reloadTransactions(me); + ((TransactionListViewModel) model).scheduleTransactionListReload(mActivity); MatrixCursor mc = (MatrixCursor) parent.getItemAtPosition(position); modelAdapter.setBoldAccountName(mc.getString(1)); modelAdapter.notifyDataSetChanged(); @@ -179,7 +161,25 @@ public class TransactionListFragment extends Fragment { Log.d("flow", String.format("Account filter set to '%s'", mShowOnlyAccountName)); } - model.reloadTransactions(this); + TransactionListViewModel.scheduleTransactionListReload(mActivity); + TransactionListViewModel.updating.addObserver(new Observer() { + @Override + public void update(Observable o, Object arg) { + swiper.setRefreshing(TransactionListViewModel.updating.get()); + } + }); + + Data.transactions.addObserver(new Observer() { + @Override + public void update(Observable o, Object arg) { + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + modelAdapter.notifyDataSetChanged(); + } + }); + } + }); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -194,57 +194,14 @@ public class TransactionListFragment extends Fragment { super.onCreateOptionsMenu(menu, inflater); } - private void update_transactions() { - retrieveTransactionsTask = new RetrieveTransactionsTask(new WeakReference<>(this)); - - RetrieveTransactionsTask.Params params = new RetrieveTransactionsTask.Params( - PreferenceManager.getDefaultSharedPreferences(mActivity)); - - retrieveTransactionsTask.execute(params); - bTransactionListCancelDownload.setEnabled(true); - } - public void onRetrieveStart() { - progressBar.setIndeterminate(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) progressBar.setProgress(0, false); - else progressBar.setProgress(0); - progressLayout.setVisibility(View.VISIBLE); - } - public void onRetrieveProgress(RetrieveTransactionsTask.Progress progress) { - if ((progress.getTotal() == RetrieveTransactionsTask.Progress.INDETERMINATE) || - (progress.getTotal() == 0)) - { - progressBar.setIndeterminate(true); - } - else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - progressBar.setMin(0); - } - progressBar.setMax(progress.getTotal()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - progressBar.setProgress(progress.getProgress(), true); - } - else progressBar.setProgress(progress.getProgress()); - progressBar.setIndeterminate(false); - } - } - public void onRetrieveDone(boolean success) { - progressLayout.setVisibility(View.GONE); - swiper.setRefreshing(false); - mActivity.updateLastUpdateText(); - if (success) { - Log.d("transactions", "calling notifyDataSetChanged()"); - modelAdapter.notifyDataSetChanged(); - } - } public void onClearAccountNameClick(View view) { vAccountFilter.setVisibility(View.GONE); if (menuTransactionListFilter != null) menuTransactionListFilter.setVisible(true); accNameFilter.setText(null); mShowOnlyAccountName = null; - model.reloadTransactions(this); modelAdapter.resetBoldAccountName(); - modelAdapter.notifyDataSetChanged(); + TransactionListViewModel.scheduleTransactionListReload(mActivity); Globals.hideSoftKeyboard(mActivity); } public void onShowFilterClick(MenuItem menuItem) { @@ -257,9 +214,4 @@ public class TransactionListFragment extends Fragment { imm.showSoftInput(accNameFilter, 0); } } - public void onStopTransactionRefreshClick(View view) { - Log.d("interactive", "Cancelling transactions refresh"); - if (retrieveTransactionsTask != null) retrieveTransactionsTask.cancel(false); - bTransactionListCancelDownload.setEnabled(false); - } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java index 52fb1976..f1c6df01 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java @@ -19,68 +19,58 @@ package net.ktnx.mobileledger.ui.transaction_list; import android.app.Activity; import android.arch.lifecycle.ViewModel; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; +import android.os.AsyncTask; import android.view.View; import android.widget.AutoCompleteTextView; import net.ktnx.mobileledger.R; +import net.ktnx.mobileledger.async.UpdateTransactionsTask; +import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerTransaction; -import net.ktnx.mobileledger.utils.MLDB; +import net.ktnx.mobileledger.model.ObservableValue; -import java.util.ArrayList; +import java.util.List; public class TransactionListViewModel extends ViewModel { + public static ObservableValue updating = new ObservableValue<>(); - private ArrayList transactions; - - public void reloadTransactions(TransactionListFragment context) { - ArrayList newList = new ArrayList<>(); - - Activity act = context.getActivity(); - + public static void scheduleTransactionListReload(Activity act) { boolean hasFilter = act.findViewById(R.id.transaction_list_account_name_filter).getVisibility() == View.VISIBLE; - - String sql; - String[] params; - - sql = "SELECT id FROM transactions ORDER BY date desc, id desc"; - params = null; - - if (hasFilter) { - String filterAccName = String.valueOf( - ((AutoCompleteTextView) act.findViewById(R.id.transaction_filter_account_name)) - .getText()); - - if (!filterAccName.isEmpty()) { - sql = "SELECT distinct tr.id from transactions tr JOIN transaction_accounts ta " + - "ON ta.transaction_id=tr.id WHERE ta.account_name LIKE ?||'%' AND ta" + - ".amount <> 0 ORDER BY tr.date desc, tr.id desc"; - params = new String[]{filterAccName}; - } - } - - Log.d("tmp", sql); - try (SQLiteDatabase db = MLDB.getReadableDatabase()) { - try (Cursor cursor = db.rawQuery(sql, params)) { - while (cursor.moveToNext()) { - newList.add(new LedgerTransaction(cursor.getInt(0))); - } - transactions = newList; - Log.d("transactions", "transaction list updated"); - } - } - + String accFilter = hasFilter ? String.valueOf( + ((AutoCompleteTextView) act.findViewById(R.id.transaction_filter_account_name)) + .getText()) : null; + updating.set(true); + AsyncTask> task = new UTT(); + task.execute(accFilter); } - public LedgerTransaction getTransaction(int position) { + public static LedgerTransaction getTransaction(int position) { + List transactions = Data.transactions.get(); if (position >= transactions.size()) return null; return transactions.get(position); } - public int getTransactionCount() { + public static int getTransactionCount() { + List transactions = Data.transactions.get(); if (transactions == null) return 0; return transactions.size(); } + private static class UTT extends UpdateTransactionsTask { + @Override + protected void onPostExecute(List list) { + super.onPostExecute(list); + updating.set(false); + if (list != null) Data.transactions.set(list); + } + @Override + protected void onCancelled(List ledgerTransactions) { + super.onCancelled(ledgerTransactions); + updating.set(false); + } + @Override + protected void onCancelled() { + super.onCancelled(); + updating.set(false); + } + } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionLoaderStep.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionLoaderStep.java new file mode 100644 index 00000000..e5243584 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionLoaderStep.java @@ -0,0 +1,77 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.ui.transaction_list; + +import net.ktnx.mobileledger.model.LedgerTransaction; +import net.ktnx.mobileledger.model.LedgerTransactionAccount; + +class TransactionLoaderStep { + private int position; + private int accountCount; + private TransactionListAdapter.LoaderStep step; + private TransactionRowHolder holder; + private LedgerTransaction transaction; + private LedgerTransactionAccount account; + private int accountPosition; + private String boldAccountName; + public TransactionLoaderStep(TransactionRowHolder holder, int position, + LedgerTransaction transaction) { + this.step = TransactionListAdapter.LoaderStep.HEAD; + this.holder = holder; + this.transaction = transaction; + this.position = position; + } + public TransactionLoaderStep(TransactionRowHolder holder, LedgerTransactionAccount account, + int accountPosition, String boldAccountName) { + this.step = TransactionListAdapter.LoaderStep.ACCOUNTS; + this.holder = holder; + this.account = account; + this.accountPosition = accountPosition; + this.boldAccountName = boldAccountName; + } + public TransactionLoaderStep(TransactionRowHolder holder, int position, int accountCount) { + this.step = TransactionListAdapter.LoaderStep.DONE; + this.holder = holder; + this.position = position; + this.accountCount = accountCount; + } + public int getAccountCount() { + return accountCount; + } + public int getPosition() { + return position; + } + public String getBoldAccountName() { + return boldAccountName; + } + public int getAccountPosition() { + return accountPosition; + } + public TransactionRowHolder getHolder() { + return holder; + } + public TransactionListAdapter.LoaderStep getStep() { + return step; + } + public LedgerTransaction getTransaction() { + return transaction; + } + public LedgerTransactionAccount getAccount() { + return account; + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionRowHolder.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionRowHolder.java new file mode 100644 index 00000000..5e975969 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionRowHolder.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2019 Damyan Ivanov. + * This file is part of Mobile-Ledger. + * Mobile-Ledger 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. + * + * Mobile-Ledger 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 Mobile-Ledger. If not, see . + */ + +package net.ktnx.mobileledger.ui.transaction_list; + +import android.support.annotation.NonNull; +import android.support.constraint.ConstraintLayout; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.ktnx.mobileledger.R; + +class TransactionRowHolder extends RecyclerView.ViewHolder { + TextView tvDescription, tvDate; + LinearLayout tableAccounts; + ConstraintLayout row; + public TransactionRowHolder(@NonNull View itemView) { + super(itemView); + this.row = itemView.findViewById(R.id.transaction_row); + this.tvDescription = itemView.findViewById(R.id.transaction_row_description); + this.tvDate = itemView.findViewById(R.id.transaction_row_date); + this.tableAccounts = itemView.findViewById(R.id.transaction_row_acc_amounts); + } +}