profileModel = null;
}
public static void storeStartupProfileAndTheme(long currentProfileId, int currentTheme) {
- SharedPreferences prefs =
- instance.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ SharedPreferences prefs = instance.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(PREF_PROFILE_ID,
- currentProfileId);
+ editor.putLong(PREF_PROFILE_ID, currentProfileId);
editor.putInt(PREF_THEME_HUE, currentTheme);
editor.apply();
}
if (profileModel != null)
return profileModel.getAuthUserName();
return Data.getProfile()
- .getAuthUserName();
+ .getAuthUser();
}
private String getAuthPassword() {
if (profileModel != null)
if (profileModel != null)
return profileModel.getUseAuthentication();
return Data.getProfile()
- .isAuthEnabled();
+ .useAuthentication();
}
@Override
public void onCreate() {
import android.os.OperationCanceledException;
import androidx.annotation.NonNull;
+import androidx.room.Transaction;
import com.fasterxml.jackson.databind.RuntimeJsonMappingException;
import net.ktnx.mobileledger.App;
+import net.ktnx.mobileledger.dao.AccountDAO;
+import net.ktnx.mobileledger.dao.AccountValueDAO;
+import net.ktnx.mobileledger.dao.TransactionAccountDAO;
+import net.ktnx.mobileledger.dao.TransactionDAO;
+import net.ktnx.mobileledger.db.Account;
+import net.ktnx.mobileledger.db.AccountWithAmounts;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Option;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.db.TransactionAccount;
+import net.ktnx.mobileledger.db.TransactionWithAccounts;
import net.ktnx.mobileledger.err.HTTPException;
import net.ktnx.mobileledger.json.API;
import net.ktnx.mobileledger.json.AccountListParser;
import net.ktnx.mobileledger.model.LedgerAccount;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.utils.Logger;
+import net.ktnx.mobileledger.utils.MLDB;
import net.ktnx.mobileledger.utils.NetworkUtil;
import java.io.BufferedReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
private final Pattern reAccountValue = Pattern.compile(
"<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
private final MainModel mainModel;
- private final MobileLedgerProfile profile;
- private final List<LedgerAccount> prevAccounts;
+ private final Profile profile;
private int expectedPostingsCount = -1;
- public RetrieveTransactionsTask(@NonNull MainModel mainModel,
- @NonNull MobileLedgerProfile profile,
- List<LedgerAccount> accounts) {
+ public RetrieveTransactionsTask(@NonNull MainModel mainModel, @NonNull Profile profile) {
this.mainModel = mainModel;
this.profile = profile;
- this.prevAccounts = accounts;
}
private static void L(String msg) {
//debug("transaction-parser", msg);
state = ParserState.EXPECTING_TRANSACTION;
L(String.format("transaction %s parsed → expecting transaction",
- transaction.getId()));
+ transaction.getLedgerId()));
// sounds like a good idea, but transaction-1 may not be the first one chronologically
// for example, when you add the initial seeding transaction after entering some others
LedgerTransactionAccount lta = parseTransactionAccountLine(line);
if (lta != null) {
transaction.addAccount(lta);
- L(String.format(Locale.ENGLISH, "%d: %s = %s", transaction.getId(),
- lta.getAccountName(), lta.getAmount()));
+ L(String.format(Locale.ENGLISH, "%d: %s = %s",
+ transaction.getLedgerId(), lta.getAccountName(),
+ lta.getAmount()));
}
else
throw new IllegalStateException(
}
private List<LedgerAccount> retrieveAccountList()
throws IOException, HTTPException, ApiNotSupportedException {
- final API apiVersion = profile.getApiVersion();
+ final API apiVersion = API.valueOf(profile.getApiVersion());
if (apiVersion.equals(API.auto)) {
return retrieveAccountListAnyVersion();
}
SQLiteDatabase db = App.getDatabase();
ArrayList<LedgerAccount> list = new ArrayList<>();
HashMap<String, LedgerAccount> map = new HashMap<>();
- HashMap<String, LedgerAccount> currentMap = new HashMap<>();
- for (LedgerAccount acc : prevAccounts)
- currentMap.put(acc.getName(), acc);
throwIfCancelled();
try (InputStream resp = http.getInputStream()) {
throwIfCancelled();
throwIfCancelled();
}
- // the current account tree may have changed, update the new-to be tree to match
- for (LedgerAccount acc : list) {
- LedgerAccount prevData = currentMap.get(acc.getName());
- if (prevData != null) {
- acc.setExpanded(prevData.isExpanded());
- acc.setAmountsExpanded(prevData.amountsExpanded());
- }
- }
-
return list;
}
private List<LedgerTransaction> retrieveTransactionList()
throws ParseException, HTTPException, IOException, ApiNotSupportedException {
- final API apiVersion = profile.getApiVersion();
+ final API apiVersion = API.valueOf(profile.getApiVersion());
if (apiVersion.equals(API.auto)) {
return retrieveTransactionListAnyVersion();
}
.compareTo(o1.getDate());
if (res != 0)
return res;
- return Long.compare(o2.getId(), o1.getId());
+ return Long.compare(o2.getLedgerId(), o1.getLedgerId());
});
return trList;
}
transactions = new ArrayList<>();
retrieveTransactionListLegacy(accounts, transactions);
}
- mainModel.setAndStoreAccountAndTransactionListFromWeb(accounts, transactions);
+
+ storeAccountsAndTransactions(accounts, transactions);
+
+ mainModel.updateDisplayedTransactionsFromWeb(transactions);
return new Result(accounts, transactions);
}
Data.backgroundTaskFinished();
}
}
+ @Transaction
+ private void storeAccountsAndTransactions(List<LedgerAccount> accounts,
+ List<LedgerTransaction> transactions) {
+ AccountDAO accDao = DB.get()
+ .getAccountDAO();
+ TransactionDAO trDao = DB.get()
+ .getTransactionDAO();
+ TransactionAccountDAO trAccDao = DB.get()
+ .getTransactionAccountDAO();
+ AccountValueDAO valDao = DB.get()
+ .getAccountValueDAO();
+
+ final List<AccountWithAmounts> list = new ArrayList<>();
+ for (LedgerAccount acc : accounts) {
+ final AccountWithAmounts a = acc.toDBOWithAmounts();
+ Account existing = accDao.getByNameSync(profile.getId(), acc.getName());
+ if (existing != null) {
+ a.account.setExpanded(existing.isExpanded());
+ a.account.setAmountsExpanded(existing.isAmountsExpanded());
+ a.account.setId(
+ existing.getId()); // not strictly needed, but since we have it anyway...
+ }
+
+ list.add(a);
+ }
+ accDao.storeAccountsSync(list, profile.getId());
+
+ long trGen = trDao.getGenerationSync(profile.getId());
+ for (LedgerTransaction tr : transactions) {
+ TransactionWithAccounts tran = tr.toDBO();
+ tran.transaction.setGeneration(trGen);
+ tran.transaction.setProfileId(profile.getId());
+
+ tran.transaction.setId(trDao.insertSync(tran.transaction));
+
+ for (TransactionAccount trAcc : tran.accounts) {
+ trAcc.setGeneration(trGen);
+ trAcc.setTransactionId(tran.transaction.getId());
+ trAcc.setId(trAccDao.insertSync(trAcc));
+ }
+ }
+
+ trDao.purgeOldTransactionsSync(profile.getId(), trGen);
+
+ DB.get()
+ .getOptionDAO()
+ .insertSync(new Option(profile.getId(), MLDB.OPT_LAST_SCRAPE,
+ String.valueOf((new Date()).getTime())));
+ }
public void throwIfCancelled() {
if (isCancelled())
throw new OperationCanceledException(null);
import android.os.AsyncTask;
import android.util.Log;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.json.API;
import net.ktnx.mobileledger.json.ApiNotSupportedException;
import net.ktnx.mobileledger.json.Gateway;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.utils.Globals;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.NetworkUtil;
public class SendTransactionTask extends AsyncTask<LedgerTransaction, Void, Void> {
private final TaskCallback taskCallback;
- private final MobileLedgerProfile mProfile;
+ private final Profile mProfile;
private final boolean simulate;
protected String error;
private String token;
private String session;
private LedgerTransaction transaction;
- public SendTransactionTask(TaskCallback callback, MobileLedgerProfile profile,
+ public SendTransactionTask(TaskCallback callback, Profile profile,
boolean simulate) {
taskCallback = callback;
mProfile = profile;
this.simulate = simulate;
}
- public SendTransactionTask(TaskCallback callback, MobileLedgerProfile profile) {
+ public SendTransactionTask(TaskCallback callback, Profile profile) {
taskCallback = callback;
mProfile = profile;
simulate = false;
try {
transaction = ledgerTransactions[0];
- final API profileApiVersion = mProfile.getApiVersion();
+ final API profileApiVersion = API.valueOf(mProfile.getApiVersion());
switch (profileApiVersion) {
case auto:
boolean sendOK = false;
private SimpleDate earliestDate, latestDate;
private SimpleDate lastDate;
private boolean done;
- private int transactionCount = 0;
public TransactionAccumulator(MainModel model) {
this.model = model;
list.add(new TransactionListItem(transaction));
lastDate = date;
- transactionCount++;
}
public void done() {
done = true;
- model.setDisplayedTransactions(list, transactionCount);
+ model.setDisplayedTransactions(list);
model.setFirstTransactionDate(earliestDate);
model.setLastTransactionDate(latestDate);
}
import android.os.AsyncTask;
import net.ktnx.mobileledger.App;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerTransaction;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.utils.SimpleDate;
public class UpdateTransactionsTask extends AsyncTask<MainModel, Void, String> {
protected String doInBackground(MainModel[] model) {
- final MobileLedgerProfile profile = Data.getProfile();
+ final Profile profile = Data.getProfile();
long profile_id = profile.getId();
Data.backgroundTaskStarted();
final AccountValueDAO valueDAO = DB.get()
.getAccountValueDAO();
Account account = accountWithAmounts.account;
- Account existingAccount = getByNameSync(account.getProfileId(), account.getName());
- if (existingAccount != null) {
- existingAccount.setGeneration(account.getGeneration());
- account = existingAccount;
- updateSync(account);
- }
- else {
- long accountId = insertSync(account);
- account.setId(accountId);
- }
+ account.setId(insertSync(account));
for (AccountValue value : accountWithAmounts.amounts) {
value.setAccountId(account.getId());
value.setGeneration(account.getGeneration());
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
-public interface CurrencyDAO {
- @Insert
- void insert(Currency... items);
+public abstract class CurrencyDAO extends BaseDAO<Currency> {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ abstract long insertSync(Currency item);
@Update
- void update(Currency... items);
+ abstract void updateSync(Currency item);
@Delete
- void delete(Currency item);
+ public abstract void deleteSync(Currency item);
@Query("SELECT * FROM currencies")
- LiveData<List<Currency>> getCurrencies();
+ public abstract LiveData<List<Currency>> getAll();
@Query("SELECT * FROM currencies WHERE id = :id")
- LiveData<Currency> getCurrencyById(Long id);
+ abstract LiveData<Currency> getById(long id);
+
+ @Query("SELECT * FROM currencies WHERE id = :id")
+ public abstract Currency getByIdSync(long id);
+
+ @Query("SELECT * FROM currencies WHERE name = :name")
+ public abstract LiveData<Currency> getByName(String name);
// not useful for now
// @Transaction
package net.ktnx.mobileledger.dao;
+import android.os.AsyncTask;
+
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
import androidx.room.Query;
+import androidx.room.Transaction;
import androidx.room.Update;
import net.ktnx.mobileledger.db.Profile;
+import java.util.List;
+
@Dao
public abstract class ProfileDAO extends BaseDAO<Profile> {
- @Insert
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract long insertSync(Profile item);
+ @Transaction
+ public long insertLastSync(Profile item) {
+ int count = getProfileCountSync();
+ item.setOrderNo(count + 1);
+ return insertSync(item);
+ }
+ public void insertLast(Profile item, OnInsertedReceiver onInsertedReceiver) {
+ AsyncTask.execute(() -> {
+ long id = insertLastSync(item);
+ if (onInsertedReceiver != null)
+ onInsertedReceiver.onInsert(id);
+ });
+ }
+
@Update
abstract void updateSync(Profile item);
@Query("SELECT * FROM profiles WHERE id=:profileId")
public abstract LiveData<Profile> getById(long profileId);
+ @Query("SELECT * FROM profiles ORDER BY order_no")
+ public abstract List<Profile> getAllOrderedSync();
+
+ @Query("SELECT * FROM profiles ORDER BY order_no")
+ public abstract LiveData<List<Profile>> getAllOrdered();
+
@Query("SELECT * FROM profiles LIMIT 1")
public abstract Profile getAnySync();
+
+ @Query("SELECT MAX(order_no) FROM profiles")
+ public abstract int getProfileCountSync();
+ public void updateOrderSync(List<Profile> list) {
+ if (list == null)
+ list = getAllOrderedSync();
+ int order = 1;
+ for (Profile p : list) {
+ p.setOrderNo(order++);
+ updateSync(p);
+ }
+ }
+ public void updateOrder(List<Profile> list, Runnable onDone) {
+ AsyncTask.execute(() -> {
+ updateOrderSync(list);
+ if (onDone != null)
+ onDone.run();
+
+ });
+ }
}
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.dao;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import net.ktnx.mobileledger.db.TransactionAccount;
+
+import java.util.List;
+
+@Dao
+public abstract class TransactionAccountDAO extends BaseDAO<TransactionAccount> {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ public abstract long insertSync(TransactionAccount item);
+
+ @Update
+ public abstract void updateSync(TransactionAccount item);
+
+ @Delete
+ public abstract void deleteSync(TransactionAccount item);
+
+ @Delete
+ public abstract void deleteSync(List<TransactionAccount> items);
+
+ @Query("SELECT * FROM transaction_accounts WHERE id = :id")
+ public abstract LiveData<TransactionAccount> getById(long id);
+}
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
return result;
}
- @Insert
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long insertSync(Transaction item);
@Update
@Query("SELECT * FROM transactions WHERE id = :transactionId")
public abstract LiveData<TransactionWithAccounts> getByIdWithAccounts(long transactionId);
+ @androidx.room.Transaction
+ @Query("SELECT * FROM transactions WHERE id = :transactionId")
+ public abstract TransactionWithAccounts getByIdWithAccountsSync(long transactionId);
+
@Query("SELECT DISTINCT description, CASE WHEN description_upper LIKE :term||'%%' THEN 1 " +
" WHEN description_upper LIKE '%%:'||:term||'%%' THEN 2 " +
" WHEN description_upper LIKE '%% '||:term||'%%' THEN 3 " +
"ORDER BY ordering, description_upper, rowid ")
public abstract List<DescriptionContainer> lookupDescriptionSync(@NonNull String term);
+ @androidx.room.Transaction
+ @Query("SELECT * from transactions WHERE description = :description ORDER BY year desc, month" +
+ " desc, day desc LIMIT 1")
+ public abstract TransactionWithAccounts getFirstByDescriptionSync(@NonNull String description);
+
+ @androidx.room.Transaction
+ @Query("SELECT * from transactions tr JOIN transaction_accounts t_a ON t_a.transaction_id = " +
+ "tr.id WHERE tr.description = :description AND t_a.account_name LIKE " +
+ "'%'||:accountTerm||'%' ORDER BY year desc, month desc, day desc LIMIT 1")
+ public abstract TransactionWithAccounts getFirstByDescriptionHavingAccountSync(
+ @NonNull String description, @NonNull String accountTerm);
+
@Query("SELECT * from transactions WHERE profile_id = :profileId")
public abstract List<Transaction> allForProfileSync(long profileId);
+ @Query("SELECT generation FROM transactions WHERE profile_id = :profileId LIMIT 1")
+ protected abstract TransactionGenerationContainer getGenerationPOJOSync(long profileId);
+ public long getGenerationSync(long profileId) {
+ TransactionGenerationContainer result = getGenerationPOJOSync(profileId);
+
+ if (result == null)
+ return 0;
+ return result.generation;
+ }
+ @Query("DELETE FROM transactions WHERE profile_id = :profileId AND generation <> " +
+ ":currentGeneration")
+ public abstract void purgeOldTransactionsSync(long profileId, long currentGeneration);
+ static class TransactionGenerationContainer {
+ @ColumnInfo
+ long generation;
+ public TransactionGenerationContainer(long generation) {
+ this.generation = generation;
+ }
+ }
+
static public class DescriptionContainer {
@ColumnInfo
public String description;
import androidx.annotation.NonNull;
import net.ktnx.mobileledger.dao.AccountDAO;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.utils.Logger;
import java.util.ArrayList;
public AccountAutocompleteAdapter(Context context) {
super(context, android.R.layout.simple_dropdown_item_1line, new ArrayList<>());
}
- public AccountAutocompleteAdapter(Context context, @NonNull MobileLedgerProfile profile) {
+ public AccountAutocompleteAdapter(Context context, @NonNull Profile profile) {
this(context);
profileId = profile.getId();
}
import net.ktnx.mobileledger.dao.ProfileDAO;
import net.ktnx.mobileledger.dao.TemplateAccountDAO;
import net.ktnx.mobileledger.dao.TemplateHeaderDAO;
+import net.ktnx.mobileledger.dao.TransactionAccountDAO;
import net.ktnx.mobileledger.dao.TransactionDAO;
import net.ktnx.mobileledger.utils.Logger;
public abstract TransactionDAO getTransactionDAO();
+ public abstract TransactionAccountDAO getTransactionAccountDAO();
+
public abstract OptionDAO getOptionDAO();
public abstract DescriptionHistoryDAO getDescriptionHistoryDAO();
})
public class TemplateAccount extends TemplateBase {
@PrimaryKey(autoGenerate = true)
- @NotNull
- private Long id;
- @NonNull
+ private long id;
@ColumnInfo(name = "template_id")
- private Long templateId;
+ private long templateId;
@ColumnInfo(name = "acc")
private String accountName;
@ColumnInfo(name = "position")
private Long position;
@ColumnInfo(name = "acc_match_group")
private Integer accountNameMatchGroup;
- @ColumnInfo(name = "currency")
- private Integer currency;
+ @ColumnInfo
+ private Long currency;
@ColumnInfo(name = "currency_match_group")
private Integer currencyMatchGroup;
@ColumnInfo(name = "amount")
accountCommentMatchGroup = o.accountCommentMatchGroup;
negateAmount = o.negateAmount;
}
- public Long getId() {
+ public long getId() {
return id;
}
- public void setId(Long id) {
+ public void setId(long id) {
this.id = id;
}
public Boolean getNegateAmount() {
public void setAccountNameMatchGroup(Integer accountNameMatchGroup) {
this.accountNameMatchGroup = accountNameMatchGroup;
}
- public Integer getCurrency() {
+ public Long getCurrency() {
return currency;
}
- public void setCurrency(Integer currency) {
+ public void setCurrency(Long currency) {
this.currency = currency;
}
+ public Currency getCurrencyObject() {
+ if (currency == null || currency <= 0)
+ return null;
+ return DB.get()
+ .getCurrencyDAO()
+ .getByIdSync(currency);
+ }
public Integer getCurrencyMatchGroup() {
return currencyMatchGroup;
}
public void setAccountCommentMatchGroup(Integer accountCommentMatchGroup) {
this.accountCommentMatchGroup = accountCommentMatchGroup;
}
- public TemplateAccount createDuplicate() {
+ public TemplateAccount createDuplicate(TemplateHeader header) {
TemplateAccount dup = new TemplateAccount(this);
- dup.id = null;
- dup.templateId = null;
+ dup.id = 0;
+ dup.templateId = header.getId();
return dup;
}
@Entity(tableName = "templates")
public class TemplateHeader extends TemplateBase {
@PrimaryKey(autoGenerate = true)
- @NonNull
- private Long id;
+ private long id;
@ColumnInfo(name = "name")
@NonNull
private String name;
public void setDateDay(Integer dateDay) {
this.dateDay = dateDay;
}
- @NonNull
- public Long getId() {
+ public long getId() {
return id;
}
- public void setId(@NonNull Long id) {
+ public void setId(long id) {
this.id = id;
}
@NonNull
}
public TemplateHeader createDuplicate() {
TemplateHeader dup = new TemplateHeader(this);
- dup.id = null;
+ dup.id = 0;
return dup;
}
result.header = header.createDuplicate();
result.accounts = new ArrayList<>();
for (TemplateAccount acc : accounts) {
- result.accounts.add(acc.createDuplicate());
+ result.accounts.add(acc.createDuplicate(result.header));
}
return result;
@ColumnInfo
private String comment;
@ColumnInfo
- private int generation = 0;
+ private long generation = 0;
public long getLedgerId() {
return ledgerId;
}
public void setComment(String comment) {
this.comment = comment;
}
- public int getGeneration() {
+ public long getGeneration() {
return generation;
}
- public void setGeneration(int generation) {
+ public void setGeneration(long generation) {
this.generation = generation;
}
@PrimaryKey(autoGenerate = true)
private long id;
@ColumnInfo(name = "transaction_id")
- private int transactionId;
+ private long transactionId;
@ColumnInfo(name = "order_no")
private int orderNo;
@ColumnInfo(name = "account_name")
@ColumnInfo
private String comment;
@ColumnInfo(defaultValue = "0")
- private int generation = 0;
+ private long generation = 0;
public long getId() {
return id;
}
this.id = id;
}
@NonNull
- public int getTransactionId() {
+ public long getTransactionId() {
return transactionId;
}
- public void setTransactionId(int transactionId) {
+ public void setTransactionId(long transactionId) {
this.transactionId = transactionId;
}
public int getOrderNo() {
public void setComment(String comment) {
this.comment = comment;
}
- public int getGeneration() {
+ public long getGeneration() {
return generation;
}
- public void setGeneration(int generation) {
+ public void setGeneration(long generation) {
this.generation = generation;
}
}
package net.ktnx.mobileledger.model;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.DiffUtil;
-
-import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.utils.Misc;
public class Currency {
- public static final DiffUtil.ItemCallback<Currency> DIFF_CALLBACK =
- new DiffUtil.ItemCallback<Currency>() {
- @Override
- public boolean areItemsTheSame(@NonNull Currency oldItem,
- @NonNull Currency newItem) {
- return oldItem.id == newItem.id;
- }
- @Override
- public boolean areContentsTheSame(@NonNull Currency oldItem,
- @NonNull Currency newItem) {
- return oldItem.name.equals(newItem.name) &&
- oldItem.position.equals(newItem.position) &&
- (oldItem.hasGap == newItem.hasGap);
- }
- };
private final int id;
private String name;
private Position position;
this.position = position;
this.hasGap = hasGap;
}
- public Currency(MobileLedgerProfile profile, String name, Position position, boolean hasGap) {
- SQLiteDatabase db = App.getDatabase();
-
- try (Cursor c = db.rawQuery("select max(rowid) from currencies", null)) {
- c.moveToNext();
- this.id = c.getInt(0) + 1;
- }
- db.execSQL("insert into currencies(id, name, position, has_gap) values(?, ?, ?, ?)",
- new Object[]{this.id, name, position.toString(), hasGap});
-
- this.name = name;
- this.position = position;
- this.hasGap = hasGap;
- }
- public static Currency loadByName(String name) {
- MobileLedgerProfile profile = Data.getProfile();
- return profile.loadCurrencyByName(name);
- }
- public static Currency loadById(int id) {
- MobileLedgerProfile profile = Data.getProfile();
- return profile.loadCurrencyById(id);
- }
static public boolean equal(Currency left, Currency right) {
if (left == null) {
return right == null;
package net.ktnx.mobileledger.model;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
-import net.ktnx.mobileledger.utils.LockHolder;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.utils.Locker;
import net.ktnx.mobileledger.utils.Logger;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
-import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
new MutableLiveData<>(false);
public static final MutableLiveData<RetrieveTransactionsTask.Progress> backgroundTaskProgress =
new MutableLiveData<>();
- public static final MutableLiveData<ArrayList<MobileLedgerProfile>> profiles =
- new MutableLiveData<>(null);
+ public static final LiveData<List<Profile>> profiles = DB.get().getProfileDAO().getAllOrdered();
public static final MutableLiveData<Currency.Position> currencySymbolPosition =
new MutableLiveData<>();
public static final MutableLiveData<Boolean> currencyGap = new MutableLiveData<>(true);
public static final MutableLiveData<String> lastTransactionsUpdateText =
new MutableLiveData<>();
public static final MutableLiveData<String> lastAccountsUpdateText = new MutableLiveData<>();
- private static final MutableLiveData<MobileLedgerProfile> profile =
+ private static final MutableLiveData<Profile> profile =
new InertMutableLiveData<>();
private static final AtomicInteger backgroundTaskCount = new AtomicInteger(0);
private static final Locker profilesLocker = new Locker();
}
@NonNull
- public static MobileLedgerProfile getProfile() {
+ public static Profile getProfile() {
return Objects.requireNonNull(profile.getValue());
}
public static void backgroundTaskStarted() {
cnt));
backgroundTasksRunning.postValue(cnt > 0);
}
- public static void setCurrentProfile(@NonNull MobileLedgerProfile newProfile) {
+ public static void setCurrentProfile(@NonNull Profile newProfile) {
profile.setValue(newProfile);
}
- public static void postCurrentProfile(@NonNull MobileLedgerProfile newProfile) {
+ public static void postCurrentProfile(@NonNull Profile newProfile) {
profile.postValue(newProfile);
}
- public static int getProfileIndex(MobileLedgerProfile profile) {
- try (LockHolder ignored = profilesLocker.lockForReading()) {
- List<MobileLedgerProfile> prList = profiles.getValue();
- if (prList == null)
- throw new AssertionError();
- for (int i = 0; i < prList.size(); i++) {
- MobileLedgerProfile p = prList.get(i);
- if (p.equals(profile))
- return i;
- }
-
- return -1;
- }
- }
- @SuppressWarnings("WeakerAccess")
- public static int getProfileIndex(long profileId) {
- try (LockHolder ignored = profilesLocker.lockForReading()) {
- List<MobileLedgerProfile> prList = profiles.getValue();
- if (prList == null)
- throw new AssertionError();
- for (int i = 0; i < prList.size(); i++) {
- MobileLedgerProfile p = prList.get(i);
- if (p.getId() == profileId)
- return i;
- }
-
- return -1;
- }
- }
- @Nullable
- public static MobileLedgerProfile getProfile(long profileId) {
- MobileLedgerProfile profile;
- try (LockHolder readLock = profilesLocker.lockForReading()) {
- List<MobileLedgerProfile> prList = profiles.getValue();
- if ((prList == null) || prList.isEmpty()) {
- readLock.close();
- try (LockHolder ignored = profilesLocker.lockForWriting()) {
- profile = MobileLedgerProfile.loadAllFromDB(profileId);
- }
- }
- else {
- int i = getProfileIndex(profileId);
- if (i == -1)
- i = 0;
- profile = prList.get(i);
- }
- }
- return profile;
- }
public static void refreshCurrencyData(Locale locale) {
NumberFormat formatter = NumberFormat.getCurrencyInstance(locale);
java.util.Currency currency = formatter.getCurrency();
return numberFormatter.format(number);
}
public static void observeProfile(LifecycleOwner lifecycleOwner,
- Observer<MobileLedgerProfile> observer) {
+ Observer<Profile> observer) {
profile.observe(lifecycleOwner, observer);
}
public static void removeProfileObservers(LifecycleOwner owner) {
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+import android.content.res.Resources;
+import android.util.SparseArray;
+
+import net.ktnx.mobileledger.R;
+
+public enum FutureDates {
+ None(0), OneWeek(7), TwoWeeks(14), OneMonth(30), TwoMonths(60), ThreeMonths(90),
+ SixMonths(180), OneYear(365), All(-1);
+ private static final SparseArray<FutureDates> map = new SparseArray<>();
+
+ static {
+ for (FutureDates item : FutureDates.values()) {
+ map.put(item.value, item);
+ }
+ }
+
+ private final int value;
+ FutureDates(int value) {
+ this.value = value;
+ }
+ public static FutureDates valueOf(int i) {
+ return map.get(i, None);
+ }
+ public int toInt() {
+ return this.value;
+ }
+ public String getText(Resources resources) {
+ switch (value) {
+ case 7:
+ return resources.getString(R.string.future_dates_7);
+ case 14:
+ return resources.getString(R.string.future_dates_14);
+ case 30:
+ return resources.getString(R.string.future_dates_30);
+ case 60:
+ return resources.getString(R.string.future_dates_60);
+ case 90:
+ return resources.getString(R.string.future_dates_90);
+ case 180:
+ return resources.getString(R.string.future_dates_180);
+ case 365:
+ return resources.getString(R.string.future_dates_365);
+ case -1:
+ return resources.getString(R.string.future_dates_all);
+ default:
+ return resources.getString(R.string.future_dates_none);
+ }
+ }
+}
return dbo;
}
+ @NonNull
+ public AccountWithAmounts toDBOWithAmounts() {
+ AccountWithAmounts dbo = new AccountWithAmounts();
+ dbo.account = toDBO();
+
+ dbo.amounts = new ArrayList<>();
+ for (LedgerAmount amt : getAmounts()) {
+ AccountValue val = new AccountValue();
+ val.setCurrency(amt.getCurrency());
+ val.setValue(amt.getAmount());
+ dbo.amounts.add(val);
+ }
+
+ return dbo;
+ }
public long getId() {
return dbId;
}
import androidx.annotation.Nullable;
import net.ktnx.mobileledger.App;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.db.Transaction;
+import net.ktnx.mobileledger.db.TransactionAccount;
+import net.ktnx.mobileledger.db.TransactionWithAccounts;
import net.ktnx.mobileledger.utils.Digest;
import net.ktnx.mobileledger.utils.Globals;
import net.ktnx.mobileledger.utils.SimpleDate;
return Float.compare(o1.getAmount(), o2.getAmount());
};
private final long profile;
- private final long id;
+ private final long ledgerId;
private final List<LedgerTransactionAccount> accounts;
+ private long dbId;
private SimpleDate date;
private String description;
private String comment;
private String dataHash;
private boolean dataLoaded;
- public LedgerTransaction(long id, String dateString, String description) throws ParseException {
- this(id, Globals.parseLedgerDate(dateString), description);
+ public LedgerTransaction(long ledgerId, String dateString, String description)
+ throws ParseException {
+ this(ledgerId, Globals.parseLedgerDate(dateString), description);
}
- public LedgerTransaction(long id, SimpleDate date, String description,
- MobileLedgerProfile profile) {
+ public LedgerTransaction(TransactionWithAccounts dbo) {
+ this(dbo.transaction.getLedgerId(), dbo.transaction.getProfileId());
+ dbId = dbo.transaction.getId();
+ date = new SimpleDate(dbo.transaction.getYear(), dbo.transaction.getMonth(),
+ dbo.transaction.getDay());
+ description = dbo.transaction.getDescription();
+ comment = dbo.transaction.getComment();
+ dataHash = dbo.transaction.getDataHash();
+ if (dbo.accounts != null)
+ for (TransactionAccount acc : dbo.accounts) {
+ accounts.add(new LedgerTransactionAccount(acc));
+ }
+ dataLoaded = true;
+ }
+ public TransactionWithAccounts toDBO() {
+ TransactionWithAccounts o = new TransactionWithAccounts();
+ o.transaction = new Transaction();
+ o.transaction.setId(dbId);
+ o.transaction.setProfileId(profile);
+ o.transaction.setLedgerId(ledgerId);
+ o.transaction.setYear(date.year);
+ o.transaction.setMonth(date.month);
+ o.transaction.setDay(date.day);
+ o.transaction.setDescription(description);
+ o.transaction.setComment(comment);
+ fillDataHash();
+ o.transaction.setDataHash(dataHash);
+
+ o.accounts = new ArrayList<>();
+ int orderNo = 1;
+ for (LedgerTransactionAccount acc : accounts) {
+ TransactionAccount a = acc.toDBO();
+ a.setOrderNo(orderNo++);
+ a.setTransactionId(dbId);
+ o.accounts.add(a);
+ }
+ return o;
+ }
+ public LedgerTransaction(long ledgerId, SimpleDate date, String description, Profile profile) {
this.profile = profile.getId();
- this.id = id;
+ this.ledgerId = ledgerId;
this.date = date;
this.description = description;
this.accounts = new ArrayList<>();
this.dataHash = null;
dataLoaded = false;
}
- public LedgerTransaction(long id, SimpleDate date, String description) {
- this(id, date, description, Data.getProfile());
+ public LedgerTransaction(long ledgerId, SimpleDate date, String description) {
+ this(ledgerId, date, description, Data.getProfile());
}
public LedgerTransaction(SimpleDate date, String description) {
this(0, date, description);
}
- public LedgerTransaction(int id) {
- this(id, (SimpleDate) null, null);
+ public LedgerTransaction(int ledgerId) {
+ this(ledgerId, (SimpleDate) null, null);
}
- public LedgerTransaction(int id, long profileId) {
+ public LedgerTransaction(long ledgerId, long profileId) {
this.profile = profileId;
- this.id = id;
+ this.ledgerId = ledgerId;
this.date = null;
this.description = null;
this.accounts = new ArrayList<>();
public void setComment(String comment) {
this.comment = comment;
}
- public long getId() {
- return id;
+ public long getLedgerId() {
+ return ledgerId;
}
protected void fillDataHash() {
loadData(App.getDatabase());
StringBuilder data = new StringBuilder();
data.append("ver1");
data.append(profile);
- data.append(getId());
+ data.append(getLedgerId());
data.append('\0');
data.append(getDescription());
data.append('\0');
try (Cursor cTr = db.rawQuery(
"SELECT year, month, day, description, comment from transactions WHERE id=?",
- new String[]{String.valueOf(id)}))
+ new String[]{String.valueOf(ledgerId)}))
{
if (cTr.moveToFirst()) {
date = new SimpleDate(cTr.getInt(0), cTr.getInt(1), cTr.getInt(2));
try (Cursor cAcc = db.rawQuery(
"SELECT account_name, amount, currency, comment FROM " +
"transaction_accounts WHERE transaction_id = ?",
- new String[]{String.valueOf(id)}))
+ new String[]{String.valueOf(ledgerId)}))
{
while (cAcc.moveToNext()) {
// debug("transactions",
import androidx.annotation.NonNull;
+import net.ktnx.mobileledger.db.TransactionAccount;
import net.ktnx.mobileledger.utils.Misc;
import java.util.Locale;
private String currency;
private String comment;
private boolean amountValid = true;
+ private long dbId;
public LedgerTransactionAccount(String accountName, float amount, String currency,
String comment) {
this.setAccountName(accountName);
amountValid = origin.amountValid;
currency = origin.getCurrency();
}
+ public LedgerTransactionAccount(TransactionAccount dbo) {
+ this(dbo.getAccountName(), dbo.getAmount(), Misc.emptyIsNull(dbo.getCurrency()),
+ Misc.emptyIsNull(dbo.getComment()));
+ amountSet = true;
+ amountValid = true;
+ dbId = dbo.getId();
+ }
public String getComment() {
return comment;
}
return sb.toString();
}
+ public TransactionAccount toDBO() {
+ TransactionAccount dbo = new TransactionAccount();
+ dbo.setAccountName(accountName);
+ if (amountSet)
+ dbo.setAmount(amount);
+ dbo.setComment(comment);
+ dbo.setCurrency(currency);
+ dbo.setId(dbId);
+
+ return dbo;
+ }
}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright © 2021 Damyan Ivanov.
- * This file is part of MoLe.
- * MoLe is free software: you can distribute it and/or modify it
- * under the term of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your opinion), any later version.
- *
- * MoLe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License terms for details.
- *
- * You should have received a copy of the GNU General Public License
- * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger.model;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-import androidx.room.Transaction;
-
-import net.ktnx.mobileledger.App;
-import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.async.DbOpQueue;
-import net.ktnx.mobileledger.dao.AccountDAO;
-import net.ktnx.mobileledger.dao.DescriptionHistoryDAO;
-import net.ktnx.mobileledger.dao.OptionDAO;
-import net.ktnx.mobileledger.dao.ProfileDAO;
-import net.ktnx.mobileledger.dao.TransactionDAO;
-import net.ktnx.mobileledger.db.AccountValue;
-import net.ktnx.mobileledger.db.AccountWithAmounts;
-import net.ktnx.mobileledger.db.DB;
-import net.ktnx.mobileledger.db.Profile;
-import net.ktnx.mobileledger.json.API;
-import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
-import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment;
-import net.ktnx.mobileledger.utils.Logger;
-import net.ktnx.mobileledger.utils.Misc;
-import net.ktnx.mobileledger.utils.SimpleDate;
-
-import org.jetbrains.annotations.Contract;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
-public final class MobileLedgerProfile {
- // N.B. when adding new fields, update the copy-constructor below
- private final long id;
- 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;
- private int themeHue;
- private int orderNo = -1;
- private API apiVersion = API.auto;
- private FutureDates futureDates = FutureDates.None;
- private boolean accountsLoaded;
- private boolean transactionsLoaded;
- private HledgerVersion detectedVersion;
- // N.B. when adding new fields, update the copy-constructor below
- transient private AccountAndTransactionListSaver accountAndTransactionListSaver;
- public MobileLedgerProfile(long id) {
- this.id = id;
- }
- public MobileLedgerProfile(MobileLedgerProfile origin) {
- id = origin.id;
- 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;
- accountsLoaded = origin.accountsLoaded;
- transactionsLoaded = origin.transactionsLoaded;
- if (origin.detectedVersion != null)
- detectedVersion = new HledgerVersion(origin.detectedVersion);
- }
- // loads all profiles into Data.profiles
- // returns the profile with the given UUID
- public static MobileLedgerProfile loadAllFromDB(long currentProfileId) {
- MobileLedgerProfile result = null;
- ArrayList<MobileLedgerProfile> list = new ArrayList<>();
- SQLiteDatabase db = App.getDatabase();
- try (Cursor cursor = db.rawQuery("SELECT id, name, url, use_authentication, auth_user, " +
- "auth_password, permit_posting, theme, order_no, " +
- "preferred_accounts_filter, future_dates, api_version, " +
- "show_commodity_by_default, default_commodity, " +
- "show_comments_by_default, detected_version_pre_1_19, " +
- "detected_version_major, detected_version_minor FROM " +
- "profiles order by order_no", null))
- {
- while (cursor.moveToNext()) {
- MobileLedgerProfile item = new MobileLedgerProfile(cursor.getLong(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);
- {
- boolean pre_1_20 = cursor.getInt(15) == 1;
- int major = cursor.getInt(16);
- int minor = cursor.getInt(17);
-
- if (!pre_1_20 && major == 0 && minor == 0) {
- item.detectedVersion = null;
- }
- else if (pre_1_20) {
- item.detectedVersion = new HledgerVersion(true);
- }
- else {
- item.detectedVersion = new HledgerVersion(major, minor);
- }
- }
- list.add(item);
- if (item.getId() == currentProfileId)
- result = item;
- }
- }
- Data.profiles.postValue(list);
- return result;
- }
- public static void storeProfilesOrder() {
- SQLiteDatabase db = App.getDatabase();
- db.beginTransactionNonExclusive();
- try {
- int orderNo = 0;
- for (MobileLedgerProfile p : Objects.requireNonNull(Data.profiles.getValue())) {
- db.execSQL("update profiles set order_no=? where id=?",
- new Object[]{orderNo, p.getId()});
- p.orderNo = orderNo;
- orderNo++;
- }
- db.setTransactionSuccessful();
- }
- finally {
- db.endTransaction();
- }
- }
- static public void startEditProfileActivity(Context context, MobileLedgerProfile profile) {
- Intent intent = new Intent(context, ProfileDetailActivity.class);
- Bundle args = new Bundle();
- if (profile != null) {
- int index = Data.getProfileIndex(profile);
- if (index != -1)
- intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID, index);
- }
- intent.putExtras(args);
- context.startActivity(intent, args);
- }
- public static MobileLedgerProfile fromDBO(Profile newProfile) {
- MobileLedgerProfile p = new MobileLedgerProfile(newProfile.getId());
- p.setDetectedVersion(new HledgerVersion(newProfile.getDetectedVersionMajor(),
- newProfile.getDetectedVersionMinor()));
- p.setApiVersion(newProfile.getApiVersion());
- p.setAuthEnabled(newProfile.useAuthentication());
- p.setAuthUserName(newProfile.getAuthUser());
- p.setAuthPassword(newProfile.getAuthPassword());
- p.setDefaultCommodity(newProfile.getDefaultCommodity());
- p.setFutureDates(newProfile.getFutureDates());
- p.setName(newProfile.getName());
- p.setPostingPermitted(newProfile.permitPosting());
- p.setPreferredAccountsFilter(newProfile.getPreferredAccountsFilter());
- p.setShowCommentsByDefault(newProfile.getShowCommentsByDefault());
- p.setShowCommodityByDefault(newProfile.getShowCommodityByDefault());
- p.setUrl(newProfile.getUrl());
- p.setThemeId(newProfile.getTheme());
-
- return p;
- }
- public HledgerVersion getDetectedVersion() {
- return detectedVersion;
- }
- public void setDetectedVersion(HledgerVersion detectedVersion) {
- this.detectedVersion = detectedVersion;
- }
- @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 (id != p.id)
- 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(detectedVersion, p.detectedVersion))
- return false;
- return futureDates == p.futureDates;
- }
- 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 API getApiVersion() {
- return apiVersion;
- }
- public void setApiVersion(API apiVersion) {
- this.apiVersion = apiVersion;
- }
- public void setApiVersion(int apiVersion) {
- this.apiVersion = 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 long getId() {
- return id;
- }
- public String getName() {
- return 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(CharSequence text) {
- setUrl(String.valueOf(text));
- }
- public void setUrl(String url) {
- this.url = url;
- }
- public boolean isAuthEnabled() {
- return authEnabled;
- }
- public void setAuthEnabled(boolean authEnabled) {
- this.authEnabled = authEnabled;
- }
- public String getAuthUserName() {
- return 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(CharSequence text) {
- setAuthPassword(String.valueOf(text));
- }
- public void setAuthPassword(String authPassword) {
- this.authPassword = authPassword;
- }
- public void storeInDB() {
- SQLiteDatabase db = App.getDatabase();
- db.beginTransactionNonExclusive();
- try {
-// 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(id, 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," +
- "detected_version_pre_1_19, detected_version_major, " +
- "detected_version_minor) " +
- "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- new Object[]{id, name, permitPosting, url, authEnabled,
- authEnabled ? authUserName : null,
- authEnabled ? authPassword : null, themeHue, orderNo,
- preferredAccountsFilter, futureDates.toInt(), apiVersion.toInt(),
- showCommodityByDefault, defaultCommodity, showCommentsByDefault,
- (detectedVersion != null) && detectedVersion.isPre_1_20_1(),
- (detectedVersion == null) ? 0 : detectedVersion.getMajor(),
- (detectedVersion == null) ? 0 : detectedVersion.getMinor()
- });
- db.setTransactionSuccessful();
- }
- finally {
- db.endTransaction();
- }
- }
- public void storeAccount(SQLiteDatabase db, int generation, 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
- String sql = "update accounts set generation = ?";
- List<Object> params = new ArrayList<>();
- params.add(generation);
- if (storeUiFields) {
- sql += ", expanded=?";
- params.add(acc.isExpanded() ? 1 : 0);
- }
- sql += " where profile_id=? and name=?";
- params.add(id);
- params.add(acc.getName());
- db.execSQL(sql, params.toArray());
-
- db.execSQL("insert into accounts(profile_id, name, name_upper, parent_name, level, " +
- "expanded, generation) select ?,?,?,?,?,0,? where (select changes() = 0)",
- new Object[]{id, acc.getName(), acc.getName().toUpperCase(), acc.getParentName(),
- acc.getLevel(), generation
- });
-// debug("accounts", String.format("Stored account '%s' in DB [%s]", acc.getName(), uuid));
- }
- public void storeTransaction(SQLiteDatabase db, int generation, LedgerTransaction tr) {
- tr.fillDataHash();
-// Logger.debug("storeTransaction", String.format(Locale.US, "ID %d", tr.getId()));
- SimpleDate d = tr.getDate();
- db.execSQL("UPDATE transactions SET year=?, month=?, day=?, description=?, comment=?, " +
- "data_hash=?, generation=? WHERE profile_id=? AND ledger_id=?",
- new Object[]{d.year, d.month, d.day, tr.getDescription(), tr.getComment(),
- tr.getDataHash(), generation, id, tr.getId()
- });
- db.execSQL(
- "INSERT INTO transactions(profile_id, ledger_id, year, month, day, description, " +
- "comment, data_hash, generation) " +
- "select ?,?,?,?,?,?,?,?,? WHERE (select changes() = 0)",
- new Object[]{id, tr.getId(), tr.getDate().year, tr.getDate().month,
- tr.getDate().day, tr.getDescription(), tr.getComment(),
- tr.getDataHash(), generation
- });
-
- int accountOrderNo = 1;
- for (LedgerTransactionAccount item : tr.getAccounts()) {
- db.execSQL("UPDATE transaction_accounts SET account_name=?, amount=?, currency=?, " +
- "comment=?, generation=? " + "WHERE transaction_id=? AND order_no=?",
- new Object[]{item.getAccountName(), item.getAmount(),
- Misc.nullIsEmpty(item.getCurrency()), item.getComment(),
- generation, tr.getId(), accountOrderNo
- });
- db.execSQL("INSERT INTO transaction_accounts(transaction_id, " +
- "order_no, account_name, amount, currency, comment, generation) " +
- "select ?, ?, ?, ?, ?, ?, ? WHERE (select changes() = 0)",
- new Object[]{tr.getId(), accountOrderNo, item.getAccountName(),
- item.getAmount(), Misc.nullIsEmpty(item.getCurrency()),
- item.getComment(), generation
- });
-
- accountOrderNo++;
- }
-// debug("profile", String.format("Transaction %d stored", tr.getId()));
- }
- public String getOption(String name, String default_value) {
- SQLiteDatabase db = App.getDatabase();
- try (Cursor cursor = db.rawQuery(
- "select value from options where profile_id = ? and name=?",
- new String[]{String.valueOf(id), name}))
- {
- if (cursor.moveToFirst()) {
- String result = cursor.getString(0);
-
- if (result == null) {
- debug("profile", "returning default value for " + name);
- result = default_value;
- }
- else
- debug("profile", String.format("option %s=%s", name, result));
-
- return result;
- }
- else
- return default_value;
- }
- catch (Exception e) {
- debug("db", "returning default value for " + name, e);
- return default_value;
- }
- }
- public long getLongOption(String name, long default_value) {
- long longResult;
- String result = getOption(name, "");
- if ((result == null) || result.isEmpty()) {
- debug("profile", String.format("Returning default value for option %s", name));
- longResult = default_value;
- }
- else {
- try {
- longResult = Long.parseLong(result);
- debug("profile", String.format("option %s=%s", name, result));
- }
- catch (Exception e) {
- debug("profile", String.format("Returning default value for option %s", name), e);
- longResult = default_value;
- }
- }
-
- return longResult;
- }
- public void setOption(String name, String value) {
- debug("profile", String.format("setting option %s=%s", name, value));
- DbOpQueue.add("insert or replace into options(profile_id, name, value) values(?, ?, ?);",
- new String[]{String.valueOf(id), name, value});
- }
- public void setLongOption(String name, long value) {
- setOption(name, String.valueOf(value));
- }
- public void removeFromDB() {
- ProfileDAO dao = DB.get()
- .getProfileDAO();
- AsyncTask.execute(() -> dao.deleteSync(dao.getByIdSync(id)));
- }
- public LedgerTransaction loadTransaction(int transactionId) {
- LedgerTransaction tr = new LedgerTransaction(transactionId, this.id);
- tr.loadData(App.getDatabase());
-
- return tr;
- }
- public int getThemeHue() {
-// debug("profile", String.format("Profile.getThemeHue() returning %d", themeHue));
- return this.themeHue;
- }
- public void setThemeHue(Object o) {
- setThemeId(Integer.parseInt(String.valueOf(o)));
- }
- public void setThemeId(int themeHue) {
-// debug("profile", String.format("Profile.setThemeHue(%d) called", themeHue));
- this.themeHue = themeHue;
- }
- public int getNextTransactionsGeneration(SQLiteDatabase db) {
- try (Cursor c = db.rawQuery(
- "SELECT generation FROM transactions WHERE profile_id=? LIMIT 1",
- new String[]{String.valueOf(id)}))
- {
- if (c.moveToFirst())
- return c.getInt(0) + 1;
- }
- return 1;
- }
- private void deleteNotPresentTransactions(SQLiteDatabase db, int generation) {
- Logger.debug("db/benchmark", "Deleting obsolete transactions");
- db.execSQL(
- "DELETE FROM transaction_accounts WHERE (select t.profile_id from transactions t " +
- "where t.id=transaction_accounts.transaction_id)=? AND generation" + " <> ?",
- new Object[]{id, generation});
- db.execSQL("DELETE FROM transactions WHERE profile_id=? AND generation <> ?",
- new Object[]{id, generation});
- Logger.debug("db/benchmark", "Done deleting obsolete transactions");
- }
- @Transaction
- public void wipeAllDataSync() {
- OptionDAO optDao = DB.get()
- .getOptionDAO();
- optDao.deleteSync(optDao.allForProfileSync(id));
-
- AccountDAO accDao = DB.get()
- .getAccountDAO();
- accDao.deleteSync(accDao.allForProfileSync(id));
-
- TransactionDAO trnDao = DB.get()
- .getTransactionDAO();
- trnDao.deleteSync(trnDao.allForProfileSync(id));
-
- DescriptionHistoryDAO descDao = DB.get()
- .getDescriptionHistoryDAO();
- descDao.sweepSync();
- }
- public void wipeAllData() {
- AsyncTask.execute(this::wipeAllDataSync);
- }
- public List<Currency> getCurrencies() {
- SQLiteDatabase db = App.getDatabase();
-
- ArrayList<Currency> result = new ArrayList<>();
-
- try (Cursor c = db.rawQuery("SELECT c.id, c.name, c.position, c.has_gap FROM currencies c",
- new String[]{}))
- {
- while (c.moveToNext()) {
- Currency currency = new Currency(c.getInt(0), c.getString(1),
- Currency.Position.valueOf(c.getString(2)), c.getInt(3) == 1);
- result.add(currency);
- }
- }
-
- return result;
- }
- Currency loadCurrencyByName(String name) {
- SQLiteDatabase db = App.getDatabase();
- Currency result = tryLoadCurrencyByName(db, name);
- if (result == null)
- throw new RuntimeException(String.format("Unable to load currency '%s'", name));
- return result;
- }
- private Currency tryLoadCurrencyByName(SQLiteDatabase db, String name) {
- try (Cursor cursor = db.rawQuery(
- "SELECT c.id, c.name, c.position, c.has_gap FROM currencies c WHERE c.name=?",
- new String[]{name}))
- {
- if (cursor.moveToFirst()) {
- return new Currency(cursor.getInt(0), cursor.getString(1),
- Currency.Position.valueOf(cursor.getString(2)), cursor.getInt(3) == 1);
- }
- return null;
- }
- }
- public void storeAccountAndTransactionListAsync(List<LedgerAccount> accounts,
- List<LedgerTransaction> transactions) {
- if (accountAndTransactionListSaver != null)
- accountAndTransactionListSaver.interrupt();
-
- accountAndTransactionListSaver =
- new AccountAndTransactionListSaver(this, accounts, transactions);
- accountAndTransactionListSaver.start();
- }
- private Currency tryLoadCurrencyById(SQLiteDatabase db, int id) {
- try (Cursor cursor = db.rawQuery(
- "SELECT c.id, c.name, c.position, c.has_gap FROM currencies c WHERE c.id=?",
- new String[]{String.valueOf(id)}))
- {
- if (cursor.moveToFirst()) {
- return new Currency(cursor.getInt(0), cursor.getString(1),
- Currency.Position.valueOf(cursor.getString(2)), cursor.getInt(3) == 1);
- }
- return null;
- }
- }
- public Currency loadCurrencyById(int id) {
- SQLiteDatabase db = App.getDatabase();
- Currency result = tryLoadCurrencyById(db, id);
- if (result == null)
- throw new RuntimeException(String.format("Unable to load currency with id '%d'", id));
- return result;
- }
-
- public enum FutureDates {
- None(0), OneWeek(7), TwoWeeks(14), OneMonth(30), TwoMonths(60), ThreeMonths(90),
- SixMonths(180), OneYear(365), All(-1);
- private static final SparseArray<FutureDates> map = new SparseArray<>();
-
- static {
- for (FutureDates item : FutureDates.values()) {
- map.put(item.value, item);
- }
- }
-
- private final int value;
- FutureDates(int value) {
- this.value = value;
- }
- public static FutureDates valueOf(int i) {
- return map.get(i, None);
- }
- public int toInt() {
- return this.value;
- }
- public String getText(Resources resources) {
- switch (value) {
- case 7:
- return resources.getString(R.string.future_dates_7);
- case 14:
- return resources.getString(R.string.future_dates_14);
- case 30:
- return resources.getString(R.string.future_dates_30);
- case 60:
- return resources.getString(R.string.future_dates_60);
- case 90:
- return resources.getString(R.string.future_dates_90);
- case 180:
- return resources.getString(R.string.future_dates_180);
- case 365:
- return resources.getString(R.string.future_dates_365);
- case -1:
- return resources.getString(R.string.future_dates_all);
- default:
- return resources.getString(R.string.future_dates_none);
- }
- }
- }
-
- private static class AccountAndTransactionListSaver extends Thread {
- private final MobileLedgerProfile profile;
- private final List<LedgerAccount> accounts;
- private final List<LedgerTransaction> transactions;
- AccountAndTransactionListSaver(MobileLedgerProfile profile, List<LedgerAccount> accounts,
- List<LedgerTransaction> transactions) {
- this.accounts = accounts;
- this.transactions = transactions;
- this.profile = profile;
- }
- public int getNextDescriptionsGeneration(SQLiteDatabase db) {
- int generation = 1;
- try (Cursor c = db.rawQuery("SELECT generation FROM description_history LIMIT 1",
- null))
- {
- if (c.moveToFirst()) {
- generation = c.getInt(0) + 1;
- }
- }
- return generation;
- }
- void deleteNotPresentDescriptions(SQLiteDatabase db, int generation) {
- Logger.debug("db/benchmark", "Deleting obsolete descriptions");
- db.execSQL("DELETE FROM description_history WHERE generation <> ?",
- new Object[]{generation});
- db.execSQL("DELETE FROM description_history WHERE generation <> ?",
- new Object[]{generation});
- Logger.debug("db/benchmark", "Done deleting obsolete descriptions");
- }
- @Override
- public void run() {
- SQLiteDatabase db = App.getDatabase();
- db.beginTransactionNonExclusive();
- try {
- int transactionsGeneration = profile.getNextTransactionsGeneration(db);
- if (isInterrupted())
- return;
-
- for (LedgerTransaction tr : transactions) {
- profile.storeTransaction(db, transactionsGeneration, tr);
- if (isInterrupted())
- return;
- }
-
- profile.deleteNotPresentTransactions(db, transactionsGeneration);
- if (isInterrupted()) {
- return;
- }
-
- Map<String, Boolean> unique = new HashMap<>();
-
- debug("descriptions", "Starting refresh");
- int descriptionsGeneration = getNextDescriptionsGeneration(db);
- try (Cursor c = db.rawQuery("SELECT distinct description from transactions",
- null))
- {
- while (c.moveToNext()) {
- String description = c.getString(0);
- String descriptionUpper = description.toUpperCase();
- if (unique.containsKey(descriptionUpper))
- continue;
-
- storeDescription(db, descriptionsGeneration, description, descriptionUpper);
-
- unique.put(descriptionUpper, true);
- }
- }
- deleteNotPresentDescriptions(db, descriptionsGeneration);
-
- db.setTransactionSuccessful();
- }
- finally {
- db.endTransaction();
- }
-
- AsyncTask.execute(() -> {
- List<AccountWithAmounts> list = new ArrayList<>();
-
- final AccountDAO dao = DB.get()
- .getAccountDAO();
-
- for (LedgerAccount acc : accounts) {
- AccountWithAmounts rec = new AccountWithAmounts();
- rec.account = acc.toDBO();
-
- if (isInterrupted())
- return;
-
- rec.amounts = new ArrayList<>();
- for (LedgerAmount amt : acc.getAmounts()) {
- AccountValue av = new AccountValue();
- av.setCurrency(amt.getCurrency());
- av.setValue(amt.getAmount());
-
- rec.amounts.add(av);
- }
-
- list.add(rec);
- }
-
- if (isInterrupted())
- return;
-
- dao.storeAccountsSync(list, profile.getId());
- });
- }
- private void storeDescription(SQLiteDatabase db, int generation, String description,
- String descriptionUpper) {
- db.execSQL("UPDATE description_history SET description=?, generation=? WHERE " +
- "description_upper=?", new Object[]{description, generation, descriptionUpper
- });
- db.execSQL(
- "INSERT INTO description_history(description, description_upper, generation) " +
- "select ?,?,? WHERE (select changes() = 0)",
- new Object[]{description, descriptionUpper, generation
- });
- }
- }
-}
acc.setAccountCommentMatchGroup(pa.getAccountCommentMatchGroup());
if (pa.getCurrencyMatchGroup() == null) {
- final Integer currencyId = pa.getCurrency();
- if (currencyId != null && currencyId > 0)
- acc.setCurrency(Currency.loadById(currencyId));
+ acc.setCurrency(pa.getCurrencyObject());
}
else
acc.setCurrencyMatchGroup(pa.getCurrencyMatchGroup());
PossiblyMatchedValue.withLiteralString("");
private final PossiblyMatchedValue<Float> amount =
PossiblyMatchedValue.withLiteralFloat(null);
- private final PossiblyMatchedValue<Currency> currency = new PossiblyMatchedValue<>();
+ private final PossiblyMatchedValue<net.ktnx.mobileledger.db.Currency> currency =
+ new PossiblyMatchedValue<>();
private boolean negateAmount;
public AccountRow() {
super(Type.ACCOUNT_ITEM);
public void setCurrencyMatchGroup(int group) {
currency.setMatchGroup(group);
}
- public Currency getCurrency() {
+ public net.ktnx.mobileledger.db.Currency getCurrency() {
return currency.getValue();
}
- public void setCurrency(Currency currency) {
+ public void setCurrency(net.ktnx.mobileledger.db.Currency currency) {
this.currency.setValue(currency);
}
public int getAccountNameMatchGroup() {
import com.google.android.material.switchmaterial.SwitchMaterial;
-import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.dao.CurrencyDAO;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Currency;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
/**
* A fragment representing a list of Items.
model = new ViewModelProvider(this).get(CurrencySelectorModel.class);
if (onCurrencySelectedListener != null)
model.setOnCurrencySelectedListener(onCurrencySelectedListener);
- MobileLedgerProfile profile = Objects.requireNonNull(Data.getProfile());
+ Profile profile = Data.getProfile();
- model.currencies.setValue(new CopyOnWriteArrayList<>(profile.getCurrencies()));
CurrencySelectorRecyclerViewAdapter adapter = new CurrencySelectorRecyclerViewAdapter();
- model.currencies.observe(this, adapter::submitList);
+ DB.get()
+ .getCurrencyDAO()
+ .getAll()
+ .observe(this, list -> {
+ List<String> strings = new ArrayList<>();
+ for (net.ktnx.mobileledger.db.Currency c : list) {
+ strings.add(c.getName());
+ }
+ adapter.submitList(strings);
+ });
recyclerView.setAdapter(adapter);
adapter.setCurrencySelectedListener(this);
String currName = String.valueOf(tvNewCurrName.getText());
if (!currName.isEmpty()) {
- List<Currency> list = new ArrayList<>(model.currencies.getValue());
+ DB.get()
+ .getCurrencyDAO()
+ .insert(new net.ktnx.mobileledger.db.Currency(null,
+ String.valueOf(tvNewCurrName.getText()), "after", false), null);
// FIXME hardcoded position and gap setting
- list.add(new Currency(profile, String.valueOf(tvNewCurrName.getText()),
- Currency.Position.after, false));
- model.currencies.setValue(list);
}
tvNewCurrName.setVisibility(View.GONE);
model.resetOnCurrencySelectedListener();
}
@Override
- public void onCurrencySelected(Currency item) {
+ public void onCurrencySelected(String item) {
model.triggerOnCurrencySelectedListener(item);
dismiss();
}
@Override
- public void onCurrencyLongClick(Currency item) {
- ArrayList<Currency> list = new ArrayList<>(model.currencies.getValue());
- App.getDatabase()
- .execSQL("delete from currencies where id=?", new Object[]{item.getId()});
- list.remove(item);
- model.currencies.setValue(list);
+ public void onCurrencyLongClick(String item) {
+ CurrencyDAO dao = DB.get()
+ .getCurrencyDAO();
+ dao.getByName(item)
+ .observe(this, dao::deleteSync);
}
public void showPositionAndPadding() {
deferredShowPositionAndPadding = true;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
-import net.ktnx.mobileledger.model.Currency;
-
-import java.util.ArrayList;
-import java.util.List;
-
public class CurrencySelectorModel extends ViewModel {
- public final MutableLiveData<List<Currency>> currencies;
private final MutableLiveData<Boolean> positionAndPaddingVisible = new MutableLiveData<>(true);
private OnCurrencySelectedListener selectionListener;
- public CurrencySelectorModel() {
- this.currencies = new MutableLiveData<>(new ArrayList<>());
- }
+ public CurrencySelectorModel() { }
public void showPositionAndPadding() {
positionAndPaddingVisible.postValue(true);
}
void resetOnCurrencySelectedListener() {
selectionListener = null;
}
- void triggerOnCurrencySelectedListener(Currency c) {
+ void triggerOnCurrencySelectedListener(String c) {
if (selectionListener != null)
selectionListener.onCurrencySelected(c);
}
import android.view.ViewGroup;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
* specified {@link OnCurrencySelectedListener}.
*/
public class CurrencySelectorRecyclerViewAdapter
- extends ListAdapter<Currency, CurrencySelectorRecyclerViewAdapter.ViewHolder> {
+ extends ListAdapter<String, CurrencySelectorRecyclerViewAdapter.ViewHolder> {
+ private static final DiffUtil.ItemCallback<String> DIFF_CALLBACK =
+ new DiffUtil.ItemCallback<String>() {
+ @Override
+ public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
+ return oldItem.equals(newItem);
+ }
+ @Override
+ public boolean areContentsTheSame(@NonNull String oldItem,
+ @NonNull String newItem) {
+ return true;
+ }
+ };
private OnCurrencySelectedListener currencySelectedListener;
private OnCurrencyLongClickListener currencyLongClickListener;
public CurrencySelectorRecyclerViewAdapter() {
- super(Currency.DIFF_CALLBACK);
+ super(DIFF_CALLBACK);
}
@NotNull
@Override
public void resetCurrencySelectedListener() {
currencySelectedListener = null;
}
- public void notifyCurrencySelected(Currency currency) {
+ public void notifyCurrencySelected(String currency) {
if (null != currencySelectedListener)
currencySelectedListener.onCurrencySelected(currency);
}
this.currencyLongClickListener = listener;
}
public void resetCurrencyLockClickListener() { currencyLongClickListener = null; }
- private void notifyCurrencyLongClicked(Currency mItem) {
+ private void notifyCurrencyLongClicked(String mItem) {
if (null != currencyLongClickListener)
currencyLongClickListener.onCurrencyLongClick(mItem);
}
public class ViewHolder extends RecyclerView.ViewHolder {
private final TextView mNameView;
- private Currency mItem;
+ private String mItem;
ViewHolder(View view) {
super(view);
public String toString() {
return super.toString() + " '" + mNameView.getText() + "'";
}
- void bindTo(Currency item) {
+ void bindTo(String item) {
mItem = item;
- mNameView.setText(item.getName());
+ mNameView.setText(item);
}
}
}
import androidx.appcompat.app.AppCompatDialogFragment;
import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.model.FutureDates;
import net.ktnx.mobileledger.utils.SimpleDate;
import java.util.Calendar;
else
this.maxDate = maxDate.toDate().getTime();
}
- public void setFutureDates(MobileLedgerProfile.FutureDates futureDates) {
- if (futureDates == MobileLedgerProfile.FutureDates.All) {
+ public void setFutureDates(FutureDates futureDates) {
+ if (futureDates == FutureDates.All) {
maxDate = Long.MAX_VALUE;
}
else {
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.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.List;
import java.util.Locale;
-import static net.ktnx.mobileledger.utils.Logger.debug;
-
public class MainModel extends ViewModel {
public final MutableLiveData<Integer> foundTransactionItemIndex = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> updatingFlag = new MutableLiveData<>(false);
private final MutableLiveData<String> accountFilter = new MutableLiveData<>();
private final MutableLiveData<List<TransactionListItem>> displayedTransactions =
new MutableLiveData<>(new ArrayList<>());
- private final MutableLiveData<List<AccountListItem>> displayedAccounts =
- new MutableLiveData<>();
- private final Locker accountsLocker = new Locker();
private final MutableLiveData<String> updateError = new MutableLiveData<>();
- private MobileLedgerProfile profile;
- private final List<LedgerAccount> allAccounts = new ArrayList<>();
private SimpleDate firstTransactionDate;
private SimpleDate lastTransactionDate;
transient private RetrieveTransactionsTask retrieveTransactionsTask;
transient private Thread displayedAccountsUpdater;
private TransactionsDisplayedFilter displayedTransactionsUpdater;
- 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);
public LiveData<String> getUpdateError() {
return updateError;
}
- public void setProfile(MobileLedgerProfile profile) {
- stopTransactionsRetrieval();
- this.profile = profile;
- }
public LiveData<List<TransactionListItem>> getDisplayedTransactions() {
return displayedTransactions;
}
- public void setDisplayedTransactions(List<TransactionListItem> list, int transactionCount) {
+ public void setDisplayedTransactions(List<TransactionListItem> list) {
displayedTransactions.postValue(list);
- Data.lastUpdateTransactionCount.postValue(transactionCount);
+ Data.lastUpdateTransactionCount.postValue(list.size());
}
public SimpleDate getFirstTransactionDate() {
return firstTransactionDate;
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);
public void transactionRetrievalDone() {
retrieveTransactionsTask = null;
}
- public synchronized Locker lockAccountsForWriting() {
- accountsLocker.lockForWriting();
- return accountsLocker;
- }
- public LiveData<List<AccountListItem>> getDisplayedAccounts() {
- return displayedAccounts;
- }
- public synchronized void setAndStoreAccountAndTransactionListFromWeb(
- List<LedgerAccount> accounts, List<LedgerTransaction> transactions) {
- profile.storeAccountAndTransactionListAsync(accounts, transactions);
-
- setLastUpdateStamp(transactions.size());
-
- updateDisplayedTransactionsFromWeb(transactions);
- }
synchronized public void updateDisplayedTransactionsFromWeb(List<LedgerTransaction> list) {
if (displayedTransactionsUpdater != null) {
displayedTransactionsUpdater.interrupt();
public void clearUpdateError() {
updateError.postValue(null);
}
- public void clearAccounts() { displayedAccounts.postValue(new ArrayList<>()); }
public void clearTransactions() {
displayedTransactions.setValue(new ArrayList<>());
}
package net.ktnx.mobileledger.ui;
-import net.ktnx.mobileledger.model.Currency;
-
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnCurrencyLongClickListener {
- void onCurrencyLongClick(Currency item);
+ void onCurrencyLongClick(String item);
}
package net.ktnx.mobileledger.ui;
-import net.ktnx.mobileledger.model.Currency;
-
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnCurrencySelectedListener {
- void onCurrencySelected(Currency item);
+ void onCurrencySelected(String item);
}
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.db.AccountWithAmounts;
import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.AccountListItem;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerAccount;
model.scheduleTransactionListRetrieval();
});
+ Data.observeProfile(this, this::onProfileChanged);
+ }
+ private void onProfileChanged(Profile profile) {
DB.get()
.getAccountDAO()
- .getAllWithAmounts(Data.getProfile()
- .getId())
+ .getAllWithAmounts(profile.getId())
.observe(getViewLifecycleOwner(), list -> AsyncTask.execute(() -> {
List<AccountListItem> adapterList = new ArrayList<>();
adapterList.add(new AccountListItem.Header(Data.lastAccountsUpdateText));
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.core.view.GravityCompat;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
import net.ktnx.mobileledger.databinding.ActivityMainBinding;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.account_summary.AccountSummaryFragment;
import net.ktnx.mobileledger.ui.new_transaction.NewTransactionActivity;
+import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
import net.ktnx.mobileledger.ui.profiles.ProfilesRecyclerViewAdapter;
import net.ktnx.mobileledger.ui.templates.TemplatesActivity;
import net.ktnx.mobileledger.ui.transaction_list.TransactionListFragment;
private DrawerLayout.SimpleDrawerListener drawerListener;
private ActionBarDrawerToggle barDrawerToggle;
private ViewPager2.OnPageChangeCallback pageChangeCallback;
- private MobileLedgerProfile profile;
+ private Profile profile;
private MainModel mainModel;
private ActivityMainBinding b;
private int fabVerticalOffset;
Data.observeProfile(this, this::onProfileChanged);
Data.profiles.observe(this, this::onProfileListChanged);
+
Data.backgroundTaskProgress.observe(this, this::onRetrieveProgress);
Data.backgroundTasksRunning.observe(this, this::onRetrieveRunningChanged);
.setValue(savedInstanceState.getString(STATE_ACC_FILTER, null));
}
- b.btnNoProfilesAdd.setOnClickListener(
- v -> MobileLedgerProfile.startEditProfileActivity(this, null));
+ b.btnNoProfilesAdd.setOnClickListener(v -> ProfileDetailActivity.start(this, null));
b.btnAddTransaction.setOnClickListener(this::fabNewTransactionClicked);
- b.navNewProfileButton.setOnClickListener(
- v -> MobileLedgerProfile.startEditProfileActivity(this, null));
+ b.navNewProfileButton.setOnClickListener(v -> ProfileDetailActivity.start(this, null));
b.transactionListCancelDownload.setOnClickListener(this::onStopTransactionRefreshClick);
mainModel.scheduleTransactionListRetrieval();
}
}
- private void createShortcuts(List<MobileLedgerProfile> list) {
+ private void createShortcuts(List<Profile> list) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1)
return;
ShortcutManager sm = getSystemService(ShortcutManager.class);
List<ShortcutInfo> shortcuts = new ArrayList<>();
int i = 0;
- for (MobileLedgerProfile p : list) {
+ for (Profile p : list) {
if (shortcuts.size() >= sm.getMaxShortcutCountPerActivity())
break;
- if (!p.isPostingPermitted())
+ if (!p.permitPosting())
continue;
final ShortcutInfo.Builder builder =
ProfileThemedActivity.PARAM_PROFILE_ID, p.getId())
.putExtra(
ProfileThemedActivity.PARAM_THEME,
- p.getThemeHue()))
+ p.getTheme()))
.setRank(i)
.build();
shortcuts.add(si);
}
sm.setDynamicShortcuts(shortcuts);
}
- private void onProfileListChanged(List<MobileLedgerProfile> newList) {
+ private void onProfileListChanged(List<Profile> newList) {
if ((newList == null) || newList.isEmpty()) {
b.noProfilesLayout.setVisibility(View.VISIBLE);
b.mainAppLayout.setVisibility(View.GONE);
(int) (getResources().getDimension(R.dimen.thumb_row_height) * newList.size()));
Logger.debug("profiles", "profile list changed");
- mProfileListAdapter.notifyDataSetChanged();
+ mProfileListAdapter.setProfileList(newList);
createShortcuts(newList);
}
/**
* called when the current profile has changed
*/
- private void onProfileChanged(MobileLedgerProfile profile) {
+ private void onProfileChanged(@Nullable Profile newProfile) {
if (this.profile == null) {
- if (profile == null)
+ if (newProfile == null)
return;
}
else {
- if (this.profile.equals(profile))
+ if (this.profile.equals(newProfile))
return;
}
- boolean haveProfile = profile != null;
+ boolean haveProfile = newProfile != null;
if (haveProfile)
- setTitle(profile.getName());
+ setTitle(newProfile.getName());
else
setTitle(R.string.app_name);
- mainModel.setProfile(profile);
-
- this.profile = profile;
-
- int newProfileTheme = haveProfile ? profile.getThemeHue() : -1;
+ int newProfileTheme = haveProfile ? newProfile.getTheme() : -1;
if (newProfileTheme != Colors.profileThemeId) {
Logger.debug("profiles",
String.format(Locale.ENGLISH, "profile theme %d → %d", Colors.profileThemeId,
return;
}
+ final boolean sameProfileId = (newProfile != null) && (this.profile != null) &&
+ this.profile.getId() == newProfile.getId();
+
+ this.profile = newProfile;
+
b.noProfilesLayout.setVisibility(haveProfile ? View.GONE : View.VISIBLE);
b.pagerLayout.setVisibility(haveProfile ? View.VISIBLE : View.VISIBLE);
mProfileListAdapter.notifyDataSetChanged();
- mainModel.clearAccounts();
- mainModel.clearTransactions();
-
if (haveProfile) {
- Logger.debug("transactions", "requesting list reload");
- mainModel.scheduleTransactionListReload();
-
- if (profile.isPostingPermitted()) {
+ if (newProfile.permitPosting()) {
b.toolbar.setSubtitle(null);
b.btnAddTransaction.show();
}
}
updateLastUpdateTextFromDB();
+
+ if (sameProfileId) {
+ Logger.debug(TAG, String.format(Locale.ROOT, "Short-cut profile 'changed' to %d",
+ newProfile.getId()));
+ return;
+ }
+
+ mainModel.stopTransactionsRetrieval();
+
+ mainModel.clearTransactions();
+
+ if (haveProfile) {
+ Logger.debug("transactions", "requesting list reload");
+ mainModel.scheduleTransactionListReload();
+ }
}
private void profileThemeChanged() {
// un-hook all observed LiveData
public void fabNewTransactionClicked(View view) {
Intent intent = new Intent(this, NewTransactionActivity.class);
intent.putExtra(ProfileThemedActivity.PARAM_PROFILE_ID, profile.getId());
- intent.putExtra(ProfileThemedActivity.PARAM_THEME, profile.getThemeHue());
+ intent.putExtra(ProfileThemedActivity.PARAM_THEME, profile.getTheme());
startActivity(intent);
overridePendingTransition(R.anim.slide_in_up, R.anim.dummy);
}
if (profile == null)
return;
- long lastUpdate = profile.getLongOption(MLDB.OPT_LAST_SCRAPE, 0L);
-
- Logger.debug("transactions", String.format(Locale.ENGLISH, "Last update = %d", lastUpdate));
- if (lastUpdate == 0) {
- Data.lastUpdateDate.postValue(null);
- }
- else {
- Data.lastUpdateDate.postValue(new Date(lastUpdate));
- }
-
- scheduleDataRetrievalIfStale(lastUpdate);
-
+ DB.get()
+ .getOptionDAO()
+ .load(profile.getId(), MLDB.OPT_LAST_SCRAPE)
+ .observe(this, opt -> {
+ long lastUpdate = 0;
+ if (opt != null) {
+ try {
+ lastUpdate = Long.parseLong(opt.getValue());
+ }
+ catch (NumberFormatException ex) {
+ Logger.debug(TAG, String.format("Error parsing '%s' as long", opt.getValue()),
+ ex);
+ }
+ }
+
+ if (lastUpdate == 0) {
+ Data.lastUpdateDate.postValue(null);
+ }
+ else {
+ Data.lastUpdateDate.postValue(new Date(lastUpdate));
+ }
+
+ scheduleDataRetrievalIfStale(lastUpdate);
+ });
}
private void refreshLastUpdateInfo() {
final int formatFlags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
builder.setMessage(error);
builder.setPositiveButton(R.string.btn_profile_options, (dialog, which) -> {
Logger.debug("error", "will start profile editor");
- MobileLedgerProfile.startEditProfileActivity(this, profile);
+ ProfileDetailActivity.start(this, profile);
});
builder.create()
.show();
}
}
public void fabShouldShow() {
- if ((profile != null) && profile.isPostingPermitted() && !b.drawerLayout.isOpen())
+ if ((profile != null) && profile.permitPosting() && !b.drawerLayout.isOpen())
fabManager.showFab();
}
@Override
import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.utils.Colors;
import net.ktnx.mobileledger.utils.Logger;
public static final String TAG = "prf-thm-act";
protected static final String PARAM_PROFILE_ID = "profile-id";
protected static final String PARAM_THEME = "theme";
- protected MobileLedgerProfile mProfile;
+ protected Profile mProfile;
private boolean themeSetUp = false;
private boolean mIgnoreProfileChange;
private int mThemeHue;
}
mProfile = profile;
- int hue = profile.getThemeHue();
+ int hue = profile.getTheme();
if (hue != mThemeHue) {
storeProfilePref(profile);
super.onCreate(savedInstanceState);
}
- public void storeProfilePref(MobileLedgerProfile profile) {
- App.storeStartupProfileAndTheme(profile.getId(), profile.getThemeHue());
+ public void storeProfilePref(Profile profile) {
+ App.storeStartupProfileAndTheme(profile.getId(), profile.getTheme());
}
protected void initProfile() {
long profileId = App.getStartupProfile();
profile = dao.getAnySync();
}
- Data.postCurrentProfile(MobileLedgerProfile.fromDBO(profile));
+ Data.postCurrentProfile(profile);
}
}
import androidx.annotation.Nullable;
import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.MobileLedgerDatabase;
private static class DatabaseInitTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
- MobileLedgerProfile.loadAllFromDB(0);
+ long ignored = DB.get().getProfileDAO().getProfileCountSync();
return null;
}
b.currencyButton.setOnClickListener(v -> {
CurrencySelectorFragment cpf = new CurrencySelectorFragment();
cpf.showPositionAndPadding();
- cpf.setOnCurrencySelectedListener(c -> adapter.setItemCurrency(getAdapterPosition(),
- (c == null) ? null : c.getName()));
+ cpf.setOnCurrencySelectedListener(
+ c -> adapter.setItemCurrency(getAdapterPosition(), c));
cpf.show(activity.getSupportFragmentManager(), "currency-selector");
});
import android.content.Context;
import android.content.Intent;
import android.database.AbstractCursor;
-import android.database.Cursor;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.ParcelFormatException;
-import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.activity.result.ActivityResultLauncher;
-import androidx.annotation.NonNull;
import androidx.core.view.MenuCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.snackbar.Snackbar;
import net.ktnx.mobileledger.BuildConfig;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.DescriptionSelectedCallback;
import net.ktnx.mobileledger.async.SendTransactionTask;
import net.ktnx.mobileledger.async.TaskCallback;
+import net.ktnx.mobileledger.dao.TransactionDAO;
import net.ktnx.mobileledger.databinding.ActivityNewTransactionBinding;
import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.db.TemplateHeader;
+import net.ktnx.mobileledger.db.TransactionWithAccounts;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.MatchedTemplate;
import net.ktnx.mobileledger.ui.activity.ProfileThemedActivity;
import net.ktnx.mobileledger.ui.templates.TemplatesActivity;
import net.ktnx.mobileledger.utils.Logger;
-import net.ktnx.mobileledger.utils.MLDB;
import net.ktnx.mobileledger.utils.Misc;
import java.util.ArrayList;
if (!model.accountListIsEmpty())
return;
- String accFilter = mProfile.getPreferredAccountsFilter();
+ AsyncTask.execute(() -> {
+ String accFilter = mProfile.getPreferredAccountsFilter();
- ArrayList<String> params = new ArrayList<>();
- StringBuilder sb = new StringBuilder("select t.profile, t.id from transactions t");
+ TransactionDAO trDao = DB.get()
+ .getTransactionDAO();
- if (!TextUtils.isEmpty(accFilter)) {
- sb.append(" JOIN transaction_accounts ta")
- .append(" ON ta.profile = t.profile")
- .append(" AND ta.transaction_id = t.id");
- }
-
- sb.append(" WHERE t.description=?");
- params.add(description);
-
- if (!TextUtils.isEmpty(accFilter)) {
- sb.append(" AND ta.account_name LIKE '%'||?||'%'");
- params.add(accFilter);
- }
+ TransactionWithAccounts tr;
- sb.append(" ORDER BY t.year desc, t.month desc, t.day desc LIMIT 1");
-
- final String sql = sb.toString();
- debug("description", sql);
- debug("description", params.toString());
-
- // FIXME: handle exceptions?
- MLDB.queryInBackground(sql, params.toArray(new String[]{}), new MLDB.CallbackHelper() {
- @Override
- public void onStart() {
- model.incrementBusyCounter();
- }
- @Override
- public void onDone() {
- model.decrementBusyCounter();
- }
- @Override
- public boolean onRow(@NonNull Cursor cursor) {
- final long profileId = cursor.getLong(0);
- final int transactionId = cursor.getInt(1);
- runOnUiThread(() -> model.loadTransactionIntoModel(profileId, transactionId));
- return false; // limit 1, by the way
- }
- @Override
- public void onNoRows() {
- if (TextUtils.isEmpty(accFilter))
+ if (Misc.emptyIsNull(accFilter) != null) {
+ tr = trDao.getFirstByDescriptionHavingAccountSync(description, accFilter);
+ if (tr != null) {
+ model.loadTransactionIntoModel(tr);
return;
-
- debug("description", "Trying transaction search without preferred account filter");
-
- final String broaderSql =
- "select t.profile, t.id from transactions t where t.description=?" +
- " ORDER BY year desc, month desc, day desc LIMIT 1";
- params.remove(1);
- debug("description", broaderSql);
- debug("description", description);
-
- runOnUiThread(() -> Snackbar.make(b.newTransactionNav,
- R.string.ignoring_preferred_account, Snackbar.LENGTH_INDEFINITE)
- .show());
-
- MLDB.queryInBackground(broaderSql, new String[]{description},
- new MLDB.CallbackHelper() {
- @Override
- public void onStart() {
- model.incrementBusyCounter();
- }
- @Override
- public boolean onRow(@NonNull Cursor cursor) {
- final long profileId = cursor.getLong(0);
- final int transactionId = cursor.getInt(1);
- runOnUiThread(() -> model.loadTransactionIntoModel(profileId,
- transactionId));
- return false;
- }
- @Override
- public void onDone() {
- model.decrementBusyCounter();
- }
- });
+ }
}
+
+ tr = trDao.getFirstByDescriptionSync(description);
+ if (tr != null)
+ model.loadTransactionIntoModel(tr);
});
}
private void onFabPressed() {
import com.google.android.material.snackbar.Snackbar;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.json.API;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerTransaction;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.QR;
+import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
import net.ktnx.mobileledger.utils.Logger;
import org.jetbrains.annotations.NotNull;
private NewTransactionItemsAdapter listAdapter;
private NewTransactionModel viewModel;
private OnNewTransactionFragmentInteractionListener mListener;
- private MobileLedgerProfile mProfile;
+ private Profile mProfile;
public NewTransactionFragment() {
// Required empty public constructor
setHasOptionsMenu(true);
.append("\n\n")
.append(error)
.append("\n\n");
- if (mProfile.getApiVersion()
- .equals(API.auto))
+ if (API.valueOf(mProfile.getApiVersion())
+ .equals(API.auto))
message.append(
resources.getString(R.string.err_json_send_error_unsupported));
else {
message.append(resources.getString(R.string.err_json_send_error_tail));
builder.setPositiveButton(R.string.btn_profile_options, (dialog, which) -> {
Logger.debug("error", "will start profile editor");
- MobileLedgerProfile.startEditProfileActivity(context, mProfile);
+ ProfileDetailActivity.start(context, mProfile);
});
}
builder.setMessage(message);
import net.ktnx.mobileledger.databinding.NewTransactionHeaderRowBinding;
import net.ktnx.mobileledger.db.TransactionDescriptionAutocompleteAdapter;
import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.model.FutureDates;
import net.ktnx.mobileledger.ui.DatePickerFragment;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.Misc;
}
private void pickTransactionDate() {
DatePickerFragment picker = new DatePickerFragment();
- picker.setFutureDates(mProfile.getFutureDates());
+ picker.setFutureDates(FutureDates.valueOf(mProfile.getFutureDates()));
picker.setOnDatePickedListener(this);
picker.setCurrentDateFromText(b.newTransactionDate.getText());
picker.show(((NewTransactionActivity) b.getRoot()
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
abstract class NewTransactionItemViewHolder extends RecyclerView.ViewHolder {
final NewTransactionItemsAdapter mAdapter;
- final MobileLedgerProfile mProfile;
+ final Profile mProfile;
public NewTransactionItemViewHolder(@NonNull View itemView,
NewTransactionItemsAdapter adapter) {
super(itemView);
import net.ktnx.mobileledger.databinding.NewTransactionAccountRowBinding;
import net.ktnx.mobileledger.databinding.NewTransactionHeaderRowBinding;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.utils.Logger;
import java.util.List;
return oldItem.equalContents(newItem);
}
});
- private MobileLedgerProfile mProfile;
+ private Profile mProfile;
private int checkHoldCounter = 0;
- NewTransactionItemsAdapter(NewTransactionModel viewModel, MobileLedgerProfile profile) {
+ NewTransactionItemsAdapter(NewTransactionModel viewModel, Profile profile) {
super();
setHasStableIds(true);
model = viewModel;
.get(position)
.getId();
}
- public void setProfile(MobileLedgerProfile profile) {
+ public void setProfile(Profile profile) {
mProfile = profile;
}
@NonNull
import net.ktnx.mobileledger.BuildConfig;
import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.db.TemplateAccount;
import net.ktnx.mobileledger.db.TemplateHeader;
+import net.ktnx.mobileledger.db.TransactionWithAccounts;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.InertMutableLiveData;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
import net.ktnx.mobileledger.model.MatchedTemplate;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.utils.Globals;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.Misc;
private final MutableLiveData<Boolean> simulateSave = new InertMutableLiveData<>(false);
private final AtomicInteger busyCounter = new AtomicInteger(0);
private final MutableLiveData<Boolean> busyFlag = new InertMutableLiveData<>(false);
- private final Observer<MobileLedgerProfile> profileObserver = profile -> {
+ private final Observer<Profile> profileObserver = profile -> {
showCurrency.postValue(profile.getShowCommodityByDefault());
showComments.postValue(profile.getShowCommentsByDefault());
};
return tr;
}
- void loadTransactionIntoModel(long profileId, int transactionId) {
+ void loadTransactionIntoModel(@NonNull TransactionWithAccounts tr) {
List<Item> newList = new ArrayList<>();
Item.resetIdDispenser();
- LedgerTransaction tr;
- MobileLedgerProfile profile = Data.getProfile(profileId);
- if (profile == null)
- throw new RuntimeException(String.format(
- "Unable to find profile %s, which is supposed to contain transaction %d",
- profileId, transactionId));
- tr = profile.loadTransaction(transactionId);
- TransactionHead head = new TransactionHead(tr.getDescription());
- head.setComment(tr.getComment());
+ TransactionHead head = new TransactionHead(tr.transaction.getDescription());
+ head.setComment(tr.transaction.getComment());
newList.add(head);
- List<LedgerTransactionAccount> accounts = tr.getAccounts();
+ List<LedgerTransactionAccount> accounts = new ArrayList<>();
+ for (net.ktnx.mobileledger.db.TransactionAccount acc : tr.accounts) {
+ accounts.add(new LedgerTransactionAccount(acc));
+ }
TransactionAccount firstNegative = null;
TransactionAccount firstPositive = null;
moveItemLast(newList, singlePositiveIndex);
}
- setItems(newList);
-
- noteFocusChanged(1, FocusedElement.Amount);
+ new Handler(Looper.getMainLooper()).post(() -> {
+ setItems(newList);
+ noteFocusChanged(1, FocusedElement.Amount);
+ });
}
/**
* A transaction is submittable if:
package net.ktnx.mobileledger.ui.profiles;
+import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.activity.CrashReportingActivity;
import net.ktnx.mobileledger.utils.Colors;
import org.jetbrains.annotations.NotNull;
-import java.util.ArrayList;
-import java.util.Locale;
-
import static net.ktnx.mobileledger.utils.Logger.debug;
/**
* in a ProfileListActivity (not really).
*/
public class ProfileDetailActivity extends CrashReportingActivity {
- private MobileLedgerProfile profile = null;
private ProfileDetailFragment mFragment;
+ public static void start(Context context, @Nullable Profile profile) {
+ Intent starter = new Intent(context, ProfileDetailActivity.class);
+ if (profile != null) {
+ starter.putExtra(ProfileDetailFragment.ARG_ITEM_ID, profile.getId());
+ starter.putExtra(ProfileDetailFragment.ARG_ITEM_ID, profile.getTheme());
+ }
+ context.startActivity(starter);
+ }
@NotNull
private ProfileDetailModel getModel() {
return new ViewModelProvider(this).get(ProfileDetailModel.class);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
- final int index = getIntent().getIntExtra(ProfileDetailFragment.ARG_ITEM_ID, -1);
-
- if (index != -1) {
- ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
- if (profiles != null) {
- profile = profiles.get(index);
- if (profile == null)
- throw new AssertionError(
- String.format("Can't get profile " + "(index:%d) from the global list",
- index));
-
- debug("profiles", String.format(Locale.ENGLISH, "Editing profile %s (%s); hue=%d",
- profile.getName(), profile.getId(), profile.getThemeHue()));
- }
- }
+ final long id = getIntent().getLongExtra(ProfileDetailFragment.ARG_ITEM_ID, -1);
+
+ if (id == -1)
+ throw new RuntimeException("Invalid or missing profile ID");
+
+ DB.get()
+ .getProfileDAO()
+ .getById(id)
+ .observe(this, this::setProfile);
+
+ int themeHue = getIntent().getIntExtra(ProfileDetailFragment.ARG_HUE, -1);
super.onCreate(savedInstanceState);
- int themeHue;
- if (profile != null)
- themeHue = profile.getThemeHue();
- else {
+ if (themeHue == -1) {
themeHue = Colors.getNewProfileThemeHue(Data.profiles.getValue());
}
Colors.setupTheme(this, themeHue);
final ProfileDetailModel model = getModel();
model.initialThemeHue = themeHue;
+ model.setThemeId(themeHue);
setContentView(R.layout.activity_profile_detail);
Toolbar toolbar = findViewById(R.id.detail_toolbar);
setSupportActionBar(toolbar);
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
- arguments.putInt(ProfileDetailFragment.ARG_ITEM_ID, index);
arguments.putInt(ProfileDetailFragment.ARG_HUE, themeHue);
mFragment = new ProfileDetailFragment();
mFragment.setArguments(arguments);
.commit();
}
}
+ private void setProfile(Profile profile) {
+ ProfileDetailModel model = new ViewModelProvider(this).get(ProfileDetailModel.class);
+ CollapsingToolbarLayout appBarLayout = findViewById(R.id.toolbar_layout);
+ if (appBarLayout != null) {
+ if (profile != null)
+ appBarLayout.setTitle(profile.getName());
+ else
+ appBarLayout.setTitle(getResources().getString(R.string.new_profile_title));
+ }
+ model.setValuesFromProfile(profile);
+ }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
-import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import net.ktnx.mobileledger.BuildConfig;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.dao.ProfileDAO;
import net.ktnx.mobileledger.databinding.ProfileDetailBinding;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.json.API;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.model.FutureDates;
import net.ktnx.mobileledger.ui.CurrencySelectorFragment;
import net.ktnx.mobileledger.ui.HueRingDialog;
import net.ktnx.mobileledger.utils.Colors;
import java.net.MalformedURLException;
import java.net.URL;
-import java.util.ArrayList;
-import java.util.Objects;
+import java.util.List;
import static net.ktnx.mobileledger.utils.Logger.debug;
public static final String ARG_HUE = "hue";
@NonNls
- private MobileLedgerProfile mProfile;
+ private Profile mProfile;
private boolean defaultCommoditySet;
private boolean syncingModelFromUI = false;
private ProfileDetailBinding binding;
inflater.inflate(R.menu.profile_details, menu);
final MenuItem menuDeleteProfile = menu.findItem(R.id.menuDelete);
menuDeleteProfile.setOnMenuItemClickListener(item -> onDeleteProfile());
- final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
+ final List<Profile> profiles = Data.profiles.getValue();
if (BuildConfig.DEBUG) {
final MenuItem menuWipeProfileData = menu.findItem(R.id.menuWipeData);
builder.setMessage(R.string.remove_profile_dialog_message);
builder.setPositiveButton(R.string.Remove, (dialog, which) -> {
debug("profiles", String.format("[fragment] removing profile %s", mProfile.getId()));
- mProfile.removeFromDB();
- ArrayList<MobileLedgerProfile> oldList = Data.profiles.getValue();
- if (oldList == null)
- throw new AssertionError();
- ArrayList<MobileLedgerProfile> newList = new ArrayList<>(oldList);
- newList.remove(mProfile);
- Data.profiles.setValue(newList);
- if (mProfile.equals(Data.getProfile())) {
- debug("profiles", "[fragment] setting current profile to 0");
- Data.setCurrentProfile(newList.get(0));
- }
+ ProfileDAO dao = DB.get()
+ .getProfileDAO();
+ dao.delete(mProfile, () -> dao.updateOrderSync(dao.getAllOrderedSync()));
final FragmentActivity activity = getActivity();
if (activity != null)
private boolean onWipeDataMenuClicked() {
// this is a development option, so no confirmation
mProfile.wipeAllData();
- if (mProfile.equals(Data.getProfile()))
- triggerProfileChange();
return true;
}
- private void triggerProfileChange() {
- int index = Data.getProfileIndex(mProfile);
- MobileLedgerProfile newProfile = new MobileLedgerProfile(mProfile);
- final ArrayList<MobileLedgerProfile> profiles =
- Objects.requireNonNull(Data.profiles.getValue());
- profiles.set(index, newProfile);
-
- ProfilesRecyclerViewAdapter viewAdapter = ProfilesRecyclerViewAdapter.getInstance();
- if (viewAdapter != null)
- viewAdapter.notifyItemChanged(index);
-
- if (mProfile.equals(Data.getProfile()))
- Data.setCurrentProfile(newProfile);
- }
private void hookTextChangeSyncRoutine(TextView view, TextChangeSyncRoutine syncRoutine) {
view.addTextChangedListener(new TextWatcher() {
@Override
if (context == null)
return;
- if ((getArguments() != null) && getArguments().containsKey(ARG_ITEM_ID)) {
- int index = getArguments().getInt(ARG_ITEM_ID, -1);
- ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
- if ((profiles != null) && (index != -1) && (index < profiles.size()))
- mProfile = profiles.get(index);
-
- Activity activity = this.getActivity();
- if (activity == null)
- throw new AssertionError();
- CollapsingToolbarLayout appBarLayout = activity.findViewById(R.id.toolbar_layout);
- if (appBarLayout != null) {
- if (mProfile != null)
- appBarLayout.setTitle(mProfile.getName());
- else
- appBarLayout.setTitle(getResources().getString(R.string.new_profile_title));
- }
- }
-
final LifecycleOwner viewLifecycleOwner = getViewLifecycleOwner();
final ProfileDetailModel model = getModel();
model.observeDefaultCommodity(viewLifecycleOwner, c -> {
if (c != null)
- setDefaultCommodity(c.getName());
+ setDefaultCommodity(c);
else
resetDefaultCommodity();
});
hookClearErrorOnFocusListener(binding.authUserName, binding.authUserNameLayout);
hookClearErrorOnFocusListener(binding.password, binding.passwordLayout);
- if (savedInstanceState == null) {
- model.setValuesFromProfile(mProfile, getArguments().getInt(ARG_HUE, -1));
- }
- checkInsecureSchemeWithAuth();
-
binding.url.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
});
menu.show();
}
- private MobileLedgerProfile.FutureDates futureDatesSettingFromMenuItemId(int itemId) {
+ private FutureDates futureDatesSettingFromMenuItemId(int itemId) {
if (itemId == R.id.menu_future_dates_7) {
- return MobileLedgerProfile.FutureDates.OneWeek;
+ return FutureDates.OneWeek;
}
else if (itemId == R.id.menu_future_dates_14) {
- return MobileLedgerProfile.FutureDates.TwoWeeks;
+ return FutureDates.TwoWeeks;
}
else if (itemId == R.id.menu_future_dates_30) {
- return MobileLedgerProfile.FutureDates.OneMonth;
+ return FutureDates.OneMonth;
}
else if (itemId == R.id.menu_future_dates_60) {
- return MobileLedgerProfile.FutureDates.TwoMonths;
+ return FutureDates.TwoMonths;
}
else if (itemId == R.id.menu_future_dates_90) {
- return MobileLedgerProfile.FutureDates.ThreeMonths;
+ return FutureDates.ThreeMonths;
}
else if (itemId == R.id.menu_future_dates_180) {
- return MobileLedgerProfile.FutureDates.SixMonths;
+ return FutureDates.SixMonths;
}
else if (itemId == R.id.menu_future_dates_365) {
- return MobileLedgerProfile.FutureDates.OneYear;
+ return FutureDates.OneYear;
}
else if (itemId == R.id.menu_future_dates_all) {
- return MobileLedgerProfile.FutureDates.All;
+ return FutureDates.All;
}
- return MobileLedgerProfile.FutureDates.None;
+ return FutureDates.None;
}
@NotNull
private ProfileDetailModel getModel() {
return;
ProfileDetailModel model = getModel();
- final ArrayList<MobileLedgerProfile> profiles =
- Objects.requireNonNull(Data.profiles.getValue());
+ ProfileDAO dao = DB.get()
+ .getProfileDAO();
if (mProfile != null) {
- int pos = Data.profiles.getValue()
- .indexOf(mProfile);
- mProfile = new MobileLedgerProfile(mProfile);
model.updateProfile(mProfile);
- mProfile.storeInDB();
+ dao.update(mProfile, null);
debug("profiles", "profile stored in DB");
- profiles.set(pos, mProfile);
// debug("profiles", String.format("Selected item is %d", mProfile.getThemeHue()));
-
- final MobileLedgerProfile currentProfile = Data.getProfile();
- if (mProfile.getId() == currentProfile.getId()) {
- Data.setCurrentProfile(mProfile);
- }
-
- ProfilesRecyclerViewAdapter viewAdapter = ProfilesRecyclerViewAdapter.getInstance();
- if (viewAdapter != null)
- viewAdapter.notifyItemChanged(pos);
}
else {
- mProfile = new MobileLedgerProfile(0);
+ mProfile = new Profile();
model.updateProfile(mProfile);
- mProfile.storeInDB();
- final ArrayList<MobileLedgerProfile> newList = new ArrayList<>(profiles);
- newList.add(mProfile);
- Data.profiles.setValue(newList);
- MobileLedgerProfile.storeProfilesOrder();
-
- // first profile ever?
- if (newList.size() == 1)
- Data.setCurrentProfile(mProfile);
+ dao.insertLast(mProfile, null);
}
Activity activity = getActivity();
import androidx.lifecycle.ViewModel;
import net.ktnx.mobileledger.App;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.json.API;
-import net.ktnx.mobileledger.model.Currency;
+import net.ktnx.mobileledger.model.FutureDates;
import net.ktnx.mobileledger.model.HledgerVersion;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.utils.Colors;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.Misc;
private static final String HTTPS_URL_START = "https://";
private final MutableLiveData<String> profileName = new MutableLiveData<>();
private final MutableLiveData<Boolean> postingPermitted = new MutableLiveData<>(true);
- private final MutableLiveData<Currency> defaultCommodity = new MutableLiveData<>(null);
- private final MutableLiveData<MobileLedgerProfile.FutureDates> futureDates =
- new MutableLiveData<>(MobileLedgerProfile.FutureDates.None);
+ private final MutableLiveData<String> defaultCommodity = new MutableLiveData<>(null);
+ private final MutableLiveData<FutureDates> futureDates =
+ new MutableLiveData<>(FutureDates.None);
private final MutableLiveData<Boolean> showCommodityByDefault = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> showCommentsByDefault = new MutableLiveData<>(true);
private final MutableLiveData<Boolean> useAuthentication = new MutableLiveData<>(false);
void observeShowCommentsByDefault(LifecycleOwner lfo, Observer<Boolean> o) {
showCommentsByDefault.observe(lfo, o);
}
- MobileLedgerProfile.FutureDates getFutureDates() {
+ FutureDates getFutureDates() {
return futureDates.getValue();
}
- void setFutureDates(MobileLedgerProfile.FutureDates newValue) {
+ void setFutureDates(FutureDates newValue) {
if (newValue != futureDates.getValue())
futureDates.setValue(newValue);
}
- void observeFutureDates(LifecycleOwner lfo, Observer<MobileLedgerProfile.FutureDates> o) {
+ void observeFutureDates(LifecycleOwner lfo, Observer<FutureDates> o) {
futureDates.observe(lfo, o);
}
- Currency getDefaultCommodity() {
+ String getDefaultCommodity() {
return defaultCommodity.getValue();
}
- void setDefaultCommodity(Currency newValue) {
- if (newValue != defaultCommodity.getValue())
+ void setDefaultCommodity(String newValue) {
+ if (!Misc.equalStrings(newValue, defaultCommodity.getValue()))
defaultCommodity.setValue(newValue);
}
- void observeDefaultCommodity(LifecycleOwner lfo, Observer<Currency> o) {
+ void observeDefaultCommodity(LifecycleOwner lfo, Observer<String> o) {
defaultCommodity.observe(lfo, o);
}
Boolean getShowCommodityByDefault() {
void observeDetectingHledgerVersion(LifecycleOwner lfo, Observer<Boolean> o) {
detectingHledgerVersion.observe(lfo, o);
}
- void setValuesFromProfile(MobileLedgerProfile mProfile, int newProfileHue) {
- final int profileThemeId;
+ void setValuesFromProfile(Profile mProfile) {
if (mProfile != null) {
profileName.setValue(mProfile.getName());
- postingPermitted.setValue(mProfile.isPostingPermitted());
+ postingPermitted.setValue(mProfile.permitPosting());
showCommentsByDefault.setValue(mProfile.getShowCommentsByDefault());
showCommodityByDefault.setValue(mProfile.getShowCommodityByDefault());
{
if (TextUtils.isEmpty(comm))
setDefaultCommodity(null);
else
- setDefaultCommodity(new Currency(-1, comm));
+ setDefaultCommodity(comm);
}
- futureDates.setValue(mProfile.getFutureDates());
- apiVersion.setValue(mProfile.getApiVersion());
+ futureDates.setValue(
+ FutureDates.valueOf(mProfile.getFutureDates()));
+ apiVersion.setValue(API.valueOf(mProfile.getApiVersion()));
url.setValue(mProfile.getUrl());
- useAuthentication.setValue(mProfile.isAuthEnabled());
- authUserName.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthUserName() : "");
- authPassword.setValue(mProfile.isAuthEnabled() ? mProfile.getAuthPassword() : "");
+ useAuthentication.setValue(mProfile.useAuthentication());
+ authUserName.setValue(mProfile.useAuthentication() ? mProfile.getAuthUser() : "");
+ authPassword.setValue(mProfile.useAuthentication() ? mProfile.getAuthPassword() : "");
preferredAccountsFilter.setValue(mProfile.getPreferredAccountsFilter());
- themeId.setValue(mProfile.getThemeHue());
- detectedVersion.setValue(mProfile.getDetectedVersion());
+ themeId.setValue(mProfile.getTheme());
+ detectedVersion.setValue(mProfile.detectedVersionPre_1_19() ? new HledgerVersion(true)
+ : new HledgerVersion(
+ mProfile.getDetectedVersionMajor(),
+ mProfile.getDetectedVersionMinor()));
}
else {
profileName.setValue(null);
postingPermitted.setValue(true);
showCommentsByDefault.setValue(true);
showCommodityByDefault.setValue(false);
- setFutureDates(MobileLedgerProfile.FutureDates.None);
+ setFutureDates(FutureDates.None);
setApiVersion(API.auto);
useAuthentication.setValue(false);
authUserName.setValue("");
authPassword.setValue("");
preferredAccountsFilter.setValue(null);
- themeId.setValue(newProfileHue);
detectedVersion.setValue(null);
}
}
- void updateProfile(MobileLedgerProfile mProfile) {
+ void updateProfile(Profile mProfile) {
mProfile.setName(profileName.getValue());
mProfile.setUrl(url.getValue());
- mProfile.setPostingPermitted(postingPermitted.getValue());
+ mProfile.setPermitPosting(postingPermitted.getValue());
mProfile.setShowCommentsByDefault(showCommentsByDefault.getValue());
- Currency c = defaultCommodity.getValue();
- mProfile.setDefaultCommodity((c == null) ? null : c.getName());
+ mProfile.setDefaultCommodity(defaultCommodity.getValue());
mProfile.setShowCommodityByDefault(showCommodityByDefault.getValue());
mProfile.setPreferredAccountsFilter(preferredAccountsFilter.getValue());
- mProfile.setAuthEnabled(useAuthentication.getValue());
- mProfile.setAuthUserName(authUserName.getValue());
+ mProfile.setUseAuthentication(useAuthentication.getValue());
+ mProfile.setAuthUser(authUserName.getValue());
mProfile.setAuthPassword(authPassword.getValue());
- mProfile.setThemeHue(themeId.getValue());
- mProfile.setFutureDates(futureDates.getValue());
- mProfile.setApiVersion(apiVersion.getValue());
- mProfile.setDetectedVersion(detectedVersion.getValue());
+ mProfile.setTheme(themeId.getValue());
+ mProfile.setFutureDates(futureDates.getValue()
+ .toInt());
+ mProfile.setApiVersion(apiVersion.getValue()
+ .toInt());
+ HledgerVersion version = detectedVersion.getValue();
+ mProfile.setDetectedVersionPre_1_19(version.isPre_1_20_1());
+ mProfile.setDetectedVersionMajor(version.getMajor());
+ mProfile.setDetectedVersionMinor(version.getMinor());
}
synchronized public void triggerVersionDetection() {
if (versionDetectionThread != null)
}
private HledgerVersion detectVersion() {
App.setAuthenticationDataFromProfileModel(model);
- HttpURLConnection http = null;
+ HttpURLConnection http;
try {
http = NetworkUtil.prepareConnection(model.getUrl(), "version",
model.getUseAuthentication());
package net.ktnx.mobileledger.ui.profiles;
-import android.content.Context;
-import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.MutableLiveData;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
-import net.ktnx.mobileledger.ui.activity.MainActivity;
import net.ktnx.mobileledger.utils.Colors;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import static net.ktnx.mobileledger.utils.Logger.debug;
public class ProfilesRecyclerViewAdapter
extends RecyclerView.Adapter<ProfilesRecyclerViewAdapter.ProfileListViewHolder> {
- private static WeakReference<ProfilesRecyclerViewAdapter> instanceRef;
public final MutableLiveData<Boolean> editingProfiles = new MutableLiveData<>(false);
- private final View.OnClickListener mOnClickListener = view -> {
- MobileLedgerProfile profile = (MobileLedgerProfile) ((View) view.getParent()).getTag();
- editProfile(view, profile);
- };
private final ItemTouchHelper rearrangeHelper;
+ private final AsyncListDiffer<Profile> listDiffer;
private RecyclerView recyclerView;
private boolean animationsEnabled = true;
+
public ProfilesRecyclerViewAdapter() {
- instanceRef = new WeakReference<>(this);
debug("flow", "ProfilesRecyclerViewAdapter.new()");
+ setHasStableIds(true);
+ listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<Profile>() {
+ @Override
+ public boolean areItemsTheSame(@NonNull Profile oldItem, @NonNull Profile newItem) {
+ return oldItem.getId() == newItem.getId();
+ }
+ @Override
+ public boolean areContentsTheSame(@NonNull Profile oldItem, @NonNull Profile newItem) {
+ return oldItem.equals(newItem);
+ }
+ });
+
ItemTouchHelper.Callback cb = new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView,
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
- final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
- if (profiles == null)
- throw new AssertionError();
+ final List<Profile> profiles = new ArrayList<>(listDiffer.getCurrentList());
Collections.swap(profiles, viewHolder.getAdapterPosition(),
target.getAdapterPosition());
- MobileLedgerProfile.storeProfilesOrder();
- notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
+ DB.get()
+ .getProfileDAO()
+ .updateOrder(profiles, null);
+// notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
};
rearrangeHelper = new ItemTouchHelper(cb);
}
- public static @Nullable
- ProfilesRecyclerViewAdapter getInstance() {
- return instanceRef.get();
+ @Override
+ public long getItemId(int position) {
+ return listDiffer.getCurrentList()
+ .get(position)
+ .getId();
+ }
+ public void setProfileList(List<Profile> list) {
+ listDiffer.submitList(list);
}
public void setAnimationsEnabled(boolean animationsEnabled) {
this.animationsEnabled = animationsEnabled;
else
startEditingProfiles();
}
- private void editProfile(View view, MobileLedgerProfile profile) {
- int index = Data.getProfileIndex(profile);
- Context context = view.getContext();
- Intent intent = new Intent(context, ProfileDetailActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
- if (index != -1)
- intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID, index);
-
- context.startActivity(intent);
- }
- private void onProfileRowClicked(View v) {
- if (editingProfiles())
- return;
- MobileLedgerProfile profile = (MobileLedgerProfile) v.getTag();
- if (profile == null)
- throw new IllegalStateException("Profile row without associated profile");
- debug("profiles", "Setting profile to " + profile.getName());
- if (Data.getProfile() != profile) {
- Data.drawerOpen.setValue(false);
- ((MainActivity) v.getContext()).storeProfilePref(profile);
- }
- Data.setCurrentProfile(profile);
- }
@NonNull
@Override
public ProfileListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.profile_list_content, parent, false);
- ProfileListViewHolder holder = new ProfileListViewHolder(view);
-
- holder.mRow.setOnClickListener(this::onProfileRowClicked);
- holder.mTitle.setOnClickListener(v -> {
- View row = (View) v.getParent();
- onProfileRowClicked(row);
- });
- holder.mColorTag.setOnClickListener(v -> {
- View row = (View) v.getParent()
- .getParent();
- onProfileRowClicked(row);
- });
- holder.mTitle.setOnLongClickListener(v -> {
- flipEditingProfiles();
- return true;
- });
-
- View.OnTouchListener dragStarter = (v, event) -> {
- if (rearrangeHelper != null && editingProfiles()) {
- rearrangeHelper.startDrag(holder);
- return true;
- }
- return false;
- };
-
- holder.tagAndHandleLayout.setOnTouchListener(dragStarter);
- return holder;
+ return new ProfileListViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final ProfileListViewHolder holder, int position) {
- final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
- if (profiles == null)
- throw new AssertionError();
- final MobileLedgerProfile profile = profiles.get(position);
- final MobileLedgerProfile currentProfile = Data.getProfile();
+ final Profile profile = listDiffer.getCurrentList()
+ .get(position);
+ final Profile currentProfile = Data.getProfile();
// debug("profiles", String.format(Locale.ENGLISH, "pos %d: %s, current: %s", position,
// profile.getUuid(), currentProfile.getUuid()));
- holder.itemView.setTag(profile);
- int hue = profile.getThemeHue();
+ int hue = profile.getTheme();
if (hue == -1)
holder.mColorTag.setBackgroundColor(
Colors.getPrimaryColorForHue(Colors.DEFAULT_HUE_DEG));
holder.mTitle.setText(profile.getName());
// holder.mSubTitle.setText(profile.getUrl());
- holder.mEditButton.setOnClickListener(mOnClickListener);
+ holder.mEditButton.setOnClickListener(view -> {
+ Profile p = listDiffer.getCurrentList()
+ .get(holder.getAdapterPosition());
+ ProfileDetailActivity.start(view.getContext(), p);
+ });
- final boolean sameProfile = currentProfile.equals(profile);
+ final boolean sameProfile = currentProfile.getId() == profile.getId();
holder.itemView.setBackground(
sameProfile ? new ColorDrawable(Colors.tableRowDarkBG) : null);
if (editingProfiles()) {
}
@Override
public int getItemCount() {
- final ArrayList<MobileLedgerProfile> profiles = Data.profiles.getValue();
- return profiles != null ? profiles.size() : 0;
+ return listDiffer.getCurrentList()
+ .size();
}
- static class ProfileListViewHolder extends RecyclerView.ViewHolder {
+ class ProfileListViewHolder extends RecyclerView.ViewHolder {
final TextView mEditButton;
final TextView mTitle, mColorTag;
final LinearLayout tagAndHandleLayout;
mRearrangeHandle = view.findViewById(R.id.profile_list_rearrange_handle);
tagAndHandleLayout = view.findViewById(R.id.handle_and_tag);
mRow = (ConstraintLayout) view;
+
+
+ mRow.setOnClickListener(this::onProfileRowClicked);
+ mTitle.setOnClickListener(v -> {
+ View row = (View) v.getParent();
+ onProfileRowClicked(row);
+ });
+ mColorTag.setOnClickListener(v -> {
+ View row = (View) v.getParent()
+ .getParent();
+ onProfileRowClicked(row);
+ });
+ mTitle.setOnLongClickListener(v -> {
+ flipEditingProfiles();
+ return true;
+ });
+
+ View.OnTouchListener dragStarter = (v, event) -> {
+ if (rearrangeHelper != null && editingProfiles()) {
+ rearrangeHelper.startDrag(this);
+ return true;
+ }
+ return false;
+ };
+
+ tagAndHandleLayout.setOnTouchListener(dragStarter);
+ }
+ private void onProfileRowClicked(View v) {
+ if (editingProfiles())
+ return;
+ Profile profile = listDiffer.getCurrentList()
+ .get(getAdapterPosition());
+ if (Data.getProfile() != profile) {
+ debug("profiles", "Setting profile to " + profile.getName());
+ Data.drawerOpen.setValue(false);
+ Data.setCurrentProfile(profile);
+ }
+ else
+ debug("profiles",
+ "Not setting profile to the current profile " + profile.getName());
}
+
}
}
.getTemplateDAO();
TemplateHeader dbHeader = modelHeader.toDBO();
if (newPattern) {
- dbHeader.setId(null);
+ dbHeader.setId(0L);
dbHeader.setId(mPatternId = headerDAO.insertSync(dbHeader));
}
else
dbAccount.setTemplateId(mPatternId);
dbAccount.setPosition(i);
if (dbAccount.getId() < 0) {
- dbAccount.setId(null);
+ dbAccount.setId(0);
dbAccount.setId(taDAO.insertSync(dbAccount));
}
else
}
private ArrayList<TemplateDetailsItem> copyItems() {
List<TemplateDetailsItem> oldList = items.getValue();
+
+ if (oldList == null)
+ return new ArrayList<>();
+
ArrayList<TemplateDetailsItem> result = new ArrayList<>(oldList.size());
for (TemplateDetailsItem item : oldList) {
.equals(newItem.getDate()));
case TRANSACTION:
return oldItem.getTransaction()
- .getId() == newItem.getTransaction()
- .getId();
+ .getLedgerId() == newItem.getTransaction()
+ .getLedgerId();
case HEADER:
return true; // there can be only one header
default:
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.TransactionDateFinder;
import net.ktnx.mobileledger.db.AccountAutocompleteAdapter;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.DatePickerFragment;
import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.MainModel;
mainActivity.fabShouldShow();
if (mainActivity instanceof FabManager.FabHandler)
- FabManager.handle((FabManager.FabHandler) mainActivity, root);
+ FabManager.handle(mainActivity, root);
LinearLayoutManager llm = new LinearLayoutManager(mainActivity);
vAccountFilter = view.findViewById(R.id.transaction_list_account_name_filter);
accNameFilter = view.findViewById(R.id.transaction_filter_account_name);
- MobileLedgerProfile profile = Data.getProfile();
+ Profile profile = Data.getProfile();
accNameFilter.setAdapter(new AccountAutocompleteAdapter(mainActivity, profile));
accNameFilter.setOnItemClickListener((parent, v, position, id) -> {
// debug("tmp", "direct onItemClick");
import net.ktnx.mobileledger.BuildConfig;
import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.db.Profile;
import net.ktnx.mobileledger.ui.HueRing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Objects;
}
return colors;
}
- public static int getNewProfileThemeHue(ArrayList<MobileLedgerProfile> profiles) {
+ public static int getNewProfileThemeHue(List<Profile> profiles) {
if ((profiles == null) || (profiles.size() == 0))
return DEFAULT_HUE_DEG;
if (profiles.size() == 1) {
int opposite = profiles.get(0)
- .getThemeHue() + 180;
+ .getTheme() + 180;
opposite %= 360;
chosenHue = opposite;
}
else {
ArrayList<Integer> hues = new ArrayList<>();
- for (MobileLedgerProfile p : profiles) {
- int hue = p.getThemeHue();
+ for (Profile p : profiles) {
+ int hue = p.getTheme();
if (hue == -1)
hue = DEFAULT_HUE_DEG;
hues.add(hue);
import androidx.annotation.NonNull;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
+import net.ktnx.mobileledger.db.Profile;
import org.jetbrains.annotations.NotNull;
public final class NetworkUtil {
private static final int thirtySeconds = 30000;
@NotNull
- public static HttpURLConnection prepareConnection(@NonNull MobileLedgerProfile profile,
+ public static HttpURLConnection prepareConnection(@NonNull Profile profile,
@NonNull String path) throws IOException {
- return prepareConnection(profile.getUrl(), path, profile.isAuthEnabled());
+ return prepareConnection(profile.getUrl(), path, profile.useAuthentication());
}
public static HttpURLConnection prepareConnection(@NonNull String url, @NonNull String path,
boolean authEnabled) throws IOException {
+++ /dev/null
-/*
- * 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.
- *
- * MoLe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License terms for details.
- *
- * You should have received a copy of the GNU General Public License
- * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package net.ktnx.mobileledger.model;
-
-import org.junit.Test;
-import org.junit.internal.ArrayComparisonFailure;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThrows;
-
-public class MobileLedgerProfileTest {
- private List<LedgerAccount> listFromArray(LedgerAccount[] array) {
- ArrayList<LedgerAccount> result = new ArrayList<>();
- Collections.addAll(result, array);
-
- return result;
- }
- private void aTest(LedgerAccount[] oldList, LedgerAccount[] newList,
- LedgerAccount[] expectedResult) {
- List<LedgerAccount> result =
- MobileLedgerProfile.mergeAccountListsFromWeb(listFromArray(oldList),
- listFromArray(newList));
- assertArrayEquals(expectedResult, result.toArray());
- }
- private void negTest(LedgerAccount[] oldList, LedgerAccount[] newList,
- LedgerAccount[] expectedResult) {
- List<LedgerAccount> result =
- MobileLedgerProfile.mergeAccountListsFromWeb(listFromArray(oldList),
- listFromArray(newList));
- assertThrows(ArrayComparisonFailure.class,
- () -> assertArrayEquals(expectedResult, result.toArray()));
- }
- private LedgerAccount[] emptyArray() {
- return new LedgerAccount[]{};
- }
- @Test
- public void mergeEmptyLists() {
- aTest(emptyArray(), emptyArray(), emptyArray());
- }
- @Test
- public void mergeIntoEmptyLists() {
- LedgerAccount acc1 = new LedgerAccount(null, "Acc1", null);
- aTest(emptyArray(), new LedgerAccount[]{acc1}, new LedgerAccount[]{acc1});
- }
- @Test
- public void mergeEmptyList() {
- LedgerAccount acc1 = new LedgerAccount(null, "Acc1", null);
- aTest(new LedgerAccount[]{acc1}, emptyArray(), emptyArray());
- }
- @Test
- public void mergeEqualLists() {
- LedgerAccount acc1 = new LedgerAccount(null, "Acc1", null);
- aTest(new LedgerAccount[]{acc1}, new LedgerAccount[]{acc1}, new LedgerAccount[]{acc1});
- }
- @Test
- public void mergeFlags() {
- LedgerAccount acc1a = new LedgerAccount(null, "Acc1", null);
- LedgerAccount acc1b = new LedgerAccount(null, "Acc1", null);
- acc1b.setExpanded(true);
- acc1b.setAmountsExpanded(true);
- List<LedgerAccount> merged = MobileLedgerProfile.mergeAccountListsFromWeb(
- listFromArray(new LedgerAccount[]{acc1a}),
- listFromArray(new LedgerAccount[]{acc1b}));
- assertArrayEquals(new LedgerAccount[]{acc1b}, merged.toArray());
- assertSame(merged.get(0), acc1a);
- // restore original values, modified by the merge
- acc1a.setExpanded(false);
- acc1a.setAmountsExpanded(false);
- negTest(new LedgerAccount[]{acc1a}, new LedgerAccount[]{acc1b},
- new LedgerAccount[]{new LedgerAccount(null, "Acc1", null)});
- }
-}
\ No newline at end of file