X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fui%2Faccount_summary%2FAccountSummaryAdapter.java;h=dcc16f3678ed3885605d4b85de77653f1f957b9c;hb=HEAD;hp=0540438ddcd6c47e4e7d9e873c393cd4a50cdb66;hpb=29fa90b17cbb87f0b16f3607f0628fe0057d6560;p=mobile-ledger.git 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 0540438d..dcc16f36 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2020 Damyan Ivanov. + * Copyright © 2024 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 @@ -17,138 +17,352 @@ package net.ktnx.mobileledger.ui.account_summary; -import android.content.Context; +import static net.ktnx.mobileledger.utils.Logger.debug; + import android.content.res.Resources; -import android.util.Log; 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.annotation.Nullable; 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.model.Data; +import net.ktnx.mobileledger.dao.BaseDAO; +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.LedgerAccount; import net.ktnx.mobileledger.ui.activity.MainActivity; -import net.ktnx.mobileledger.utils.LockHolder; +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; -public class AccountSummaryAdapter - extends RecyclerView.Adapter { +public class AccountSummaryAdapter extends RecyclerView.Adapter { 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 listDiffer; - AccountSummaryAdapter() { } + AccountSummaryAdapter() { + setHasStableIds(true); - public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) { - try (LockHolder lh = Data.accounts.lockForReading()) { - LedgerAccount acc = Data.accounts.get(position); - Context ctx = holder.row.getContext(); - Resources rm = ctx.getResources(); + listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback() { + @Nullable + @Override + public Object getChangePayload(@NonNull AccountListItem oldItem, + @NonNull AccountListItem newItem) { + Change changes = new Change(); - holder.row.setTag(acc); - holder.row.setVisibility(View.VISIBLE); - holder.tvAccountName.setText(acc.getShortName()); - ConstraintLayout.LayoutParams lp = - (ConstraintLayout.LayoutParams) holder.tvAccountName.getLayoutParams(); - lp.setMarginStart( - acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) / 3); - holder.expanderContainer.setVisibility( - acc.hasSubAccounts() ? View.VISIBLE : View.GONE); - holder.expanderContainer.setRotation(acc.isExpanded() ? 0 : 180); - int amounts = acc.getAmountCount(); - if ((amounts > AMOUNT_LIMIT) && !acc.amountsExpanded()) { - holder.tvAccountAmounts.setText(acc.getAmountsString(AMOUNT_LIMIT)); - holder.accountExpanderContainer.setVisibility(View.VISIBLE); - } - else { - holder.tvAccountAmounts.setText(acc.getAmountsString()); - holder.accountExpanderContainer.setVisibility(View.GONE); - } + final LedgerAccount oldAcc = oldItem.toAccount() + .getAccount(); + final LedgerAccount newAcc = newItem.toAccount() + .getAccount(); - holder.row.setTag(R.id.POS, position); - } - } + if (!Misc.equalStrings(oldAcc.getName(), newAcc.getName())) + changes.add(Change.NAME); - @NonNull + if (oldAcc.getLevel() != newAcc.getLevel()) + changes.add(Change.LEVEL); + + if (oldAcc.isExpanded() != newAcc.isExpanded()) + changes.add(Change.EXPANDED); + + if (oldAcc.amountsExpanded() != newAcc.amountsExpanded()) + changes.add(Change.EXPANDED_AMOUNTS); + + if (!oldAcc.getAmountsString() + .equals(newAcc.getAmountsString())) + changes.add(Change.AMOUNTS); + + return changes.toPayload(); + } + @Override + public boolean areItemsTheSame(@NotNull AccountListItem oldItem, + @NotNull AccountListItem newItem) { + final AccountListItem.Type oldType = oldItem.getType(); + final AccountListItem.Type newType = newItem.getType(); + if (oldType != newType) + return false; + if (oldType == AccountListItem.Type.HEADER) + return true; + + return oldItem.toAccount() + .getAccount() + .getId() == newItem.toAccount() + .getAccount() + .getId(); + } + @Override + public boolean areContentsTheSame(@NotNull AccountListItem oldItem, + @NotNull AccountListItem newItem) { + return oldItem.sameContent(newItem); + } + }); + } + @Override + public long getItemId(int position) { + if (position == 0) + return 0; + return listDiffer.getCurrentList() + .get(position) + .toAccount() + .getAccount() + .getId(); + } @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 void onBindViewHolder(@NonNull RowHolder holder, int position, + @NonNull List payloads) { + holder.bind(listDiffer.getCurrentList() + .get(position), payloads); + super.onBindViewHolder(holder, position, payloads); } + public void onBindViewHolder(@NonNull RowHolder holder, int position) { + holder.bind(listDiffer.getCurrentList() + .get(position), null); + } + @NonNull + @Override + 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 public int getItemCount() { - return Data.accounts.size(); + return listDiffer.getCurrentList() + .size(); + } + @Override + public int getItemViewType(int position) { + return (position == 0) ? ITEM_TYPE_HEADER : ITEM_TYPE_ACCOUNT; } - static class LedgerRowHolder extends RecyclerView.ViewHolder { - TextView tvAccountName, tvAccountAmounts; - ConstraintLayout row; - View expanderContainer; - ImageView expander; - View accountExpanderContainer; - public LedgerRowHolder(@NonNull View itemView) { + public void setAccounts(List newList) { + Misc.onMainThread(() -> listDiffer.submitList(newList)); + } + static class Change { + static final int NAME = 1; + static final int EXPANDED = 1 << 1; + static final int LEVEL = 1 << 2; + static final int EXPANDED_AMOUNTS = 1 << 3; + static final int AMOUNTS = 1 << 4; + private int value = 0; + public Change() { + } + public Change(int initialValue) { + value = initialValue; + } + public void add(int bits) { + value = value | bits; + } + public void add(Change change) { + value = value | change.value; + } + public void remove(int bits) { + value = value & (~bits); + } + public void remove(Change change) { + value = value & (~change.value); + } + public Change toPayload() { + if (value == 0) + return null; + return this; + } + public boolean has(int bits) { + return value == 0 || (value & bits) == bits; + } + } + + static abstract class RowHolder extends RecyclerView.ViewHolder { + public RowHolder(@NonNull View itemView) { super(itemView); - this.row = itemView.findViewById(R.id.account_summary_row); - this.tvAccountName = itemView.findViewById(R.id.account_row_acc_name); - this.tvAccountAmounts = itemView.findViewById(R.id.account_row_acc_amounts); - this.expanderContainer = itemView.findViewById(R.id.account_expander_container); - this.expander = itemView.findViewById(R.id.account_expander); - this.accountExpanderContainer = - itemView.findViewById(R.id.account_row_amounts_expander_container); + } + public abstract void bind(AccountListItem accountListItem, @Nullable List payloads); + } + + 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, @Nullable List payloads) { + Resources r = itemView.getResources(); +// Logger.debug("acc", itemView.getContext() +// .toString()); + ((AccountListItem.Header) item).getText() + .observe((LifecycleOwner) itemView.getContext(), + b.lastUpdateText::setText); + } + } + + class AccountRowHolder extends AccountSummaryAdapter.RowHolder { + private final AccountListRowBinding b; + public AccountRowHolder(@NonNull AccountListRowBinding binding) { + super(binding.getRoot()); + b = binding; itemView.setOnLongClickListener(this::onItemLongClick); - tvAccountName.setOnLongClickListener(this::onItemLongClick); - tvAccountAmounts.setOnLongClickListener(this::onItemLongClick); - expanderContainer.setOnLongClickListener(this::onItemLongClick); - expander.setOnLongClickListener(this::onItemLongClick); - row.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() { + LedgerAccount account = getAccount(); + if (!account.hasSubAccounts()) + return; + debug("accounts", "Account expander clicked"); + + BaseDAO.runAsync(() -> { + 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(getBindingAdapterPosition()) + .toAccount() + .getAccount(); + } + private void toggleAmountsExpanded() { + LedgerAccount account = getAccount(); + if (account.getAmountCount() <= AMOUNT_LIMIT) + return; + + account.toggleAmountsExpanded(); + if (account.amountsExpanded()) { + b.accountRowAccAmounts.setText(account.getAmountsString()); + b.accountRowAmountsExpanderContainer.setVisibility(View.GONE); + } + else { + b.accountRowAccAmounts.setText(account.getAmountsString(AMOUNT_LIMIT)); + b.accountRowAmountsExpanderContainer.setVisibility(View.VISIBLE); + } + + BaseDAO.runAsync(() -> { + 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); - View row; - int id = v.getId(); - switch (id) { - case R.id.account_summary_row: - row = v; - break; - case R.id.account_row_acc_amounts: - case R.id.account_row_amounts_expander_container: - row = (View) v.getParent(); - break; - case R.id.account_row_acc_name: - case R.id.account_expander_container: - row = (View) v.getParent() - .getParent(); - break; - case R.id.account_expander: - row = (View) v.getParent() - .getParent() - .getParent(); - break; - default: - Log.e("error", - String.format("Don't know how to handle long click on id %d", id)); - return false; - } - LedgerAccount acc = (LedgerAccount) row.getTag(); - builder.setTitle(acc.getName()); + final String accountName = getAccount().getName(); + builder.setTitle(accountName); builder.setItems(R.array.acc_ctx_menu, (dialog, which) -> { - switch (which) { - case 0: - // show transactions - activity.showAccountTransactions(acc); - break; + if (which == 0) {// show transactions + activity.showAccountTransactions(accountName); + } + else { + throw new RuntimeException(String.format("Unknown menu item id (%d)", which)); } dialog.dismiss(); }); builder.show(); return true; } + @Override + public void bind(AccountListItem item, @Nullable List payloads) { + LedgerAccount acc = item.toAccount() + .getAccount(); + + Change changes = new Change(); + if (payloads != null) { + for (Object p : payloads) { + if (p instanceof Change) + changes.add((Change) p); + } + } +// debug("accounts", +// String.format(Locale.US, "Binding '%s' to %s", acc.getName(), this)); + + Resources rm = b.getRoot() + .getContext() + .getResources(); + + if (changes.has(Change.NAME)) + b.accountRowAccName.setText(acc.getShortName()); + + if (changes.has(Change.LEVEL)) { + ConstraintLayout.LayoutParams lp = + (ConstraintLayout.LayoutParams) b.flowWrapper.getLayoutParams(); + lp.setMarginStart( + acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) / 3); + } + + if (acc.hasSubAccounts()) { + b.accountExpanderContainer.setVisibility(View.VISIBLE); + + if (changes.has(Change.EXPANDED)) { + 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); + } + } + } + else { + b.accountExpanderContainer.setVisibility(View.GONE); + } + + if (changes.has(Change.EXPANDED_AMOUNTS)) { + 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); + } + } + } } }