public void put(LedgerTransaction transaction, SimpleDate date) {
if (done)
throw new IllegalStateException("Can't put new items after done()");
+
+ // first item
if (null == latestDate) {
+ list.add(new TransactionListItem());
latestDate = date;
list.add(new TransactionListItem(date, SimpleDate.today().month != date.month));
}
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.SimpleDate;
+import org.jetbrains.annotations.NotNull;
+
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public static class Params {
public final MainModel model;
public final SimpleDate date;
- public Params(MainModel model, SimpleDate date) {
+ public Params(@NotNull MainModel model, @NotNull SimpleDate date) {
this.model = model;
this.date = date;
}
static class TransactionListItemComparator implements Comparator<TransactionListItem> {
@Override
- public int compare(TransactionListItem a, TransactionListItem b) {
+ public int compare(@NotNull TransactionListItem a, @NotNull TransactionListItem b) {
+ if (a.getType() == TransactionListItem.Type.HEADER)
+ return +1;
+ if (b.getType() == TransactionListItem.Type.HEADER)
+ return -1;
final SimpleDate aDate = a.getDate();
final SimpleDate bDate = b.getDate();
int res = aDate.compareTo(bDate);
--- /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 androidx.annotation.NonNull;
+
+import org.jetbrains.annotations.NotNull;
+
+public class AccountListItem {
+ private final Type type;
+ private LedgerAccount account;
+ public AccountListItem(@NotNull LedgerAccount account) {
+ this.type = Type.ACCOUNT;
+ this.account = account;
+ }
+ public AccountListItem() {
+ this.type = Type.HEADER;
+ }
+ @NonNull
+ public Type getType() {
+ return type;
+ }
+ @NotNull
+ public LedgerAccount getAccount() {
+ if (type != Type.ACCOUNT)
+ throw new IllegalStateException(
+ String.format("Item type is not %s, but %s", Type.ACCOUNT, type));
+ return account;
+ }
+ public enum Type {ACCOUNT, HEADER}
+}
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.util.ArrayList;
+import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
public static final MutableLiveData<Boolean> currencyGap = new MutableLiveData<>(true);
public static final MutableLiveData<Locale> locale = new MutableLiveData<>();
public static final MutableLiveData<Boolean> drawerOpen = new MutableLiveData<>(false);
+ public static final MutableLiveData<Date> lastUpdateLiveData = new MutableLiveData<>(null);
+ public static final ObservableValue<Long> lastUpdate = new ObservableValue<>();
private static final MutableLiveData<MobileLedgerProfile> profile =
new InertMutableLiveData<>();
private static final AtomicInteger backgroundTaskCount = new AtomicInteger(0);
import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.utils.SimpleDate;
+import org.jetbrains.annotations.NotNull;
+
public class TransactionListItem {
private final Type type;
private SimpleDate date;
private boolean monthShown;
private LedgerTransaction transaction;
- public TransactionListItem(SimpleDate date, boolean monthShown) {
+ public TransactionListItem(@NotNull SimpleDate date, boolean monthShown) {
this.type = Type.DELIMITER;
this.date = date;
this.monthShown = monthShown;
}
- public TransactionListItem(LedgerTransaction transaction) {
+ public TransactionListItem(@NotNull LedgerTransaction transaction) {
this.type = Type.TRANSACTION;
this.transaction = transaction;
}
+ public TransactionListItem() {
+ this.type = Type.HEADER;
+ }
@NonNull
public Type getType() {
return type;
public SimpleDate getDate() {
if (date != null)
return date;
+ if (type == Type.HEADER)
+ throw new IllegalStateException("Header item has no date");
transaction.loadData(App.getDatabase());
return transaction.getDate();
}
public boolean isMonthShown() {
return monthShown;
}
+ @NotNull
public LedgerTransaction getTransaction() {
+ if (type != Type.TRANSACTION)
+ throw new IllegalStateException(
+ String.format("Item type is not %s, but %s", Type.TRANSACTION, type));
return transaction;
}
- public enum Type {TRANSACTION, DELIMITER}
+ public enum Type {TRANSACTION, DELIMITER, HEADER}
}
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.model.Data;
import net.ktnx.mobileledger.model.LedgerAccount;
import net.ktnx.mobileledger.model.LedgerTransaction;
public class MainModel extends ViewModel {
public final MutableLiveData<Integer> foundTransactionItemIndex = new MutableLiveData<>(null);
- public final MutableLiveData<Date> lastUpdateDate = 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<LedgerAccount>> displayedAccounts = new MutableLiveData<>();
+ private final MutableLiveData<List<AccountListItem>> displayedAccounts =
+ new MutableLiveData<>();
private final Locker accountsLocker = new Locker();
private final MutableLiveData<String> updateError = new MutableLiveData<>();
+ private final Map<String, LedgerAccount> accountMap = new HashMap<>();
private MobileLedgerProfile profile;
private List<LedgerAccount> allAccounts = new ArrayList<>();
- private final Map<String, LedgerAccount> accountMap = new HashMap<>();
private SimpleDate firstTransactionDate;
private SimpleDate lastTransactionDate;
transient private RetrieveTransactionsTask retrieveTransactionsTask;
debug("db", "Updating transaction value stamp");
Date now = new Date();
profile.setLongOption(MLDB.OPT_LAST_SCRAPE, now.getTime());
- lastUpdateDate.postValue(now);
+ Data.lastUpdateLiveData.postValue(now);
}
public void scheduleTransactionListReload() {
UpdateTransactionsTask task = new UpdateTransactionsTask();
updateAccountsMap(allAccounts);
}
}
- public LiveData<List<LedgerAccount>> getDisplayedAccounts() {
+ public LiveData<List<AccountListItem>> getDisplayedAccounts() {
return displayedAccounts;
}
synchronized public void scheduleAccountListReload() {
}
@Override
public void run() {
- List<LedgerAccount> newDisplayed = new ArrayList<>();
+ List<AccountListItem> newDisplayed = new ArrayList<>();
Logger.debug("dFilter", "waiting for synchronized block");
Logger.debug("dFilter", String.format(Locale.US,
"entered synchronized block (about to examine %d accounts)", list.size()));
+ newDisplayed.add(new AccountListItem()); // header
for (LedgerAccount a : list) {
- if (isInterrupted()) {
+ if (isInterrupted())
return;
- }
- if (a.isVisible()) {
- newDisplayed.add(a);
- }
+ if (a.isVisible())
+ newDisplayed.add(new AccountListItem(a));
}
if (!isInterrupted()) {
model.displayedAccounts.postValue(newDisplayed);
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.DbOpQueue;
+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 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 static final int AMOUNT_LIMIT = 3;
- private final AsyncListDiffer<LedgerAccount> listDiffer;
+ private final AsyncListDiffer<AccountListItem> listDiffer;
private final MainModel model;
AccountSummaryAdapter(MainModel model) {
this.model = model;
- listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<LedgerAccount>() {
+ listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<AccountListItem>() {
@Override
- public boolean areItemsTheSame(@NotNull LedgerAccount oldItem,
- @NotNull LedgerAccount newItem) {
- return TextUtils.equals(oldItem.getName(), newItem.getName());
+ 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;
+
+ return TextUtils.equals(oldItem.getAccount()
+ .getName(), newItem.getAccount()
+ .getName());
}
@Override
- public boolean areContentsTheSame(@NotNull LedgerAccount oldItem,
- @NotNull LedgerAccount newItem) {
- return oldItem.equals(newItem);
+ 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 listDiffer.getCurrentList()
.size();
}
- public void setAccounts(List<LedgerAccount> newList) {
+ public void setAccounts(List<AccountListItem> newList) {
listDiffer.submitList(newList);
}
class LedgerRowHolder extends RecyclerView.ViewHolder {
- final TextView tvAccountName, tvAccountAmounts;
- final ConstraintLayout row;
- final View expanderContainer;
- final ImageView expander;
- final View accountExpanderContainer;
+ 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) {
super(itemView);
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);
- expander = itemView.findViewById(R.id.account_expander);
- accountExpanderContainer =
+ 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);
expanderContainer.setOnClickListener(v -> toggleAccountExpanded());
expander.setOnClickListener(v -> toggleAccountExpanded());
tvAccountAmounts.setOnClickListener(v -> toggleAmountsExpanded());
+
}
private void toggleAccountExpanded() {
if (!mAccount.hasSubAccounts())
mAccount.toggleAmountsExpanded();
if (mAccount.amountsExpanded()) {
tvAccountAmounts.setText(mAccount.getAmountsString());
- accountExpanderContainer.setVisibility(View.GONE);
+ amountExpanderContainer.setVisibility(View.GONE);
}
else {
tvAccountAmounts.setText(mAccount.getAmountsString(AMOUNT_LIMIT));
- accountExpanderContainer.setVisibility(View.VISIBLE);
+ amountExpanderContainer.setVisibility(View.VISIBLE);
}
MobileLedgerProfile profile = mAccount.getProfile();
builder.show();
return true;
}
- public void bindToAccount(LedgerAccount acc) {
- Logger.debug("accounts", String.format(Locale.US, "Binding to '%s'", acc.getName()));
- Context ctx = row.getContext();
- Resources rm = ctx.getResources();
- mAccount = acc;
+ public void bindToAccount(AccountListItem item) {
+ final AccountListItem.Type newType = item.getType();
+ setType(newType);
- row.setTag(acc);
+ switch (newType) {
+ case ACCOUNT:
+ LedgerAccount acc = item.getAccount();
- tvAccountName.setText(acc.getShortName());
+ debug("accounts", String.format(Locale.US, "Binding to '%s'", acc.getName()));
+ Context ctx = row.getContext();
+ Resources rm = ctx.getResources();
+ mAccount = acc;
- ConstraintLayout.LayoutParams lp =
- (ConstraintLayout.LayoutParams) tvAccountName.getLayoutParams();
- lp.setMarginStart(
- acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) / 3);
+ row.setTag(acc);
- if (acc.hasSubAccounts()) {
- expanderContainer.setVisibility(View.VISIBLE);
- expanderContainer.setRotation(acc.isExpanded() ? 0 : 180);
- }
- else {
- expanderContainer.setVisibility(View.GONE);
- }
+ tvAccountName.setText(acc.getShortName());
- int amounts = acc.getAmountCount();
- if ((amounts > AMOUNT_LIMIT) && !acc.amountsExpanded()) {
- tvAccountAmounts.setText(acc.getAmountsString(AMOUNT_LIMIT));
- accountExpanderContainer.setVisibility(View.VISIBLE);
+ 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.lastUpdate.get());
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + newType);
}
- else {
- tvAccountAmounts.setText(acc.getAmountsString());
- accountExpanderContainer.setVisibility(View.GONE);
+
+ }
+ void setLastUpdateText(long lastUpdate) {
+ final int formatFlags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NUMERIC_DATE;
+ tvLastUpdate.setText((lastUpdate == 0) ? "----" : DateUtils.formatDateTime(
+ tvLastUpdate.getContext(), lastUpdate, formatFlags));
+ }
+ private void initLastUpdateObserver() {
+ if (lastUpdateObserver != null)
+ return;
+
+ lastUpdateObserver = (o, arg) -> setLastUpdateText(Data.lastUpdate.get());
+
+ Data.lastUpdate.addObserver(lastUpdateObserver);
+ }
+ private void dropLastUpdateObserver() {
+ if (lastUpdateObserver == null)
+ return;
+
+ Data.lastUpdate.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);
}
+
+ lastType = newType;
}
}
}
import androidx.recyclerview.widget.RecyclerView;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.AccountListItem;
import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.model.LedgerAccount;
import net.ktnx.mobileledger.ui.MainModel;
import net.ktnx.mobileledger.ui.MobileLedgerListFragment;
import net.ktnx.mobileledger.ui.activity.MainActivity;
model.getDisplayedAccounts()
.observe(getViewLifecycleOwner(), this::onAccountsChanged);
}
- private void onAccountsChanged(List<LedgerAccount> accounts) {
+ private void onAccountsChanged(List<AccountListItem> accounts) {
Logger.debug("async-acc",
String.format(Locale.US, "fragment: got new account list (%d items)",
accounts.size()));
import android.os.Bundle;
import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import org.jetbrains.annotations.NotNull;
-import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
.setValue(savedInstanceState.getString(STATE_ACC_FILTER, null));
}
- mainModel.lastUpdateDate.observe(this, this::updateLastUpdateDisplay);
-
findViewById(R.id.btn_no_profiles_add).setOnClickListener(
v -> startEditProfileActivity(null));
updateLastUpdateTextFromDB();
}
- private void updateLastUpdateDisplay(Date newValue) {
- ViewGroup l = findViewById(R.id.transactions_last_update_layout);
- TextView v = findViewById(R.id.transactions_last_update);
- if (newValue == null) {
- l.setVisibility(View.INVISIBLE);
- Logger.debug("main", "no last update date :(");
- }
- else {
- final String text = DateFormat.getDateTimeInstance()
- .format(newValue);
- v.setText(text);
- l.setVisibility(View.VISIBLE);
- Logger.debug("main", String.format("Date formatted: %s", text));
- }
- }
private void profileThemeChanged() {
storeThemeIdInPrefs(profile.getThemeHue());
// un-hook all observed LiveData
Data.removeProfileObservers(this);
Data.profiles.removeObservers(this);
- mainModel.lastUpdateDate.removeObservers(this);
+ Data.lastUpdateLiveData.removeObservers(this);
recreate();
}
if (profile == null)
return;
- long last_update = profile.getLongOption(MLDB.OPT_LAST_SCRAPE, 0L);
+ long lastUpdate = profile.getLongOption(MLDB.OPT_LAST_SCRAPE, 0L);
- Logger.debug("transactions",
- String.format(Locale.ENGLISH, "Last update = %d", last_update));
- if (last_update == 0) {
- mainModel.lastUpdateDate.postValue(null);
+ Logger.debug("transactions", String.format(Locale.ENGLISH, "Last update = %d", lastUpdate));
+ if (lastUpdate == 0) {
+ Data.lastUpdateLiveData.postValue(null);
}
else {
- mainModel.lastUpdateDate.postValue(new Date(last_update));
+ Data.lastUpdateLiveData.postValue(new Date(lastUpdate));
}
- scheduleDataRetrievalIfStale(last_update);
+
+ // this is unfortunate, but it appears we need a two-stage rocket to make
+ // a value reach a recycler view item holder. first stage is a regular
+ // LiveData that can be observed by an activity (this).
+ // the second stage forwards the changes, in the UI thread, to the
+ // observable value, observed by the view holders.
+ // view holders can't observe the LiveData because they don't have
+ // access to lifecycle owners. oh, also the value is updated by a thread
+ // so it must be tunnelled by an activity for it to reach the view
+ // holders in the UI thread
+ Data.lastUpdateLiveData.observe(this, date -> runOnUiThread(
+ () -> Data.lastUpdate.set((date == null) ? 0 : date.getTime())));
+ scheduleDataRetrievalIfStale(lastUpdate);
}
public void onStopTransactionRefreshClick(View view) {
import net.ktnx.mobileledger.App;
import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
import net.ktnx.mobileledger.model.TransactionListItem;
return oldItem.getTransaction()
.getId() == newItem.getTransaction()
.getId();
+ case HEADER:
+ return true; // there can be only one header
default:
throw new IllegalStateException(
String.format(Locale.US, "Unexpected transaction item type %s",
case TRANSACTION:
return oldItem.getTransaction()
.equals(newItem.getTransaction());
+ case HEADER:
+ // headers don't differ in their contents. they observe the last update
+ // date and react to its changes
+ return true;
default:
throw new IllegalStateException(
String.format(Locale.US, "Unexpected transaction item type %s",
if (item == null)
return;
- switch (item.getType()) {
+ final TransactionListItem.Type newType = item.getType();
+ holder.setType(newType);
+
+ switch (newType) {
case TRANSACTION:
- holder.vTransaction.setVisibility(View.VISIBLE);
- holder.vDelimiter.setVisibility(View.GONE);
LedgerTransaction tr = item.getTransaction();
// debug("transactions", String.format("Filling position %d with %d
break;
case DELIMITER:
SimpleDate date = item.getDate();
- holder.vTransaction.setVisibility(View.GONE);
- holder.vDelimiter.setVisibility(View.VISIBLE);
holder.tvDelimiterDate.setText(DateFormat.getDateInstance()
.format(date.toDate()));
if (item.isMonthShown()) {
holder.vDelimiterThick.setVisibility(View.GONE);
}
break;
+ case HEADER:
+ holder.setLastUpdateText(Data.lastUpdate.get());
+
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + newType);
}
}
-
@NonNull
@Override
public TransactionRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
package net.ktnx.mobileledger.ui.transaction_list;
+import android.text.format.DateUtils;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
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;
class TransactionRowHolder extends RecyclerView.ViewHolder {
final TextView tvDescription;
final CardView vTransaction;
final TextView tvDelimiterMonth, tvDelimiterDate;
final View vDelimiterThick;
+ final View vHeader;
+ final TextView tvLastUpdate;
+ TransactionListItem.Type lastType;
+ private Observer lastUpdateObserver;
public TransactionRowHolder(@NonNull View itemView) {
super(itemView);
this.row = itemView.findViewById(R.id.transaction_row);
this.tvDelimiterDate = itemView.findViewById(R.id.transaction_delimiter_date);
this.tvDelimiterMonth = itemView.findViewById(R.id.transaction_delimiter_month);
this.vDelimiterThick = itemView.findViewById(R.id.transaction_delimiter_thick);
+ 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.lastUpdate.get());
+
+ Data.lastUpdate.addObserver(lastUpdateObserver);
+ }
+ void setLastUpdateText(long lastUpdate) {
+ final int formatFlags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR |
+ DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NUMERIC_DATE;
+ tvLastUpdate.setText((lastUpdate == 0) ? "----"
+ : DateUtils.formatDateTime(tvLastUpdate.getContext(),
+ lastUpdate, formatFlags));
+ }
+ private void dropLastUpdateObserver() {
+ if (lastUpdateObserver == null)
+ return;
+
+ Data.lastUpdate.deleteObserver(lastUpdateObserver);
+ lastUpdateObserver = null;
+ }
+ void setType(TransactionListItem.Type newType) {
+ if (newType == lastType)
+ return;
+
+ switch (newType) {
+ case TRANSACTION:
+ 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);
+ }
+
+ lastType = newType;
}
}
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"
- android:contentDescription="@string/sub_accounts_expand_collapse_trigger_description"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
>
</FrameLayout>
-
+ <include layout="@layout/last_update_layout" />
</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/last_update_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ >
+ <TextView
+ android:id="@+id/last_update_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:text="@string/transactions_last_update_label"
+ android:textAppearance="@android:style/TextAppearance.Material.Small"
+ app:layout_constraintEnd_toStartOf="@id/last_update_text"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+
+ <TextView
+ android:id="@+id/last_update_text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/activity_horizontal_margin"
+ android:layout_weight="1"
+ android:text="\?"
+ android:textAppearance="@android:style/TextAppearance.Material.Small"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:ignore="HardcodedText"
+ />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar">
- <androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/transactions_last_update_layout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:elevation="24dp"
- android:orientation="horizontal"
- >
-
- <TextView
- android:id="@+id/transaction_last_update_label"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:text="@string/transactions_last_update_label"
- android:textAppearance="@android:style/TextAppearance.Material.Small"
- app:layout_constraintEnd_toStartOf="@id/transactions_last_update"
- app:layout_constraintTop_toTopOf="parent"
- />
-
- <TextView
- android:id="@+id/transactions_last_update"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="\?"
- android:textAppearance="@android:style/TextAppearance.Material.Small"
- tools:ignore="HardcodedText"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- />
- </androidx.constraintlayout.widget.ConstraintLayout>
-
<LinearLayout
android:id="@+id/transaction_progress_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:visibility="visible"
+ app:cardCornerRadius="0dp"
app:cardElevation="2dp"
app:cardUseCompatPadding="false"
- app:cardCornerRadius="0dp"
>
<androidx.constraintlayout.widget.ConstraintLayout
/>
<TextView
android:id="@+id/transaction_comment"
+ style="@style/transaction_list_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginTop="0dp"
android:text="Comment text"
tools:ignore="HardcodedText"
- style="@style/transaction_list_comment"
/>
</LinearLayout>
/>
</androidx.constraintlayout.widget.ConstraintLayout>
+ <include layout="@layout/last_update_layout" />
</FrameLayout>
\ No newline at end of file