From 2d85826653a8ba3e619afc83c5c91216a7fdb0b6 Mon Sep 17 00:00:00 2001 From: Damyan Ivanov Date: Fri, 18 Sep 2020 18:54:30 +0300 Subject: [PATCH] convert the last update global header to a list header to avoid taking up precious space, and to help animation of transactions coming in the front of the list -- before they were silently inserted above the first, in above the visible scroller window. now there is an item fixed at the first position --- .../async/TransactionAccumulator.java | 3 + .../async/TransactionDateFinder.java | 10 +- .../mobileledger/model/AccountListItem.java | 46 +++++ .../net/ktnx/mobileledger/model/Data.java | 4 + .../model/TransactionListItem.java | 17 +- .../net/ktnx/mobileledger/ui/MainModel.java | 22 +-- .../AccountSummaryAdapter.java | 178 +++++++++++++----- .../AccountSummaryFragment.java | 4 +- .../ui/activity/MainActivity.java | 46 ++--- .../TransactionListAdapter.java | 23 ++- .../TransactionRowHolder.java | 62 ++++++ .../main/res/layout/account_summary_row.xml | 4 +- .../main/res/layout/last_update_layout.xml | 51 +++++ app/src/main/res/layout/main_app_layout.xml | 33 ---- .../main/res/layout/transaction_list_row.xml | 5 +- 15 files changed, 376 insertions(+), 132 deletions(-) create mode 100644 app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java create mode 100644 app/src/main/res/layout/last_update_layout.xml diff --git a/app/src/main/java/net/ktnx/mobileledger/async/TransactionAccumulator.java b/app/src/main/java/net/ktnx/mobileledger/async/TransactionAccumulator.java index 1b964256..43d36e1f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/TransactionAccumulator.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/TransactionAccumulator.java @@ -36,7 +36,10 @@ public class TransactionAccumulator { 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)); } diff --git a/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java b/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java index 6c261f68..608b4771 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/TransactionDateFinder.java @@ -24,6 +24,8 @@ import net.ktnx.mobileledger.ui.MainModel; 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; @@ -61,7 +63,7 @@ public class TransactionDateFinder extends AsyncTask { @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); diff --git a/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java b/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java new file mode 100644 index 00000000..5fd95171 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/model/AccountListItem.java @@ -0,0 +1,46 @@ +/* + * 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 . + */ + +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} +} diff --git a/app/src/main/java/net/ktnx/mobileledger/model/Data.java b/app/src/main/java/net/ktnx/mobileledger/model/Data.java index 74e274d6..66738cb0 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/Data.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/Data.java @@ -32,9 +32,11 @@ import net.ktnx.mobileledger.utils.LockHolder; import net.ktnx.mobileledger.utils.Locker; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.MLDB; +import net.ktnx.mobileledger.utils.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; @@ -54,6 +56,8 @@ public final class Data { public static final MutableLiveData currencyGap = new MutableLiveData<>(true); public static final MutableLiveData locale = new MutableLiveData<>(); public static final MutableLiveData drawerOpen = new MutableLiveData<>(false); + public static final MutableLiveData lastUpdateLiveData = new MutableLiveData<>(null); + public static final ObservableValue lastUpdate = new ObservableValue<>(); private static final MutableLiveData profile = new InertMutableLiveData<>(); private static final AtomicInteger backgroundTaskCount = new AtomicInteger(0); diff --git a/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java b/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java index 6f1ee9eb..1e62ff13 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java @@ -22,20 +22,25 @@ import androidx.annotation.NonNull; 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; @@ -44,14 +49,20 @@ public class TransactionListItem { 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} } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java index 5bc2554c..6d640d62 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/MainModel.java @@ -32,6 +32,7 @@ import net.ktnx.mobileledger.App; import net.ktnx.mobileledger.async.RetrieveTransactionsTask; import net.ktnx.mobileledger.async.TransactionAccumulator; import net.ktnx.mobileledger.async.UpdateTransactionsTask; +import net.ktnx.mobileledger.model.AccountListItem; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerAccount; import net.ktnx.mobileledger.model.LedgerTransaction; @@ -55,17 +56,17 @@ import static net.ktnx.mobileledger.utils.Logger.debug; public class MainModel extends ViewModel { public final MutableLiveData foundTransactionItemIndex = new MutableLiveData<>(null); - public final MutableLiveData lastUpdateDate = new MutableLiveData<>(null); private final MutableLiveData updatingFlag = new MutableLiveData<>(false); private final MutableLiveData accountFilter = new MutableLiveData<>(); private final MutableLiveData> displayedTransactions = new MutableLiveData<>(new ArrayList<>()); - private final MutableLiveData> displayedAccounts = new MutableLiveData<>(); + private final MutableLiveData> displayedAccounts = + new MutableLiveData<>(); private final Locker accountsLocker = new Locker(); private final MutableLiveData updateError = new MutableLiveData<>(); + private final Map accountMap = new HashMap<>(); private MobileLedgerProfile profile; private List allAccounts = new ArrayList<>(); - private final Map accountMap = new HashMap<>(); private SimpleDate firstTransactionDate; private SimpleDate lastTransactionDate; transient private RetrieveTransactionsTask retrieveTransactionsTask; @@ -127,7 +128,7 @@ public class MainModel extends ViewModel { 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(); @@ -209,7 +210,7 @@ public class MainModel extends ViewModel { updateAccountsMap(allAccounts); } } - public LiveData> getDisplayedAccounts() { + public LiveData> getDisplayedAccounts() { return displayedAccounts; } synchronized public void scheduleAccountListReload() { @@ -355,18 +356,17 @@ public class MainModel extends ViewModel { } @Override public void run() { - List newDisplayed = new ArrayList<>(); + List newDisplayed = new ArrayList<>(); Logger.debug("dFilter", "waiting for synchronized block"); Logger.debug("dFilter", String.format(Locale.US, "entered synchronized block (about to examine %d accounts)", list.size())); + newDisplayed.add(new AccountListItem()); // header 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); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java index 30025c28..c6c90915 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java @@ -20,6 +20,7 @@ package net.ktnx.mobileledger.ui.account_summary; 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; @@ -35,38 +36,54 @@ import androidx.recyclerview.widget.RecyclerView; 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 { public static final int AMOUNT_LIMIT = 3; - private final AsyncListDiffer listDiffer; + private final AsyncListDiffer listDiffer; private final MainModel model; AccountSummaryAdapter(MainModel model) { this.model = model; - listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback() { + listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback() { @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()); } }); } @@ -89,26 +106,33 @@ public class AccountSummaryAdapter return listDiffer.getCurrentList() .size(); } - public void setAccounts(List newList) { + public void setAccounts(List 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); @@ -121,6 +145,7 @@ public class AccountSummaryAdapter expanderContainer.setOnClickListener(v -> toggleAccountExpanded()); expander.setOnClickListener(v -> toggleAccountExpanded()); tvAccountAmounts.setOnClickListener(v -> toggleAmountsExpanded()); + } private void toggleAccountExpanded() { if (!mAccount.hasSubAccounts()) @@ -156,11 +181,11 @@ public class AccountSummaryAdapter 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(); @@ -189,38 +214,103 @@ public class AccountSummaryAdapter 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; } } } diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java index 29003b82..5f4ffe57 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryFragment.java @@ -31,8 +31,8 @@ import androidx.recyclerview.widget.LinearLayoutManager; 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; @@ -102,7 +102,7 @@ public class AccountSummaryFragment extends MobileLedgerListFragment { model.getDisplayedAccounts() .observe(getViewLifecycleOwner(), this::onAccountsChanged); } - private void onAccountsChanged(List accounts) { + private void onAccountsChanged(List accounts) { Logger.debug("async-acc", String.format(Locale.US, "fragment: got new account list (%d items)", accounts.size())); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java index 1674c029..9082695f 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java @@ -29,7 +29,6 @@ import android.os.Build; 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; @@ -66,7 +65,6 @@ import net.ktnx.mobileledger.utils.MLDB; import org.jetbrains.annotations.NotNull; -import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -235,8 +233,6 @@ public class MainActivity extends ProfileThemedActivity { .setValue(savedInstanceState.getString(STATE_ACC_FILTER, null)); } - mainModel.lastUpdateDate.observe(this, this::updateLastUpdateDisplay); - findViewById(R.id.btn_no_profiles_add).setOnClickListener( v -> startEditProfileActivity(null)); @@ -460,28 +456,13 @@ public class MainActivity extends ProfileThemedActivity { 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(); } @@ -569,17 +550,28 @@ public class MainActivity extends ProfileThemedActivity { 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) { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java index 492c6151..94712cbd 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java @@ -39,6 +39,7 @@ import androidx.recyclerview.widget.RecyclerView; 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; @@ -76,6 +77,8 @@ public class TransactionListAdapter extends RecyclerView.Adapter 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; } } diff --git a/app/src/main/res/layout/account_summary_row.xml b/app/src/main/res/layout/account_summary_row.xml index 8112c2a7..4f345e7d 100644 --- a/app/src/main/res/layout/account_summary_row.xml +++ b/app/src/main/res/layout/account_summary_row.xml @@ -77,12 +77,12 @@ 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" /> @@ -118,5 +118,5 @@ > - + \ No newline at end of file diff --git a/app/src/main/res/layout/last_update_layout.xml b/app/src/main/res/layout/last_update_layout.xml new file mode 100644 index 00000000..e9ddb485 --- /dev/null +++ b/app/src/main/res/layout/last_update_layout.xml @@ -0,0 +1,51 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main_app_layout.xml b/app/src/main/res/layout/main_app_layout.xml index d9f4b426..ffee1994 100644 --- a/app/src/main/res/layout/main_app_layout.xml +++ b/app/src/main/res/layout/main_app_layout.xml @@ -70,39 +70,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/toolbar"> - - - - - - - @@ -142,5 +142,6 @@ /> + \ No newline at end of file -- 2.39.2