X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;ds=sidebyside;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fmodel%2FMobileLedgerProfile.java;h=7214e2b0f8b2680a45723695fcda1453559f9fbe;hb=7ea992dfac30dd23663da581505f9837d168879e;hp=2b587c8ed4ad6d01331577122702e2f8c8ef6eec;hpb=e809f9bacb61890374d1b9c8c46edeaed8a838b0;p=mobile-ledger.git
diff --git a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
index 2b587c8e..7214e2b0 100644
--- a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
+++ b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java
@@ -1,87 +1,170 @@
/*
- * Copyright © 2019 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger is free software: you can distribute it and/or modify it
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your opinion), any later version.
*
- * Mobile-Ledger is distributed in the hope that it will be useful,
+ * MoLe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License terms for details.
*
* You should have received a copy of the GNU General Public License
- * along with Mobile-Ledger. If not, see .
+ * along with MoLe. If not, see .
*/
package net.ktnx.mobileledger.model;
+import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.util.Log;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.SparseArray;
-import net.ktnx.mobileledger.utils.Globals;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import net.ktnx.mobileledger.App;
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.async.DbOpQueue;
+import net.ktnx.mobileledger.async.SendTransactionTask;
+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.Misc;
+
+import org.jetbrains.annotations.Contract;
import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
-import java.util.UUID;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import static net.ktnx.mobileledger.utils.Logger.debug;
public final class MobileLedgerProfile {
- private String uuid;
+ private final MutableLiveData> displayedAccounts;
+ private final MutableLiveData> allTransactions;
+ private final MutableLiveData> displayedTransactions;
+ // N.B. when adding new fields, update the copy-constructor below
+ private final String uuid;
+ private final Locker accountsLocker = new Locker();
+ private List allAccounts;
private String name;
+ private boolean permitPosting;
+ private boolean showCommentsByDefault;
+ private boolean showCommodityByDefault;
+ private String defaultCommodity;
+ private String preferredAccountsFilter;
private String url;
private boolean authEnabled;
private String authUserName;
private String authPassword;
- public MobileLedgerProfile(String uuid, String name, String url, boolean authEnabled,
- String authUserName, String authPassword) {
+ private int themeHue;
+ private int orderNo = -1;
+ private SendTransactionTask.API apiVersion = SendTransactionTask.API.auto;
+ private Calendar firstTransactionDate;
+ private Calendar lastTransactionDate;
+ private FutureDates futureDates = FutureDates.None;
+ private boolean accountsLoaded;
+ private boolean transactionsLoaded;
+ // N.B. when adding new fields, update the copy-constructor below
+ transient private AccountListLoader loader = null;
+ transient private Thread displayedAccountsUpdater;
+ transient private AccountListSaver accountListSaver;
+ transient private TransactionListSaver transactionListSaver;
+ transient private AccountAndTransactionListSaver accountAndTransactionListSaver;
+ private Map accountMap = new HashMap<>();
+ public MobileLedgerProfile(String uuid) {
this.uuid = uuid;
- this.name = name;
- this.url = url;
- this.authEnabled = authEnabled;
- this.authUserName = authUserName;
- this.authPassword = authPassword;
+ allAccounts = new ArrayList<>();
+ displayedAccounts = new MutableLiveData<>();
+ allTransactions = new MutableLiveData<>(new ArrayList<>());
+ displayedTransactions = new MutableLiveData<>(new ArrayList<>());
}
- public MobileLedgerProfile(CharSequence name, CharSequence url, boolean authEnabled,
- CharSequence authUserName, CharSequence authPassword) {
- this.uuid = String.valueOf(UUID.randomUUID());
- this.name = String.valueOf(name);
- this.url = String.valueOf(url);
- this.authEnabled = authEnabled;
- this.authUserName = String.valueOf(authUserName);
- this.authPassword = String.valueOf(authPassword);
+ public MobileLedgerProfile(MobileLedgerProfile origin) {
+ uuid = origin.uuid;
+ name = origin.name;
+ permitPosting = origin.permitPosting;
+ showCommentsByDefault = origin.showCommentsByDefault;
+ showCommodityByDefault = origin.showCommodityByDefault;
+ preferredAccountsFilter = origin.preferredAccountsFilter;
+ url = origin.url;
+ authEnabled = origin.authEnabled;
+ authUserName = origin.authUserName;
+ authPassword = origin.authPassword;
+ themeHue = origin.themeHue;
+ orderNo = origin.orderNo;
+ futureDates = origin.futureDates;
+ apiVersion = origin.apiVersion;
+ defaultCommodity = origin.defaultCommodity;
+ firstTransactionDate = origin.firstTransactionDate;
+ lastTransactionDate = origin.lastTransactionDate;
+ displayedAccounts = origin.displayedAccounts;
+ allAccounts = origin.allAccounts;
+ accountMap = origin.accountMap;
+ displayedTransactions = origin.displayedTransactions;
+ allTransactions = origin.allTransactions;
+ accountsLoaded = origin.accountsLoaded;
+ transactionsLoaded = origin.transactionsLoaded;
}
// loads all profiles into Data.profiles
// returns the profile with the given UUID
- public static MobileLedgerProfile loadAllFromDB(String currentProfileUUID) {
+ public static MobileLedgerProfile loadAllFromDB(@Nullable String currentProfileUUID) {
MobileLedgerProfile result = null;
- List list = new ArrayList<>();
- SQLiteDatabase db = MLDB.getReadableDatabase();
+ ArrayList list = new ArrayList<>();
+ SQLiteDatabase db = App.getDatabase();
try (Cursor cursor = db.rawQuery("SELECT uuid, name, url, use_authentication, auth_user, " +
- "auth_password FROM profiles order by order_no", null))
+ "auth_password, permit_posting, theme, order_no, " +
+ "preferred_accounts_filter, future_dates, api_version, " +
+ "show_commodity_by_default, default_commodity, " +
+ "show_comments_by_default FROM " +
+ "profiles order by order_no", null))
{
while (cursor.moveToNext()) {
- MobileLedgerProfile item =
- new MobileLedgerProfile(cursor.getString(0), cursor.getString(1),
- cursor.getString(2), cursor.getInt(3) == 1, cursor.getString(4),
- cursor.getString(5));
+ MobileLedgerProfile item = new MobileLedgerProfile(cursor.getString(0));
+ item.setName(cursor.getString(1));
+ item.setUrl(cursor.getString(2));
+ item.setAuthEnabled(cursor.getInt(3) == 1);
+ item.setAuthUserName(cursor.getString(4));
+ item.setAuthPassword(cursor.getString(5));
+ item.setPostingPermitted(cursor.getInt(6) == 1);
+ item.setThemeId(cursor.getInt(7));
+ item.orderNo = cursor.getInt(8);
+ item.setPreferredAccountsFilter(cursor.getString(9));
+ item.setFutureDates(cursor.getInt(10));
+ item.setApiVersion(cursor.getInt(11));
+ item.setShowCommodityByDefault(cursor.getInt(12) == 1);
+ item.setDefaultCommodity(cursor.getString(13));
+ item.setShowCommentsByDefault(cursor.getInt(14) == 1);
list.add(item);
- if (item.getUuid().equals(currentProfileUUID)) result = item;
+ if (item.getUuid()
+ .equals(currentProfileUUID))
+ result = item;
}
}
- Data.profiles.setList(list);
+ Data.profiles.setValue(list);
return result;
}
public static void storeProfilesOrder() {
- SQLiteDatabase db = MLDB.getWritableDatabase();
- db.beginTransaction();
+ SQLiteDatabase db = App.getDatabase();
+ db.beginTransactionNonExclusive();
try {
int orderNo = 0;
- for (MobileLedgerProfile p : Data.profiles.getList()) {
+ for (MobileLedgerProfile p : Data.profiles.getValue()) {
db.execSQL("update profiles set order_no=? where uuid=?",
new Object[]{orderNo, p.getUuid()});
+ p.orderNo = orderNo;
orderNo++;
}
db.setTransactionSuccessful();
@@ -90,43 +173,183 @@ public final class MobileLedgerProfile {
db.endTransaction();
}
}
- public static List createInitialProfileList() {
- List result = new ArrayList<>();
- MobileLedgerProfile first =
- new MobileLedgerProfile(UUID.randomUUID().toString(), "default", "", false, "", "");
- first.storeInDB();
- result.add(first);
+ public static ArrayList mergeAccountListsFromWeb(List oldList,
+ List newList) {
+ LedgerAccount oldAcc, newAcc;
+ ArrayList merged = new ArrayList<>();
- return result;
- }
- public static MobileLedgerProfile loadUUIDFromDB(String profileUUID) {
- SQLiteDatabase db = MLDB.getReadableDatabase();
- String name;
- String url;
- String authUser;
- String authPassword;
- Boolean useAuthentication;
- try (Cursor cursor = db.rawQuery("SELECT name, url, use_authentication, auth_user, " +
- "auth_password FROM profiles WHERE uuid=?",
- new String[]{profileUUID}))
- {
- if (cursor.moveToNext()) {
- name = cursor.getString(0);
- url = cursor.getString(1);
- useAuthentication = cursor.getInt(2) == 1;
- authUser = useAuthentication ? cursor.getString(3) : null;
- authPassword = useAuthentication ? cursor.getString(4) : null;
+ 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;
}
- else {
- name = "Unknown profile";
- url = "Https://server/url";
- useAuthentication = false;
- authUser = authPassword = null;
+ 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;
+ }
+ public void mergeAccountListFromWeb(List newList) {
+
+ try (LockHolder l = accountsLocker.lockForWriting()) {
+ allAccounts = mergeAccountListsFromWeb(allAccounts, newList);
+ updateAccountsMap(allAccounts);
+ }
+ }
+ public LiveData> getDisplayedAccounts() {
+ return displayedAccounts;
+ }
+ @Contract(value = "null -> false", pure = true)
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null)
+ return false;
+ if (obj == this)
+ return true;
+ if (obj.getClass() != this.getClass())
+ return false;
+
+ MobileLedgerProfile p = (MobileLedgerProfile) obj;
+ if (!uuid.equals(p.uuid))
+ return false;
+ if (!name.equals(p.name))
+ return false;
+ if (permitPosting != p.permitPosting)
+ return false;
+ if (showCommentsByDefault != p.showCommentsByDefault)
+ return false;
+ if (showCommodityByDefault != p.showCommodityByDefault)
+ return false;
+ if (!Objects.equals(defaultCommodity, p.defaultCommodity))
+ return false;
+ if (!Objects.equals(preferredAccountsFilter, p.preferredAccountsFilter))
+ return false;
+ if (!Objects.equals(url, p.url))
+ return false;
+ if (authEnabled != p.authEnabled)
+ return false;
+ if (!Objects.equals(authUserName, p.authUserName))
+ return false;
+ if (!Objects.equals(authPassword, p.authPassword))
+ return false;
+ if (themeHue != p.themeHue)
+ return false;
+ if (apiVersion != p.apiVersion)
+ return false;
+ if (!Objects.equals(firstTransactionDate, p.firstTransactionDate))
+ return false;
+ if (!Objects.equals(lastTransactionDate, p.lastTransactionDate))
+ return false;
+ return futureDates == p.futureDates;
+ }
+ 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;
}
- return new MobileLedgerProfile(profileUUID, name, url, useAuthentication, authUser,
- authPassword);
+ Logger.debug("async-acc", "Starting AccountListLoader");
+ loader = new AccountListLoader(this);
+ loader.start();
+ }
+ synchronized public void abortAccountListReload() {
+ if (loader == null)
+ return;
+ loader.interrupt();
+ loader = null;
+ }
+ public boolean getShowCommentsByDefault() {
+ return showCommentsByDefault;
+ }
+ public void setShowCommentsByDefault(boolean newValue) {
+ this.showCommentsByDefault = newValue;
+ }
+ public boolean getShowCommodityByDefault() {
+ return showCommodityByDefault;
+ }
+ public void setShowCommodityByDefault(boolean showCommodityByDefault) {
+ this.showCommodityByDefault = showCommodityByDefault;
+ }
+ public String getDefaultCommodity() {
+ return defaultCommodity;
+ }
+ public void setDefaultCommodity(String defaultCommodity) {
+ this.defaultCommodity = defaultCommodity;
+ }
+ public void setDefaultCommodity(CharSequence defaultCommodity) {
+ if (defaultCommodity == null)
+ this.defaultCommodity = null;
+ else
+ this.defaultCommodity = String.valueOf(defaultCommodity);
+ }
+ public SendTransactionTask.API getApiVersion() {
+ return apiVersion;
+ }
+ public void setApiVersion(SendTransactionTask.API apiVersion) {
+ this.apiVersion = apiVersion;
+ }
+ public void setApiVersion(int apiVersion) {
+ this.apiVersion = SendTransactionTask.API.valueOf(apiVersion);
+ }
+ public FutureDates getFutureDates() {
+ return futureDates;
+ }
+ public void setFutureDates(int anInt) {
+ futureDates = FutureDates.valueOf(anInt);
+ }
+ public void setFutureDates(FutureDates futureDates) {
+ this.futureDates = futureDates;
+ }
+ public String getPreferredAccountsFilter() {
+ return preferredAccountsFilter;
+ }
+ public void setPreferredAccountsFilter(String preferredAccountsFilter) {
+ this.preferredAccountsFilter = preferredAccountsFilter;
+ }
+ public void setPreferredAccountsFilter(CharSequence preferredAccountsFilter) {
+ setPreferredAccountsFilter(String.valueOf(preferredAccountsFilter));
+ }
+ public boolean isPostingPermitted() {
+ return permitPosting;
+ }
+ public void setPostingPermitted(boolean permitPosting) {
+ this.permitPosting = permitPosting;
}
public String getUuid() {
return uuid;
@@ -134,21 +357,21 @@ public final class MobileLedgerProfile {
public String getName() {
return name;
}
- public void setName(String name) {
- this.name = name;
- }
public void setName(CharSequence text) {
setName(String.valueOf(text));
}
+ public void setName(String name) {
+ this.name = name;
+ }
public String getUrl() {
return url;
}
- public void setUrl(String url) {
- this.url = url;
- }
public void setUrl(CharSequence text) {
setUrl(String.valueOf(text));
}
+ public void setUrl(String url) {
+ this.url = url;
+ }
public boolean isAuthEnabled() {
return authEnabled;
}
@@ -158,29 +381,39 @@ public final class MobileLedgerProfile {
public String getAuthUserName() {
return authUserName;
}
- public void setAuthUserName(String authUserName) {
- this.authUserName = authUserName;
- }
public void setAuthUserName(CharSequence text) {
setAuthUserName(String.valueOf(text));
}
+ public void setAuthUserName(String authUserName) {
+ this.authUserName = authUserName;
+ }
public String getAuthPassword() {
return authPassword;
}
- public void setAuthPassword(String authPassword) {
- this.authPassword = authPassword;
- }
public void setAuthPassword(CharSequence text) {
setAuthPassword(String.valueOf(text));
}
+ public void setAuthPassword(String authPassword) {
+ this.authPassword = authPassword;
+ }
public void storeInDB() {
- SQLiteDatabase db = MLDB.getWritableDatabase();
- db.beginTransaction();
+ SQLiteDatabase db = App.getDatabase();
+ db.beginTransactionNonExclusive();
try {
- db.execSQL("REPLACE INTO profiles(uuid, name, url, use_authentication, auth_user, " +
- "auth_password) VALUES(?, ?, ?, ?, ?, ?)",
- new Object[]{uuid, name, url, authEnabled, authEnabled ? authUserName : null,
- authEnabled ? authPassword : null
+// debug("profiles", String.format("Storing profile in DB: uuid=%s, name=%s, " +
+// "url=%s, permit_posting=%s, authEnabled=%s, " +
+// "themeHue=%d", uuid, name, url,
+// permitPosting ? "TRUE" : "FALSE", authEnabled ? "TRUE" : "FALSE", themeHue));
+ db.execSQL("REPLACE INTO profiles(uuid, name, permit_posting, url, " +
+ "use_authentication, auth_user, auth_password, theme, order_no, " +
+ "preferred_accounts_filter, future_dates, api_version, " +
+ "show_commodity_by_default, default_commodity, show_comments_by_default) " +
+ "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ new Object[]{uuid, name, permitPosting, url, authEnabled,
+ authEnabled ? authUserName : null,
+ authEnabled ? authPassword : null, themeHue, orderNo,
+ preferredAccountsFilter, futureDates.toInt(), apiVersion.toInt(),
+ showCommodityByDefault, defaultCommodity, showCommentsByDefault
});
db.setTransactionSuccessful();
}
@@ -188,50 +421,57 @@ public final class MobileLedgerProfile {
db.endTransaction();
}
}
- public void storeAccount(LedgerAccount acc) {
- SQLiteDatabase db = MLDB.getWritableDatabase();
-
+ public void storeAccount(SQLiteDatabase db, LedgerAccount acc, boolean storeUiFields) {
// replace into is a bad idea because it would reset hidden to its default value
// we like the default, but for new accounts only
- db.execSQL("update accounts set level = ?, keep = 1 where profile=? and name = ?",
- new Object[]{acc.getLevel(), uuid, acc.getName()});
- db.execSQL("insert into accounts(profile, name, name_upper, parent_name, level) " +
- "select ?,?,?,?,? where (select changes() = 0)",
+ String sql = "update accounts set keep = 1";
+ List