}
dependencies {
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.0'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
def room_version = '2.2.6'
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
- def nav_version = '2.3.3'
+ def nav_version = '2.3.4'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
- testImplementation 'junit:junit:4.13.1'
- androidTestImplementation 'androidx.test:runner:1.4.0-alpha04'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha04'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test:runner:1.4.0-alpha05'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha05'
implementation 'org.jetbrains:annotations:15.0'
- implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.11.3'
+ implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.12.2'
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
- implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
+ implementation 'androidx.appcompat:appcompat:1.3.0-rc01'
}
allprojects {
return null;
}
}
- public MobileLedgerProfile getProfile() {
- return profile;
- }
@Override
protected void onProgressUpdate(Progress... values) {
super.onProgressUpdate(values);
else {
parentAccount = null;
}
- lastAccount = new LedgerAccount(profile, accName, parentAccount);
+ lastAccount = new LedgerAccount(accName, parentAccount);
accounts.add(lastAccount);
map.put(accName, lastAccount);
parentAccount = null;
}
- acc = new LedgerAccount(profile, accountName, parentAccount);
+ acc = new LedgerAccount(accountName, parentAccount);
createdAccounts.add(acc);
return acc;
}
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
+import androidx.room.Transaction;
import androidx.room.Update;
import net.ktnx.mobileledger.db.Account;
+import net.ktnx.mobileledger.db.AccountWithAmounts;
import java.util.ArrayList;
import java.util.List;
@Delete
public abstract void deleteSync(Account item);
- @Query("SELECT * FROM accounts")
- public abstract LiveData<List<Account>> getAll();
+ @Delete
+ public abstract void deleteSync(List<Account> items);
+
+ @Query("SELECT * FROM accounts WHERE profile_id=:profileId")
+ public abstract LiveData<List<Account>> getAll(long profileId);
+
+ @Transaction
+ @Query("SELECT * FROM accounts WHERE profile_id = :profileId")
+ public abstract LiveData<List<AccountWithAmounts>> getAllWithAmounts(long profileId);
+
+ @Query("SELECT * FROM accounts WHERE id=:id")
+ public abstract Account getByIdSync(long id);
// not useful for now
// @Transaction
@Query("SELECT * FROM accounts WHERE profile_id = :profileId AND name = :accountName")
public abstract LiveData<Account> getByName(long profileId, @NonNull String accountName);
+ @Transaction
+ @Query("SELECT * FROM accounts WHERE profile_id = :profileId AND name = :accountName")
+ public abstract LiveData<AccountWithAmounts> getByNameWithAmounts(long profileId,
+ @NonNull String accountName);
+
@Query("SELECT name, CASE WHEN name_upper LIKE :term||'%%' THEN 1 " +
" WHEN name_upper LIKE '%%:'||:term||'%%' THEN 2 " +
" WHEN name_upper LIKE '%% '||:term||'%%' THEN 3 " +
"WHERE name_upper LIKE '%%'||:term||'%%' " + "ORDER BY ordering, name_upper, rowid ")
public abstract List<AccountNameContainer> lookupByNameSync(@NonNull String term);
+ @Query("SELECT * FROM accounts WHERE profile_id = :profileId")
+ public abstract List<Account> allForProfileSync(long profileId);
+
static public class AccountNameContainer {
@ColumnInfo
public String name;
--- /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.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import net.ktnx.mobileledger.db.AccountValue;
+
+import java.util.List;
+
+@Dao
+public abstract class AccountValueDAO extends BaseDAO<AccountValue> {
+ @Insert
+ public abstract long insertSync(AccountValue item);
+
+ @Update
+ public abstract void updateSync(AccountValue item);
+
+ @Delete
+ public abstract void deleteSync(AccountValue item);
+
+ @Query("SELECT * FROM account_values WHERE account_id=:accountId")
+ public abstract LiveData<List<AccountValue>> getAll(long accountId);
+
+ @Query("SELECT * FROM account_values WHERE account_id = :accountId AND currency = :currency")
+ public abstract AccountValue getByCurrencySync(long accountId, @NonNull String currency);
+}
import net.ktnx.mobileledger.db.Option;
+import java.util.List;
+
@Dao
public abstract class OptionDAO extends BaseDAO<Option> {
@Insert(onConflict = OnConflictStrategy.REPLACE)
@Delete
public abstract void deleteSync(Option item);
+ @Delete
+ public abstract void deleteSync(List<Option> items);
+
@Query("SELECT * FROM options WHERE profile_id = :profileId AND name = :name")
public abstract LiveData<Option> load(long profileId, String name);
@Query("SELECT * FROM options WHERE profile_id = :profileId AND name = :name")
public abstract Option loadSync(long profileId, String name);
+
+ @Query("SELECT * FROM options WHERE profile_id = :profileId")
+ public abstract List<Option> allForProfileSync(long profileId);
}
@Delete
public abstract void deleteSync(Transaction item);
+ @Delete
+ public abstract void deleteSync(List<Transaction> items);
+
@Query("SELECT * FROM transactions")
public abstract LiveData<List<Transaction>> getAll();
"ORDER BY ordering, description_upper, rowid ")
public abstract List<DescriptionContainer> lookupDescriptionSync(@NonNull String term);
+ @Query("SELECT * from transactions WHERE profile_id = :profileId")
+ public abstract List<Transaction> allForProfileSync(long profileId);
+
static public class DescriptionContainer {
@ColumnInfo
public String description;
--- /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.db;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class AccountWithAmounts {
+ @Embedded
+ public Account account;
+ @Relation(parentColumn = "id", entityColumn = "account_id")
+ public List<AccountValue> amounts;
+}
import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.dao.AccountDAO;
+import net.ktnx.mobileledger.dao.AccountValueDAO;
import net.ktnx.mobileledger.dao.CurrencyDAO;
import net.ktnx.mobileledger.dao.OptionDAO;
import net.ktnx.mobileledger.dao.TemplateAccountDAO;
public abstract AccountDAO getAccountDAO();
+ public abstract AccountValueDAO getAccountValueDAO();
+
public abstract TransactionDAO getTransactionDAO();
public abstract OptionDAO getOptionDAO();
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
parent = task.ensureAccountExists(parentName, map, createdParents);
parent.setHasSubAccounts(true);
}
- acc = new LedgerAccount(task.getProfile(), accName, parent);
+ acc = new LedgerAccount(accName, parent);
map.put(accName, acc);
String lastCurrency = null;
package net.ktnx.mobileledger.model;
import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
import org.jetbrains.annotations.NotNull;
-public class AccountListItem {
+public abstract class AccountListItem {
private AccountListItem() {}
+ public abstract boolean sameContent(AccountListItem other);
@NonNull
public Type getType() {
if (this instanceof Account)
public Account(@NotNull LedgerAccount account) {
this.account = account;
}
+ @Override
+ public boolean sameContent(AccountListItem other) {
+ if (!(other instanceof Account))
+ return false;
+ return ((Account) other).account.hasSubAccounts() == account.hasSubAccounts() &&
+ ((Account) other).account.amountsExpanded() == account.amountsExpanded() &&
+ ((Account) other).account.isExpanded() == account.isExpanded() &&
+ ((Account) other).account.getLevel() == account.getLevel() &&
+ ((Account) other).account.getAmountsString()
+ .equals(account.getAmountsString());
+ }
}
public static class Header extends AccountListItem {
- public Header() {
+ private final LiveData<String> text;
+ public Header(@NonNull LiveData<String> text) {
+ this.text = text;
+ }
+ public LiveData<String> getText() {
+ return text;
+ }
+ @Override
+ public boolean sameContent(AccountListItem other) {
+ return true;
}
}
}
import net.ktnx.mobileledger.utils.Locker;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.MLDB;
-import net.ktnx.mobileledger.utils.ObservableValue;
import java.text.NumberFormat;
import java.text.ParseException;
public static final MutableLiveData<Integer> lastUpdateTransactionCount =
new MutableLiveData<>(0);
public static final MutableLiveData<Integer> lastUpdateAccountCount = new MutableLiveData<>(0);
- public static final ObservableValue<String> lastTransactionsUpdateText =
- new ObservableValue<>();
- public static final ObservableValue<String> lastAccountsUpdateText = new ObservableValue<>();
+ public static final MutableLiveData<String> lastTransactionsUpdateText =
+ new MutableLiveData<>();
+ public static final MutableLiveData<String> lastAccountsUpdateText = new MutableLiveData<>();
private static final MutableLiveData<MobileLedgerProfile> profile =
new InertMutableLiveData<>();
private static final AtomicInteger backgroundTaskCount = new AtomicInteger(0);
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import java.lang.ref.WeakReference;
+import net.ktnx.mobileledger.db.Account;
+import net.ktnx.mobileledger.db.AccountValue;
+import net.ktnx.mobileledger.db.AccountWithAmounts;
+
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class LedgerAccount {
static Pattern reHigherAccount = Pattern.compile("^[^:]+:");
+ private final LedgerAccount parent;
+ private long dbId;
+ private long profileId;
private String name;
private String shortName;
private int level;
- private final LedgerAccount parent;
private boolean expanded;
private List<LedgerAmount> amounts;
private boolean hasSubAccounts;
private boolean amountsExpanded;
- private final WeakReference<MobileLedgerProfile> profileWeakReference;
- public LedgerAccount(MobileLedgerProfile profile, String name, @Nullable LedgerAccount parent) {
- this.profileWeakReference = new WeakReference<>(profile);
+ public LedgerAccount(String name, @Nullable LedgerAccount parent) {
this.parent = parent;
if (parent != null && !name.startsWith(parent.getName() + ":"))
throw new IllegalStateException(
else
return accName.substring(0, colonPos);
}
- public @Nullable
- MobileLedgerProfile getProfile() {
- return profileWeakReference.get();
+ @NonNull
+ static public LedgerAccount fromDBO(AccountWithAmounts in, LedgerAccount parent) {
+ LedgerAccount res = new LedgerAccount(in.account.getName(), parent);
+ res.dbId = in.account.getId();
+ res.profileId = in.account.getProfileId();
+ res.setName(in.account.getName());
+ res.setExpanded(in.account.isExpanded());
+ res.setAmountsExpanded(in.account.isAmountsExpanded());
+
+ res.amounts = new ArrayList<>();
+ for (AccountValue val : in.amounts) {
+ res.amounts.add(new LedgerAmount(val.getValue(), val.getCurrency()));
+ }
+
+ return res;
}
@Override
public int hashCode() {
.startsWith(name + ":");
}
private void stripName() {
- if (parent == null) {
- level = 0;
- shortName = name;
- }
- else {
- level = parent.level + 1;
- shortName = name.substring(parent.getName()
- .length() + 1);
- }
+ String[] split = name.split(":");
+ shortName = split[split.length - 1];
+ level = split.length - 1;
}
public String getName() {
return name;
public int getLevel() {
return level;
}
-
@NonNull
public String getShortName() {
return shortName;
}
-
public String getParentName() {
return (parent == null) ? null : parent.getName();
}
public boolean amountsExpanded() { return amountsExpanded; }
public void setAmountsExpanded(boolean flag) { amountsExpanded = flag; }
public void toggleAmountsExpanded() { amountsExpanded = !amountsExpanded; }
-
public void propagateAmountsTo(LedgerAccount acc) {
for (LedgerAmount a : amounts)
a.propagateToAccount(acc);
public List<LedgerAmount> getAmounts() {
return amounts;
}
+ @NonNull
+ public Account toDBO() {
+ Account dbo = new Account();
+ dbo.setName(name);
+ dbo.setNameUpper(name.toUpperCase());
+ dbo.setParentName(extractParentName(name));
+ dbo.setLevel(level);
+ dbo.setId(dbId);
+ dbo.setProfileId(profileId);
+ dbo.setExpanded(expanded);
+ dbo.setAmountsExpanded(amountsExpanded);
+
+ return dbo;
+ }
+ public long getId() {
+ return dbId;
+ }
}
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
import androidx.annotation.NonNull;
+import net.ktnx.mobileledger.dao.AccountValueDAO;
+import net.ktnx.mobileledger.db.Account;
+import net.ktnx.mobileledger.db.AccountValue;
+import net.ktnx.mobileledger.db.DB;
+
public class LedgerAmount {
private final String currency;
private final float amount;
+ private long dbId;
public LedgerAmount(float amount, @NonNull String currency) {
this.currency = currency;
this.amount = amount;
}
-
public LedgerAmount(float amount) {
this.amount = amount;
this.currency = null;
}
+ static public LedgerAmount fromDBO(AccountValue dbo) {
+ final LedgerAmount ledgerAmount = new LedgerAmount(dbo.getValue(), dbo.getCurrency());
+ ledgerAmount.dbId = dbo.getId();
+ return ledgerAmount;
+ }
+ public AccountValue toDBO(Account account) {
+ final AccountValueDAO dao = DB.get()
+ .getAccountValueDAO();
+ AccountValue obj = new AccountValue();
+ obj.setId(dbId);
+ obj.setAccountId(account.getId());
+ obj.setCurrency(currency);
+ obj.setValue(amount);
+
+ return obj;
+ }
@SuppressLint("DefaultLocale")
@NonNull
public String toString() {
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
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.OptionDAO;
+import net.ktnx.mobileledger.dao.TransactionDAO;
+import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.json.API;
import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment;
}
private int getNextAccountsGeneration(SQLiteDatabase db) {
try (Cursor c = db.rawQuery("SELECT generation FROM accounts WHERE profile_id=? LIMIT 1",
- new String[]{String.valueOf(id)})) {
+ new String[]{String.valueOf(id)}))
+ {
if (c.moveToFirst())
return c.getInt(0) + 1;
}
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));
+ }
public void wipeAllData() {
- SQLiteDatabase db = App.getDatabase();
- db.beginTransaction();
- try {
- String[] pUuid = new String[]{String.valueOf(id)};
- db.execSQL("delete from options where profile=?", pUuid);
- db.execSQL("delete from accounts where profile=?", pUuid);
- db.execSQL("delete from account_values where profile=?", pUuid);
- db.execSQL("delete from transactions where profile=?", pUuid);
- db.execSQL("delete from transaction_accounts where profile=?", pUuid);
- db.setTransactionSuccessful();
- debug("wipe", String.format(Locale.ENGLISH, "Profile %s wiped out", pUuid[0]));
- }
- finally {
- db.endTransaction();
- }
+ AsyncTask.execute(this::wipeAllDataSync);
}
public List<Currency> getCurrencies() {
SQLiteDatabase db = App.getDatabase();
package net.ktnx.mobileledger.ui;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Build;
import android.text.TextUtils;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
-import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.async.RetrieveTransactionsTask;
import net.ktnx.mobileledger.async.TransactionAccumulator;
import net.ktnx.mobileledger.async.UpdateTransactionsTask;
private SimpleDate lastTransactionDate;
transient private RetrieveTransactionsTask retrieveTransactionsTask;
transient private Thread displayedAccountsUpdater;
- transient private AccountListLoader loader = null;
private TransactionsDisplayedFilter displayedTransactionsUpdater;
public static ArrayList<LedgerAccount> mergeAccountListsFromWeb(List<LedgerAccount> oldList,
List<LedgerAccount> newList) {
public LiveData<List<AccountListItem>> getDisplayedAccounts() {
return displayedAccounts;
}
- synchronized public void scheduleAccountListReload() {
- Logger.debug("async-acc", "scheduleAccountListReload() enter");
- if ((loader != null) && loader.isAlive()) {
- Logger.debug("async-acc", "returning early - loader already active");
- return;
- }
-
- loader = new AccountListLoader(profile, this);
- loader.start();
- }
public synchronized void setAndStoreAccountAndTransactionListFromWeb(
List<LedgerAccount> accounts, List<LedgerTransaction> transactions) {
profile.storeAccountAndTransactionListAsync(accounts, transactions);
updateDisplayedTransactionsFromWeb(transactions);
}
- synchronized public void abortAccountListReload() {
- if (loader == null)
- return;
- loader.interrupt();
- loader = null;
- }
synchronized public void updateDisplayedAccounts() {
if (displayedAccountsUpdater != null) {
displayedAccountsUpdater.interrupt();
public void clearTransactions() {
displayedTransactions.setValue(new ArrayList<>());
}
-
- static class AccountListLoader extends Thread {
- private final MobileLedgerProfile profile;
- private final MainModel model;
- AccountListLoader(MobileLedgerProfile profile, MainModel model) {
- this.profile = profile;
- this.model = model;
- }
- @Override
- public void run() {
- Logger.debug("async-acc", "AccountListLoader::run() entered");
- long profileId = profile.getId();
- ArrayList<LedgerAccount> list = new ArrayList<>();
- HashMap<String, LedgerAccount> map = new HashMap<>();
-
- String sql = "SELECT a.name, a.expanded, a.amounts_expanded, a.id";
- sql += " from accounts a WHERE a.profile_id = ?";
- sql += " ORDER BY a.name";
-
- SQLiteDatabase db = App.getDatabase();
- Logger.debug("async-acc", "AccountListLoader::run() connected to DB");
- try (Cursor cursor = db.rawQuery(sql, new String[]{String.valueOf(profileId)})) {
- Logger.debug("async-acc", "AccountListLoader::run() executed query");
- while (cursor.moveToNext()) {
- if (isInterrupted())
- return;
-
- final long accId = cursor.getLong(3);
- final String accName = cursor.getString(0);
-// debug("accounts",
-// String.format("Read account '%s' from DB [%s]", accName,
-// profileId));
- String parentName = LedgerAccount.extractParentName(accName);
- LedgerAccount parent;
- if (parentName != null) {
- parent = map.get(parentName);
- if (parent == null)
- throw new IllegalStateException(
- String.format("Can't load account '%s': parent '%s' not loaded",
- accName, parentName));
- parent.setHasSubAccounts(true);
- }
- else
- parent = null;
-
- LedgerAccount acc = new LedgerAccount(profile, accName, parent);
- acc.setExpanded(cursor.getInt(1) == 1);
- acc.setAmountsExpanded(cursor.getInt(2) == 1);
- acc.setHasSubAccounts(false);
-
- try (Cursor c2 = db.rawQuery(
- "SELECT value, currency FROM account_values WHERE account_id = ?",
- new String[]{String.valueOf(accId)}))
- {
- while (c2.moveToNext()) {
- acc.addAmount(c2.getFloat(0), c2.getString(1));
- }
- }
-
- list.add(acc);
- map.put(accName, acc);
- }
- Logger.debug("async-acc", "AccountListLoader::run() query execution done");
- }
-
- if (isInterrupted())
- return;
-
- Logger.debug("async-acc", "AccountListLoader::run() posting new list");
- model.allAccounts = list;
- model.updateAccountsMap(list);
- model.updateDisplayedAccounts();
- }
- }
-
static class AccountListDisplayedFilter extends Thread {
private final MainModel model;
private final List<LedgerAccount> list;
Logger.debug("dFilter", "waiting for synchronized block");
Logger.debug("dFilter", String.format(Locale.US,
"entered synchronized block (about to examine %d accounts)", list.size()));
- newDisplayed.add(new AccountListItem.Header()); // header
+ newDisplayed.add(new AccountListItem.Header(Data.lastAccountsUpdateText)); // header
int count = 0;
for (LedgerAccount a : list) {
package net.ktnx.mobileledger.ui.account_summary;
-import android.content.Context;
import android.content.res.Resources;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.AsyncListDiffer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.async.DbOpQueue;
+import net.ktnx.mobileledger.databinding.AccountListRowBinding;
+import net.ktnx.mobileledger.databinding.AccountListSummaryRowBinding;
+import net.ktnx.mobileledger.db.Account;
+import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.model.AccountListItem;
-import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerAccount;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
-import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.activity.MainActivity;
-import net.ktnx.mobileledger.utils.Locker;
+import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.Misc;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Locale;
-import java.util.Observer;
import static net.ktnx.mobileledger.utils.Logger.debug;
-public class AccountSummaryAdapter
- extends RecyclerView.Adapter<AccountSummaryAdapter.LedgerRowHolder> {
+public class AccountSummaryAdapter extends RecyclerView.Adapter<AccountSummaryAdapter.RowHolder> {
public static final int AMOUNT_LIMIT = 3;
+ private static final int ITEM_TYPE_HEADER = 1;
+ private static final int ITEM_TYPE_ACCOUNT = 2;
private final AsyncListDiffer<AccountListItem> listDiffer;
- private final MainModel model;
- AccountSummaryAdapter(MainModel model) {
- this.model = model;
-
+ AccountSummaryAdapter() {
+ setHasStableIds(true);
+
listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<AccountListItem>() {
@Override
public boolean areItemsTheSame(@NotNull AccountListItem oldItem,
@NotNull AccountListItem newItem) {
final AccountListItem.Type oldType = oldItem.getType();
final AccountListItem.Type newType = newItem.getType();
- if (oldType == AccountListItem.Type.HEADER) {
- return newType == AccountListItem.Type.HEADER;
- }
if (oldType != newType)
return false;
+ if (oldType == AccountListItem.Type.HEADER)
+ return true;
return Misc.equalStrings(oldItem.getAccount()
.getName(), newItem.getAccount()
@Override
public boolean areContentsTheSame(@NotNull AccountListItem oldItem,
@NotNull AccountListItem newItem) {
- if (oldItem.getType()
- .equals(AccountListItem.Type.HEADER))
- return true;
- return oldItem.getAccount()
- .equals(newItem.getAccount());
+ return oldItem.sameContent(newItem);
}
});
}
-
- public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) {
- holder.bindToAccount(listDiffer.getCurrentList()
- .get(position));
+ @Override
+ public long getItemId(int position) {
+ if (position == 0)
+ return 0;
+ return listDiffer.getCurrentList()
+ .get(position)
+ .getAccount()
+ .getId();
+ }
+ public void onBindViewHolder(@NonNull RowHolder holder, int position) {
+ holder.bind(listDiffer.getCurrentList()
+ .get(position));
}
@NonNull
@Override
- public LedgerRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- View row = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.account_summary_row, parent, false);
- return new LedgerRowHolder(row);
+ public RowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+
+ final RowHolder result;
+ switch (viewType) {
+ case ITEM_TYPE_HEADER:
+ result = new HeaderRowHolder(
+ AccountListSummaryRowBinding.inflate(inflater, parent, false));
+ break;
+ case ITEM_TYPE_ACCOUNT:
+ result = new AccountRowHolder(
+ AccountListRowBinding.inflate(inflater, parent, false));
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + viewType);
+ }
+
+ Logger.debug("acc-ui", "Creating " + result);
+ return result;
}
@Override
return listDiffer.getCurrentList()
.size();
}
+ @Override
+ public int getItemViewType(int position) {
+ return (position == 0) ? ITEM_TYPE_HEADER : ITEM_TYPE_ACCOUNT;
+ }
public void setAccounts(List<AccountListItem> newList) {
- listDiffer.submitList(newList);
+ new Handler(Looper.getMainLooper()).post(() -> listDiffer.submitList(newList));
}
- class LedgerRowHolder extends RecyclerView.ViewHolder {
- private final TextView tvAccountName, tvAccountAmounts;
- private final ConstraintLayout row;
- private final View expanderContainer;
- private final View amountExpanderContainer;
- private final View lLastUpdate;
- private final TextView tvLastUpdate;
- private final View vAccountNameLayout;
- LedgerAccount mAccount;
- private AccountListItem.Type lastType;
- private Observer lastUpdateObserver;
- public LedgerRowHolder(@NonNull View itemView) {
+ static abstract class RowHolder extends RecyclerView.ViewHolder {
+ public RowHolder(@NonNull View itemView) {
super(itemView);
+ }
+ public abstract void bind(AccountListItem accountListItem);
+ }
- row = itemView.findViewById(R.id.account_summary_row);
- vAccountNameLayout = itemView.findViewById(R.id.account_name_layout);
- tvAccountName = itemView.findViewById(R.id.account_row_acc_name);
- tvAccountAmounts = itemView.findViewById(R.id.account_row_acc_amounts);
- expanderContainer = itemView.findViewById(R.id.account_expander_container);
- ImageView expander = itemView.findViewById(R.id.account_expander);
- amountExpanderContainer =
- itemView.findViewById(R.id.account_row_amounts_expander_container);
- lLastUpdate = itemView.findViewById(R.id.last_update_container);
- tvLastUpdate = itemView.findViewById(R.id.last_update_text);
-
- itemView.setOnLongClickListener(this::onItemLongClick);
- tvAccountName.setOnLongClickListener(this::onItemLongClick);
- tvAccountAmounts.setOnLongClickListener(this::onItemLongClick);
- expanderContainer.setOnLongClickListener(this::onItemLongClick);
- expander.setOnLongClickListener(this::onItemLongClick);
- row.setOnLongClickListener(this::onItemLongClick);
+ static class HeaderRowHolder extends RowHolder {
+ private final AccountListSummaryRowBinding b;
+ public HeaderRowHolder(@NonNull AccountListSummaryRowBinding binding) {
+ super(binding.getRoot());
+ b = binding;
+ }
+ @Override
+ public void bind(AccountListItem item) {
+ Resources r = itemView.getResources();
+ Logger.debug("acc", itemView.getContext()
+ .toString());
+ ((AccountListItem.Header) item).getText()
+ .observe((LifecycleOwner) itemView.getContext(),
+ b.lastUpdateText::setText);
+ }
+ }
- tvAccountName.setOnClickListener(v -> toggleAccountExpanded());
- expanderContainer.setOnClickListener(v -> toggleAccountExpanded());
- expander.setOnClickListener(v -> toggleAccountExpanded());
- tvAccountAmounts.setOnClickListener(v -> toggleAmountsExpanded());
+ class AccountRowHolder extends AccountSummaryAdapter.RowHolder {
+ private final AccountListRowBinding b;
+ public AccountRowHolder(@NonNull AccountListRowBinding binding) {
+ super(binding.getRoot());
+ b = binding;
+ itemView.setOnLongClickListener(this::onItemLongClick);
+ b.accountRowAccName.setOnLongClickListener(this::onItemLongClick);
+ b.accountRowAccAmounts.setOnLongClickListener(this::onItemLongClick);
+ b.accountExpanderContainer.setOnLongClickListener(this::onItemLongClick);
+ b.accountExpander.setOnLongClickListener(this::onItemLongClick);
+
+ b.accountRowAccName.setOnClickListener(v -> toggleAccountExpanded());
+ b.accountExpanderContainer.setOnClickListener(v -> toggleAccountExpanded());
+ b.accountExpander.setOnClickListener(v -> toggleAccountExpanded());
+ b.accountRowAccAmounts.setOnClickListener(v -> toggleAmountsExpanded());
}
private void toggleAccountExpanded() {
- if (!mAccount.hasSubAccounts())
+ LedgerAccount account = getAccount();
+ if (!account.hasSubAccounts())
return;
debug("accounts", "Account expander clicked");
- // make sure we use the same object as the one in the allAccounts list
- MobileLedgerProfile profile = mAccount.getProfile();
- if (profile == null) {
- return;
- }
- try (Locker ignored = model.lockAccountsForWriting()) {
- LedgerAccount realAccount = model.locateAccount(mAccount.getName());
- if (realAccount == null)
- return;
-
- mAccount = realAccount;
- mAccount.toggleExpanded();
- }
- expanderContainer.animate()
- .rotation(mAccount.isExpanded() ? 0 : 180);
- model.updateDisplayedAccounts();
-
- DbOpQueue.add("update accounts set expanded=? where name=? and profile_id=?",
- new Object[]{mAccount.isExpanded(), mAccount.getName(), profile.getId()
- });
-
+ AsyncTask.execute(() -> {
+ Account dbo = account.toDBO();
+ dbo.setExpanded(!dbo.isExpanded());
+ Logger.debug("accounts",
+ String.format(Locale.ROOT, "%s (%d) → %s", account.getName(), dbo.getId(),
+ dbo.isExpanded() ? "expanded" : "collapsed"));
+ DB.get()
+ .getAccountDAO()
+ .updateSync(dbo);
+ });
+ }
+ @NotNull
+ private LedgerAccount getAccount() {
+ return listDiffer.getCurrentList()
+ .get(getAdapterPosition())
+ .getAccount();
}
private void toggleAmountsExpanded() {
- if (mAccount.getAmountCount() <= AMOUNT_LIMIT)
+ LedgerAccount account = getAccount();
+ if (account.getAmountCount() <= AMOUNT_LIMIT)
return;
- mAccount.toggleAmountsExpanded();
- if (mAccount.amountsExpanded()) {
- tvAccountAmounts.setText(mAccount.getAmountsString());
- amountExpanderContainer.setVisibility(View.GONE);
+ account.toggleAmountsExpanded();
+ if (account.amountsExpanded()) {
+ b.accountRowAccAmounts.setText(account.getAmountsString());
+ b.accountRowAmountsExpanderContainer.setVisibility(View.GONE);
}
else {
- tvAccountAmounts.setText(mAccount.getAmountsString(AMOUNT_LIMIT));
- amountExpanderContainer.setVisibility(View.VISIBLE);
+ b.accountRowAccAmounts.setText(account.getAmountsString(AMOUNT_LIMIT));
+ b.accountRowAmountsExpanderContainer.setVisibility(View.VISIBLE);
}
- MobileLedgerProfile profile = mAccount.getProfile();
- if (profile == null)
- return;
-
- DbOpQueue.add("update accounts set amounts_expanded=? where name=? and profile=?",
- new Object[]{mAccount.amountsExpanded(), mAccount.getName(), profile.getId()
- });
-
+ AsyncTask.execute(() -> {
+ Account dbo = account.toDBO();
+ DB.get()
+ .getAccountDAO()
+ .updateSync(dbo);
+ });
}
private boolean onItemLongClick(View v) {
MainActivity activity = (MainActivity) v.getContext();
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- final String accountName = mAccount.getName();
+ final String accountName = getAccount().getName();
builder.setTitle(accountName);
builder.setItems(R.array.acc_ctx_menu, (dialog, which) -> {
if (which == 0) {// show transactions
builder.show();
return true;
}
- public void bindToAccount(AccountListItem item) {
- final AccountListItem.Type newType = item.getType();
- setType(newType);
-
- switch (newType) {
- case ACCOUNT:
- LedgerAccount acc = item.getAccount();
-
- debug("accounts", String.format(Locale.US, "Binding to '%s'", acc.getName()));
- Context ctx = row.getContext();
- Resources rm = ctx.getResources();
- mAccount = acc;
-
- row.setTag(acc);
-
- tvAccountName.setText(acc.getShortName());
-
- ConstraintLayout.LayoutParams lp =
- (ConstraintLayout.LayoutParams) tvAccountName.getLayoutParams();
- lp.setMarginStart(
- acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) /
- 3);
-
- if (acc.hasSubAccounts()) {
- expanderContainer.setVisibility(View.VISIBLE);
- expanderContainer.setRotation(acc.isExpanded() ? 0 : 180);
- }
- else {
- expanderContainer.setVisibility(View.GONE);
- }
-
- int amounts = acc.getAmountCount();
- if ((amounts > AMOUNT_LIMIT) && !acc.amountsExpanded()) {
- tvAccountAmounts.setText(acc.getAmountsString(AMOUNT_LIMIT));
- amountExpanderContainer.setVisibility(View.VISIBLE);
- }
- else {
- tvAccountAmounts.setText(acc.getAmountsString());
- amountExpanderContainer.setVisibility(View.GONE);
- }
-
- break;
- case HEADER:
- setLastUpdateText(Data.lastAccountsUpdateText.get());
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + newType);
+ @Override
+ public void bind(AccountListItem item) {
+ LedgerAccount acc = item.getAccount();
+
+ debug("accounts",
+ String.format(Locale.US, "Binding to '%s' to %s", acc.getName(), this));
+ Resources rm = b.getRoot()
+ .getContext()
+ .getResources();
+
+ b.accountRowAccName.setText(acc.getShortName());
+
+ ConstraintLayout.LayoutParams lp =
+ (ConstraintLayout.LayoutParams) b.accountNameLayout.getLayoutParams();
+ lp.setMarginStart(
+ acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) / 3);
+
+ if (acc.hasSubAccounts()) {
+ b.accountExpanderContainer.setVisibility(View.VISIBLE);
+ int wantedRotation = acc.isExpanded() ? 0 : 180;
+ if (b.accountExpanderContainer.getRotation() != wantedRotation) {
+ Logger.debug("acc-ui",
+ String.format(Locale.ROOT, "Rotating %s to %d", acc.getName(),
+ wantedRotation));
+ b.accountExpanderContainer.animate()
+ .rotation(wantedRotation);
+ }
}
-
- }
- void setLastUpdateText(String text) {
- tvLastUpdate.setText(text);
- }
- private void initLastUpdateObserver() {
- if (lastUpdateObserver != null)
- return;
-
- lastUpdateObserver = (o, arg) -> setLastUpdateText(Data.lastAccountsUpdateText.get());
-
- Data.lastAccountsUpdateText.addObserver(lastUpdateObserver);
- }
- private void dropLastUpdateObserver() {
- if (lastUpdateObserver == null)
- return;
-
- Data.lastAccountsUpdateText.deleteObserver(lastUpdateObserver);
- lastUpdateObserver = null;
- }
- private void setType(AccountListItem.Type newType) {
- if (newType == lastType)
- return;
-
- switch (newType) {
- case ACCOUNT:
- row.setLongClickable(true);
- amountExpanderContainer.setVisibility(View.VISIBLE);
- vAccountNameLayout.setVisibility(View.VISIBLE);
- tvAccountAmounts.setVisibility(View.VISIBLE);
- lLastUpdate.setVisibility(View.GONE);
- dropLastUpdateObserver();
- break;
- case HEADER:
- row.setLongClickable(false);
- tvAccountAmounts.setVisibility(View.GONE);
- amountExpanderContainer.setVisibility(View.GONE);
- vAccountNameLayout.setVisibility(View.GONE);
- lLastUpdate.setVisibility(View.VISIBLE);
- initLastUpdateObserver();
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + newType);
+ else {
+ b.accountExpanderContainer.setVisibility(View.GONE);
}
- lastType = newType;
+ int amounts = acc.getAmountCount();
+ if ((amounts > AMOUNT_LIMIT) && !acc.amountsExpanded()) {
+ b.accountRowAccAmounts.setText(acc.getAmountsString(AMOUNT_LIMIT));
+ b.accountRowAmountsExpanderContainer.setVisibility(View.VISIBLE);
+ }
+ else {
+ b.accountRowAccAmounts.setText(acc.getAmountsString());
+ b.accountRowAmountsExpanderContainer.setVisibility(View.GONE);
+ }
}
}
}
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
package net.ktnx.mobileledger.ui.account_summary;
import android.content.Context;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.db.AccountWithAmounts;
+import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.model.AccountListItem;
import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.model.LedgerAccount;
import net.ktnx.mobileledger.ui.FabManager;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.MobileLedgerListFragment;
import net.ktnx.mobileledger.ui.activity.MainActivity;
import net.ktnx.mobileledger.utils.Colors;
-import net.ktnx.mobileledger.utils.Logger;
import org.jetbrains.annotations.NotNull;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import static net.ktnx.mobileledger.utils.Logger.debug;
Data.backgroundTasksRunning.observe(this.getViewLifecycleOwner(),
this::onBackgroundTaskRunningChanged);
- modelAdapter = new AccountSummaryAdapter(model);
+ modelAdapter = new AccountSummaryAdapter();
MainActivity mainActivity = getMainActivity();
root = view.findViewById(R.id.account_root);
mainActivity.fabShouldShow();
if (mainActivity instanceof FabManager.FabHandler)
- FabManager.handle((FabManager.FabHandler) mainActivity, root);
+ FabManager.handle(mainActivity, root);
refreshLayout = view.findViewById(R.id.account_swipe_refresh_layout);
Colors.themeWatch.observe(getViewLifecycleOwner(), this::themeChanged);
model.scheduleTransactionListRetrieval();
});
- model.getDisplayedAccounts()
- .observe(getViewLifecycleOwner(), this::onAccountsChanged);
- }
- private void onAccountsChanged(List<AccountListItem> accounts) {
- Logger.debug("async-acc",
- String.format(Locale.US, "fragment: got new account list (%d items)",
- accounts.size()));
- modelAdapter.setAccounts(accounts);
+ DB.get()
+ .getAccountDAO()
+ .getAllWithAmounts(Data.getProfile()
+ .getId())
+ .observe(getViewLifecycleOwner(), list -> AsyncTask.execute(() -> {
+ List<AccountListItem> adapterList = new ArrayList<>();
+ adapterList.add(new AccountListItem.Header(Data.lastAccountsUpdateText));
+ HashMap<String, LedgerAccount> accMap = new HashMap<>();
+ for (AccountWithAmounts dbAcc : list) {
+ LedgerAccount parent = null;
+ String parentName = dbAcc.account.getParentName();
+ if (parentName != null)
+ parent = accMap.get(parentName);
+ if (parent != null)
+ parent.setHasSubAccounts(true);
+ final LedgerAccount account = LedgerAccount.fromDBO(dbAcc, parent);
+ if (account.isVisible())
+ adapterList.add(new AccountListItem.Account(account));
+ accMap.put(dbAcc.account.getName(), account);
+ }
+ modelAdapter.setAccounts(adapterList);
+ Data.lastUpdateAccountCount.postValue(adapterList.size() - 1);
+ }));
}
}
mainModel.clearTransactions();
if (haveProfile) {
- mainModel.scheduleAccountListReload();
Logger.debug("transactions", "requesting list reload");
mainModel.scheduleTransactionListReload();
Integer transactionCount = Data.lastUpdateTransactionCount.getValue();
Date lastUpdate = Data.lastUpdateDate.getValue();
if (lastUpdate == null) {
- Data.lastTransactionsUpdateText.set("----");
- Data.lastAccountsUpdateText.set("----");
+ Data.lastTransactionsUpdateText.setValue("----");
+ Data.lastAccountsUpdateText.setValue("----");
}
else {
- Data.lastTransactionsUpdateText.set(
+ Data.lastTransactionsUpdateText.setValue(
String.format(Objects.requireNonNull(Data.locale.getValue()),
templateForTransactions,
transactionCount == null ? 0 : transactionCount,
DateUtils.formatDateTime(this, lastUpdate.getTime(), formatFlags)));
- Data.lastAccountsUpdateText.set(
+ Data.lastAccountsUpdateText.setValue(
String.format(Objects.requireNonNull(Data.locale.getValue()),
templateForAccounts, accountCount == null ? 0 : accountCount,
DateUtils.formatDateTime(this, lastUpdate.getTime(), formatFlags)));
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
}
break;
case HEADER:
- holder.setLastUpdateText(Data.lastTransactionsUpdateText.get());
+ holder.setLastUpdateText(Data.lastTransactionsUpdateText.getValue());
break;
default:
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
import androidx.recyclerview.widget.RecyclerView;
import net.ktnx.mobileledger.R;
-import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.TransactionListItem;
import java.util.Observer;
this.vHeader = itemView.findViewById(R.id.last_update_container);
this.tvLastUpdate = itemView.findViewById(R.id.last_update_text);
}
- private void initLastUpdateObserver() {
- if (lastUpdateObserver != null)
- return;
-
- lastUpdateObserver = (o, arg) -> setLastUpdateText(Data.lastTransactionsUpdateText.get());
-
- Data.lastTransactionsUpdateText.addObserver(lastUpdateObserver);
- }
void setLastUpdateText(String text) {
tvLastUpdate.setText(text);
}
- private void dropLastUpdateObserver() {
- if (lastUpdateObserver == null)
- return;
-
- Data.lastTransactionsUpdateText.deleteObserver(lastUpdateObserver);
- lastUpdateObserver = null;
- }
void setType(TransactionListItem.Type newType) {
if (newType == lastType)
return;
vHeader.setVisibility(View.GONE);
vTransaction.setVisibility(View.VISIBLE);
vDelimiter.setVisibility(View.GONE);
- dropLastUpdateObserver();
break;
case DELIMITER:
vHeader.setVisibility(View.GONE);
vTransaction.setVisibility(View.GONE);
vDelimiter.setVisibility(View.VISIBLE);
- dropLastUpdateObserver();
break;
case HEADER:
vHeader.setVisibility(View.VISIBLE);
vTransaction.setVisibility(View.GONE);
vDelimiter.setVisibility(View.GONE);
- initLastUpdateObserver();
break;
default:
throw new IllegalStateException("Unexpected value: " + newType);
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+
+<!--
+ ~ 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/>.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/account_summary_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true"
+ android:longClickable="true"
+ >
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/account_name_layout"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toStartOf="@+id/account_row_acc_amounts"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ app:layout_constraintHorizontal_weight="3"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ >
+ <TextView
+ android:id="@+id/account_row_acc_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:gravity="center_vertical"
+ android:longClickable="true"
+ android:minHeight="@dimen/default_account_row_height"
+ android:paddingStart="8dp"
+ android:text="AccountName"
+ android:textAppearance="@android:style/TextAppearance.Material.Medium"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintEnd_toStartOf="@id/account_expander_container"
+ app:layout_constraintHorizontal_bias="0.0"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_max="wrap"
+ app:layout_constraintWidth_percent=".67"
+ tools:ignore="HardcodedText"
+ />
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/account_expander_container"
+ android:layout_width="@dimen/thumb_row_height"
+ android:layout_height="@dimen/default_account_row_height"
+ android:foregroundGravity="center_vertical"
+ android:minHeight="@dimen/default_account_row_height"
+ app:layout_constraintBottom_toBottomOf="@id/account_row_acc_name"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/account_row_acc_name"
+ app:layout_constraintTop_toTopOf="@id/account_row_acc_name"
+ >
+
+ <ImageView
+ android:id="@+id/account_expander"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:background="@drawable/ic_expand_less_black_24dp"
+ android:backgroundTint="?colorPrimary"
+ android:clickable="true"
+ android:contentDescription="@string/sub_accounts_expand_collapse_trigger_description"
+ android:focusable="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ <TextView
+ android:id="@+id/account_row_acc_amounts"
+ style="@style/account_summary_amounts"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="8dp"
+ android:gravity="center_vertical"
+ android:minHeight="@dimen/default_account_row_height"
+ android:text="USD 123,45\n678,90\nIRAUSD -17 000.00"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_weight="2"
+ app:layout_constraintStart_toEndOf="@id/account_name_layout"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_min="90sp"
+ tools:ignore="HardcodedText"
+ />
+
+ <FrameLayout
+ android:id="@+id/account_row_amounts_expander_container"
+ android:layout_width="0dp"
+ android:layout_height="18sp"
+ android:background="@drawable/fade_down_white"
+ app:layout_constraintBottom_toBottomOf="@id/account_row_acc_amounts"
+ app:layout_constraintEnd_toEndOf="@id/account_row_acc_amounts"
+ app:layout_constraintStart_toStartOf="@id/account_row_acc_amounts"
+ >
+
+ </FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+
+
+<!--
+ ~ 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/>.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/account_summary_row"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:animateLayoutChanges="true"
+ android:longClickable="true"
+ android:paddingTop="4dp"
+ >
+
+ <TextView
+ android:id="@+id/last_update_text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/activity_horizontal_margin"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:text="1 123 transactions as of 29.02.2020 13:37"
+ android:textAppearance="@android:style/TextAppearance.Material.Small"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:ignore="HardcodedText"
+ />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-
-
-<!--
- ~ 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/>.
- -->
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/account_summary_row"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:animateLayoutChanges="true"
- android:longClickable="true"
- >
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/account_name_layout"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintEnd_toStartOf="@+id/account_row_acc_amounts"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
- app:layout_constraintHorizontal_weight="3"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- >
- <TextView
- android:id="@+id/account_row_acc_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:gravity="center_vertical"
- android:longClickable="true"
- android:minHeight="@dimen/default_account_row_height"
- android:paddingStart="8dp"
- android:text="AccountName"
- android:textAppearance="@android:style/TextAppearance.Material.Medium"
- app:layout_constrainedWidth="true"
- app:layout_constraintEnd_toStartOf="@id/account_expander_container"
- app:layout_constraintHorizontal_bias="0.0"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintWidth_percent=".67"
- tools:ignore="HardcodedText"
- />
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/account_expander_container"
- android:layout_width="@dimen/thumb_row_height"
- android:layout_height="@dimen/default_account_row_height"
- android:foregroundGravity="center_vertical"
- android:minHeight="@dimen/default_account_row_height"
- app:layout_constraintBottom_toBottomOf="@id/account_row_acc_name"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@id/account_row_acc_name"
- app:layout_constraintTop_toTopOf="@id/account_row_acc_name"
- >
-
- <ImageView
- android:id="@+id/account_expander"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:background="@drawable/ic_expand_less_black_24dp"
- android:backgroundTint="?colorPrimary"
- android:clickable="true"
- android:contentDescription="@string/sub_accounts_expand_collapse_trigger_description"
- android:focusable="true"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- />
- </androidx.constraintlayout.widget.ConstraintLayout>
- </androidx.constraintlayout.widget.ConstraintLayout>
- <TextView
- android:id="@+id/account_row_acc_amounts"
- style="@style/account_summary_amounts"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginStart="12dp"
- android:layout_marginEnd="8dp"
- android:gravity="center_vertical"
- android:minHeight="@dimen/default_account_row_height"
- android:text="USD 123,45\n678,90\nIRAUSD -17 000.00"
- android:textAppearance="@style/TextAppearance.AppCompat.Medium"
- app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_weight="2"
- app:layout_constraintStart_toEndOf="@id/account_name_layout"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintWidth_min="90sp"
- tools:ignore="HardcodedText"
- />
-
- <FrameLayout
- android:id="@+id/account_row_amounts_expander_container"
- android:layout_width="0dp"
- android:layout_height="18sp"
- android:background="@drawable/fade_down_white"
- app:layout_constraintBottom_toBottomOf="@id/account_row_acc_amounts"
- app:layout_constraintEnd_toEndOf="@id/account_row_acc_amounts"
- app:layout_constraintStart_toStartOf="@id/account_row_acc_amounts"
- >
-
- </FrameLayout>
- <include layout="@layout/last_update_layout" />
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
/*
- * Copyright © 2020 Damyan Ivanov.
+ * Copyright © 2021 Damyan Ivanov.
* This file is part of MoLe.
* MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.2'
+ classpath 'com.android.tools.build:gradle:4.1.3'
// NOTE: Do not place your application dependencies here; they belong