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;
public class RetrieveAccountsTask extends android.os.AsyncTask<Void, Integer, Void> {
int error;
- WeakReference<AccountSummaryFragment> mContext;
+ WeakReference<MainActivity> mContext;
private SharedPreferences pref;
- public RetrieveAccountsTask(WeakReference<AccountSummaryFragment> context) {
+ public RetrieveAccountsTask(WeakReference<MainActivity> context) {
mContext = context;
error = 0;
}
// %3A is '='
Pattern account_name_re =
Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
- Pattern value_re = Pattern.compile(
+ Pattern account_value_re = Pattern.compile(
"<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
- Pattern tr_re = Pattern.compile("</tr>");
+ Pattern tr_end_re = Pattern.compile("</tr>");
Pattern descriptions_line_re =
Pattern.compile("\\bdescriptionsSuggester\\s*=\\s*new\\b");
Pattern description_items_re =
continue;
}
- Matcher tr_m = tr_re.matcher(line);
+ Matcher tr_m = tr_end_re.matcher(line);
if (tr_m.find()) {
Log.d("account-parser", "<tr> - another account expected");
last_account_name = null;
}
if (last_account_name != null) {
- m = value_re.matcher(line);
+ m = account_value_re.matcher(line);
boolean match_found = false;
while (m.find()) {
throwIfCancelled();
}
@Override
protected void onPostExecute(Void result) {
- AccountSummaryFragment ctx = mContext.get();
+ MainActivity ctx = mContext.get();
if (ctx == null) return;
- ctx.onAccountRefreshDone(this.error);
+ ctx.onRetrieveDone(this.error == 0);
}
}
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
+import android.os.OperationCanceledException;
import android.util.Log;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.model.LedgerAccount;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
-import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment;
+import net.ktnx.mobileledger.ui.activity.MainActivity;
+import net.ktnx.mobileledger.ui.transaction_list.TransactionListViewModel;
import net.ktnx.mobileledger.utils.MLDB;
import net.ktnx.mobileledger.utils.NetworkUtil;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
private static final Pattern transactionDetailsPattern =
Pattern.compile("^\\s+" + "(\\S[\\S\\s]+\\S)\\s\\s+([-+]?\\d[\\d,.]*)(?:\\s+(\\S+)$)?");
private static final Pattern endPattern = Pattern.compile("\\bid=\"addmodal\"");
- protected WeakReference<TransactionListFragment> contextRef;
+ protected WeakReference<MainActivity> contextRef;
protected int error;
+ // %3A is '='
+ Pattern account_name_re = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
+ Pattern account_value_re = Pattern.compile(
+ "<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
+ Pattern tr_end_re = Pattern.compile("</tr>");
+ Pattern descriptions_line_re = Pattern.compile("\\bdescriptionsSuggester\\s*=\\s*new\\b");
+ Pattern description_items_re = Pattern.compile("\"value\":\"([^\"]+)\"");
private boolean success;
- public RetrieveTransactionsTask(WeakReference<TransactionListFragment> contextRef) {
+ public RetrieveTransactionsTask(WeakReference<MainActivity> 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);
}
Progress progress = new Progress();
int maxTransactionId = Progress.INDETERMINATE;
success = false;
+ List<LedgerAccount> 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()) {
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"));
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("<h2>General Journal</h2>")) {
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);
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)
}
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);
m.group(1), m.group(2)));
}
break;
+
case EXPECTING_TRANSACTION_DETAILS:
if (line.isEmpty()) {
// transaction data collected
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
break;
default:
throw new RuntimeException(
- String.format("Unknown parser state %s", state.name()));
+ String.format("Unknown parser updating %s", state.name()));
}
}
if (!isCancelled()) {
}
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) {
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 {
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<String, Void, List<LedgerTransaction>> {
+ protected List<LedgerTransaction> doInBackground(String[] filterAccName) {
+ Data.backgroundTaskCount.incrementAndGet();
+ try {
+ ArrayList<LedgerTransaction> 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+import java.util.List;
+
+public final class Data {
+ public static TransactionList transactions = new TransactionList();
+ public static ObservableValue<List<LedgerAccount>> accounts = new ObservableValue<>();
+ public static ObservableValue<List<String>> descriptions = new ObservableValue<>();
+ public static ObservableAtomicInteger backgroundTaskCount = new ObservableAtomicInteger(0);
+}
.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;
}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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();
+ }
+}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+import java.util.Observable;
+import java.util.Observer;
+
+public class ObservableValue<T> {
+ private final ObservableValueImpl<T> 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<T> 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
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+import java.util.List;
+
+public class TransactionList extends ObservableValue<List<LedgerTransaction>> {
+}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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;
+}
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;
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;
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;
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);
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);
}
@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)
}
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);
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;
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;
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) {
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();
}
}
}
+ 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);
+ }
+ }
}
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;
import static net.ktnx.mobileledger.utils.DimensionUtils.dp2px;
-public class TransactionListAdapter
- extends RecyclerView.Adapter<TransactionListAdapter.TransactionRowHolder> {
- TransactionListViewModel model;
+public class TransactionListAdapter extends RecyclerView.Adapter<TransactionRowHolder> {
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
@Override
public int getItemCount() {
- return model.getTransactionCount();
+ return TransactionListViewModel.getTransactionCount();
}
public void setBoldAccountName(String boldAccountName) {
this.boldAccountName = boldAccountName;
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<TransactionLoaderParams, TransactionLoaderStep, Void> {
+ @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
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;
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) {
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);
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");
@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();
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) {
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) {
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
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<Boolean> updating = new ObservableValue<>();
- private ArrayList<LedgerTransaction> transactions;
-
- public void reloadTransactions(TransactionListFragment context) {
- ArrayList<LedgerTransaction> 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<String, Void, List<LedgerTransaction>> task = new UTT();
+ task.execute(accFilter);
}
- public LedgerTransaction getTransaction(int position) {
+ public static LedgerTransaction getTransaction(int position) {
+ List<LedgerTransaction> transactions = Data.transactions.get();
if (position >= transactions.size()) return null;
return transactions.get(position);
}
- public int getTransactionCount() {
+ public static int getTransactionCount() {
+ List<LedgerTransaction> transactions = Data.transactions.get();
if (transactions == null) return 0;
return transactions.size();
}
+ private static class UTT extends UpdateTransactionsTask {
+ @Override
+ protected void onPostExecute(List<LedgerTransaction> list) {
+ super.onPostExecute(list);
+ updating.set(false);
+ if (list != null) Data.transactions.set(list);
+ }
+ @Override
+ protected void onCancelled(List<LedgerTransaction> ledgerTransactions) {
+ super.onCancelled(ledgerTransactions);
+ updating.set(false);
+ }
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+ updating.set(false);
+ }
+ }
}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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;
+ }
+}
--- /dev/null
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+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);
+ }
+}