X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fui%2FMainModel.java;h=2355d2a833c2f5192d64887fac87162f39bc9bf2;hb=96c0c980b90119e043ee1d754cfcaa6115912321;hp=44a6f303be75fac7d193b16cee33400af49d5e9b;hpb=5989f5a490a6e212bffc3f50fa2fd6e5a07d4b37;p=mobile-ledger.git diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java index 44a6f303..2355d2a8 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java @@ -1,5 +1,5 @@ /* - * Copyright © 2020 Damyan Ivanov. + * Copyright © 2021 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,119 +17,39 @@ package net.ktnx.mobileledger.ui; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; -import android.os.Build; -import android.text.TextUtils; -import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; -import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.async.RetrieveTransactionsTask; import net.ktnx.mobileledger.async.TransactionAccumulator; import net.ktnx.mobileledger.async.UpdateTransactionsTask; -import net.ktnx.mobileledger.model.AccountListItem; +import net.ktnx.mobileledger.db.Profile; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerAccount; import net.ktnx.mobileledger.model.LedgerTransaction; -import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.model.TransactionListItem; -import net.ktnx.mobileledger.utils.LockHolder; -import net.ktnx.mobileledger.utils.Locker; import net.ktnx.mobileledger.utils.Logger; -import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.SimpleDate; import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Map; - -import static net.ktnx.mobileledger.utils.Logger.debug; public class MainModel extends ViewModel { public final MutableLiveData foundTransactionItemIndex = new MutableLiveData<>(null); private final MutableLiveData updatingFlag = new MutableLiveData<>(false); - private final MutableLiveData accountFilter = new MutableLiveData<>(); + private final MutableLiveData accountFilter = new MutableLiveData<>(null); private final MutableLiveData> displayedTransactions = new MutableLiveData<>(new ArrayList<>()); - private final MutableLiveData> displayedAccounts = - new MutableLiveData<>(); - private final Locker accountsLocker = new Locker(); private final MutableLiveData updateError = new MutableLiveData<>(); - private final Map accountMap = new HashMap<>(); - private MobileLedgerProfile profile; - private List allAccounts = new ArrayList<>(); private SimpleDate firstTransactionDate; private SimpleDate lastTransactionDate; transient private RetrieveTransactionsTask retrieveTransactionsTask; transient private Thread displayedAccountsUpdater; - transient private AccountListLoader loader = null; private TransactionsDisplayedFilter displayedTransactionsUpdater; - public static ArrayList mergeAccountListsFromWeb(List oldList, - List newList) { - LedgerAccount oldAcc, newAcc; - ArrayList merged = new ArrayList<>(); - - Iterator oldIterator = oldList.iterator(); - Iterator newIterator = newList.iterator(); - - while (true) { - if (!oldIterator.hasNext()) { - // the rest of the incoming are new - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - newIterator.forEachRemaining(merged::add); - } - else { - while (newIterator.hasNext()) - merged.add(newIterator.next()); - } - break; - } - oldAcc = oldIterator.next(); - - if (!newIterator.hasNext()) { - // no more incoming accounts. ignore the rest of the old - break; - } - newAcc = newIterator.next(); - - // ignore now missing old items - if (oldAcc.getName() - .compareTo(newAcc.getName()) < 0) - continue; - - // add newly found items - if (oldAcc.getName() - .compareTo(newAcc.getName()) > 0) - { - merged.add(newAcc); - continue; - } - - // two items with same account names; forward-merge UI-controlled fields - // it is important that the result list contains a new LedgerAccount instance - // so that the change is propagated to the UI - newAcc.setExpanded(oldAcc.isExpanded()); - newAcc.setAmountsExpanded(oldAcc.amountsExpanded()); - merged.add(newAcc); - } - - return merged; - } - private void setLastUpdateStamp(long transactionCount) { - debug("db", "Updating transaction value stamp"); - Date now = new Date(); - profile.setLongOption(MLDB.OPT_LAST_SCRAPE, now.getTime()); - Data.lastUpdateDate.postValue(now); - } public void scheduleTransactionListReload() { UpdateTransactionsTask task = new UpdateTransactionsTask(); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this); @@ -140,16 +60,12 @@ public class MainModel extends ViewModel { public LiveData getUpdateError() { return updateError; } - public void setProfile(MobileLedgerProfile profile) { - stopTransactionsRetrieval(); - this.profile = profile; - } public LiveData> getDisplayedTransactions() { return displayedTransactions; } - public void setDisplayedTransactions(List list, int transactionCount) { + public void setDisplayedTransactions(List list) { displayedTransactions.postValue(list); - Data.lastUpdateTransactionCount.postValue(transactionCount); + Data.lastUpdateTransactionCount.postValue(list.size()); } public SimpleDate getFirstTransactionDate() { return firstTransactionDate; @@ -166,29 +82,14 @@ public class MainModel extends ViewModel { public void setLastTransactionDate(SimpleDate latestDate) { this.lastTransactionDate = latestDate; } - private void applyTransactionFilter(List list) { - final String accFilter = accountFilter.getValue(); - ArrayList newList = new ArrayList<>(); - - TransactionAccumulator accumulator = new TransactionAccumulator(this); - if (TextUtils.isEmpty(accFilter)) - for (LedgerTransaction tr : list) - newList.add(new TransactionListItem(tr)); - else - for (LedgerTransaction tr : list) - if (tr.hasAccountNamedLike(accFilter)) - newList.add(new TransactionListItem(tr)); - - displayedTransactions.postValue(newList); - } public synchronized void scheduleTransactionListRetrieval() { if (retrieveTransactionsTask != null) { Logger.debug("db", "Ignoring request for transaction retrieval - already active"); return; } - MobileLedgerProfile profile = Data.getProfile(); + Profile profile = Data.getProfile(); - retrieveTransactionsTask = new RetrieveTransactionsTask(this, profile, allAccounts); + retrieveTransactionsTask = new RetrieveTransactionsTask(this, profile); Logger.debug("db", "Created a background transaction retrieval task"); retrieveTransactionsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -196,58 +97,12 @@ public class MainModel extends ViewModel { public synchronized void stopTransactionsRetrieval() { if (retrieveTransactionsTask != null) retrieveTransactionsTask.cancel(true); + else + Data.backgroundTaskProgress.setValue(null); } public void transactionRetrievalDone() { retrieveTransactionsTask = null; } - public synchronized Locker lockAccountsForWriting() { - accountsLocker.lockForWriting(); - return accountsLocker; - } - public void mergeAccountListFromWeb(List newList) { - - try (LockHolder l = accountsLocker.lockForWriting()) { - allAccounts = mergeAccountListsFromWeb(allAccounts, newList); - updateAccountsMap(allAccounts); - } - } - public LiveData> getDisplayedAccounts() { - return displayedAccounts; - } - synchronized public void scheduleAccountListReload() { - Logger.debug("async-acc", "scheduleAccountListReload() enter"); - if ((loader != null) && loader.isAlive()) { - Logger.debug("async-acc", "returning early - loader already active"); - return; - } - - loader = new AccountListLoader(profile, this); - loader.start(); - } - public synchronized void setAndStoreAccountAndTransactionListFromWeb( - List accounts, List transactions) { - profile.storeAccountAndTransactionListAsync(accounts, transactions); - - setLastUpdateStamp(transactions.size()); - - mergeAccountListFromWeb(accounts); - updateDisplayedAccounts(); - - updateDisplayedTransactionsFromWeb(transactions); - } - synchronized public void abortAccountListReload() { - if (loader == null) - return; - loader.interrupt(); - loader = null; - } - synchronized public void updateDisplayedAccounts() { - if (displayedAccountsUpdater != null) { - displayedAccountsUpdater.interrupt(); - } - displayedAccountsUpdater = new AccountListDisplayedFilter(this, allAccounts); - displayedAccountsUpdater.start(); - } synchronized public void updateDisplayedTransactionsFromWeb(List list) { if (displayedTransactionsUpdater != null) { displayedTransactionsUpdater.interrupt(); @@ -255,133 +110,13 @@ public class MainModel extends ViewModel { displayedTransactionsUpdater = new TransactionsDisplayedFilter(this, list); displayedTransactionsUpdater.start(); } - public List getAllAccounts() { - return allAccounts; - } - private void updateAccountsMap(List newAccounts) { - accountMap.clear(); - for (LedgerAccount acc : newAccounts) { - accountMap.put(acc.getName(), acc); - } - } - @Nullable - public LedgerAccount locateAccount(String name) { - return accountMap.get(name); - } public void clearUpdateError() { updateError.postValue(null); } - public void clearAccounts() { displayedAccounts.postValue(new ArrayList<>()); } public void clearTransactions() { displayedTransactions.setValue(new ArrayList<>()); } - static class AccountListLoader extends Thread { - private final MobileLedgerProfile profile; - private final MainModel model; - AccountListLoader(MobileLedgerProfile profile, MainModel model) { - this.profile = profile; - this.model = model; - } - @Override - public void run() { - Logger.debug("async-acc", "AccountListLoader::run() entered"); - String profileUUID = profile.getUuid(); - ArrayList list = new ArrayList<>(); - HashMap map = new HashMap<>(); - - String sql = "SELECT a.name, a.expanded, a.amounts_expanded"; - sql += " from accounts a WHERE a.profile = ?"; - sql += " ORDER BY a.name"; - - SQLiteDatabase db = App.getDatabase(); - Logger.debug("async-acc", "AccountListLoader::run() connected to DB"); - try (Cursor cursor = db.rawQuery(sql, new String[]{profileUUID})) { - Logger.debug("async-acc", "AccountListLoader::run() executed query"); - while (cursor.moveToNext()) { - if (isInterrupted()) - return; - - final String accName = cursor.getString(0); -// debug("accounts", -// String.format("Read account '%s' from DB [%s]", accName, -// profileUUID)); - String parentName = LedgerAccount.extractParentName(accName); - LedgerAccount parent; - if (parentName != null) { - parent = map.get(parentName); - if (parent == null) - throw new IllegalStateException( - String.format("Can't load account '%s': parent '%s' not loaded", - accName, parentName)); - parent.setHasSubAccounts(true); - } - else - parent = null; - - LedgerAccount acc = new LedgerAccount(profile, accName, parent); - acc.setExpanded(cursor.getInt(1) == 1); - acc.setAmountsExpanded(cursor.getInt(2) == 1); - acc.setHasSubAccounts(false); - - try (Cursor c2 = db.rawQuery( - "SELECT value, currency FROM account_values WHERE profile = ?" + " " + - "AND account = ?", new String[]{profileUUID, accName})) - { - while (c2.moveToNext()) { - acc.addAmount(c2.getFloat(0), c2.getString(1)); - } - } - - list.add(acc); - map.put(accName, acc); - } - Logger.debug("async-acc", "AccountListLoader::run() query execution done"); - } - - if (isInterrupted()) - return; - - Logger.debug("async-acc", "AccountListLoader::run() posting new list"); - model.allAccounts = list; - model.updateAccountsMap(list); - model.updateDisplayedAccounts(); - } - } - - static class AccountListDisplayedFilter extends Thread { - private final MainModel model; - private final List list; - AccountListDisplayedFilter(MainModel model, List list) { - this.model = model; - this.list = list; - } - @Override - public void run() { - List newDisplayed = new ArrayList<>(); - Logger.debug("dFilter", "waiting for synchronized block"); - Logger.debug("dFilter", String.format(Locale.US, - "entered synchronized block (about to examine %d accounts)", list.size())); - newDisplayed.add(new AccountListItem()); // header - - int count = 0; - for (LedgerAccount a : list) { - if (isInterrupted()) - return; - - if (a.isVisible()) { - newDisplayed.add(new AccountListItem(a)); - count++; - } - } - if (!isInterrupted()) { - model.displayedAccounts.postValue(newDisplayed); - Data.lastUpdateAccountCount.postValue(count); - } - Logger.debug("dFilter", "left synchronized block"); - } - } - static class TransactionsDisplayedFilter extends Thread { private final MainModel model; private final List list; @@ -392,13 +127,12 @@ public class MainModel extends ViewModel { @Override public void run() { List newDisplayed = new ArrayList<>(); - Logger.debug("dFilter", "waiting for synchronized block"); Logger.debug("dFilter", String.format(Locale.US, "entered synchronized block (about to examine %d transactions)", list.size())); String accNameFilter = model.getAccountFilter() .getValue(); - TransactionAccumulator acc = new TransactionAccumulator(model); + TransactionAccumulator acc = new TransactionAccumulator(accNameFilter); for (LedgerTransaction tr : list) { if (isInterrupted()) { return; @@ -408,10 +142,12 @@ public class MainModel extends ViewModel { acc.put(tr, tr.getDate()); } } - if (!isInterrupted()) { - acc.done(); - } - Logger.debug("dFilter", "left synchronized block"); + + if (isInterrupted()) + return; + + acc.publishResults(model); + Logger.debug("dFilter", "transaction list updated"); } } }