]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/account_summary/AccountSummaryAdapter.java
migrate to surrogate IDs for all database objects
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / account_summary / AccountSummaryAdapter.java
1 /*
2  * Copyright © 2021 Damyan Ivanov.
3  * This file is part of MoLe.
4  * MoLe is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * MoLe is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.ui.account_summary;
19
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.ImageView;
26 import android.widget.TextView;
27
28 import androidx.annotation.NonNull;
29 import androidx.appcompat.app.AlertDialog;
30 import androidx.constraintlayout.widget.ConstraintLayout;
31 import androidx.recyclerview.widget.AsyncListDiffer;
32 import androidx.recyclerview.widget.DiffUtil;
33 import androidx.recyclerview.widget.RecyclerView;
34
35 import net.ktnx.mobileledger.R;
36 import net.ktnx.mobileledger.async.DbOpQueue;
37 import net.ktnx.mobileledger.model.AccountListItem;
38 import net.ktnx.mobileledger.model.Data;
39 import net.ktnx.mobileledger.model.LedgerAccount;
40 import net.ktnx.mobileledger.model.MobileLedgerProfile;
41 import net.ktnx.mobileledger.ui.MainModel;
42 import net.ktnx.mobileledger.ui.activity.MainActivity;
43 import net.ktnx.mobileledger.utils.Locker;
44 import net.ktnx.mobileledger.utils.Misc;
45
46 import org.jetbrains.annotations.NotNull;
47
48 import java.util.List;
49 import java.util.Locale;
50 import java.util.Observer;
51
52 import static net.ktnx.mobileledger.utils.Logger.debug;
53
54 public class AccountSummaryAdapter
55         extends RecyclerView.Adapter<AccountSummaryAdapter.LedgerRowHolder> {
56     public static final int AMOUNT_LIMIT = 3;
57     private final AsyncListDiffer<AccountListItem> listDiffer;
58     private final MainModel model;
59     AccountSummaryAdapter(MainModel model) {
60         this.model = model;
61
62         listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<AccountListItem>() {
63             @Override
64             public boolean areItemsTheSame(@NotNull AccountListItem oldItem,
65                                            @NotNull AccountListItem newItem) {
66                 final AccountListItem.Type oldType = oldItem.getType();
67                 final AccountListItem.Type newType = newItem.getType();
68                 if (oldType == AccountListItem.Type.HEADER) {
69                     return newType == AccountListItem.Type.HEADER;
70                 }
71                 if (oldType != newType)
72                     return false;
73
74                 return Misc.equalStrings(oldItem.getAccount()
75                                                 .getName(), newItem.getAccount()
76                                                                    .getName());
77             }
78             @Override
79             public boolean areContentsTheSame(@NotNull AccountListItem oldItem,
80                                               @NotNull AccountListItem newItem) {
81                 if (oldItem.getType()
82                            .equals(AccountListItem.Type.HEADER))
83                     return true;
84                 return oldItem.getAccount()
85                               .equals(newItem.getAccount());
86             }
87         });
88     }
89
90     public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) {
91         holder.bindToAccount(listDiffer.getCurrentList()
92                                        .get(position));
93     }
94
95     @NonNull
96     @Override
97     public LedgerRowHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
98         View row = LayoutInflater.from(parent.getContext())
99                                  .inflate(R.layout.account_summary_row, parent, false);
100         return new LedgerRowHolder(row);
101     }
102
103     @Override
104     public int getItemCount() {
105         return listDiffer.getCurrentList()
106                          .size();
107     }
108     public void setAccounts(List<AccountListItem> newList) {
109         listDiffer.submitList(newList);
110     }
111     class LedgerRowHolder extends RecyclerView.ViewHolder {
112         private final TextView tvAccountName, tvAccountAmounts;
113         private final ConstraintLayout row;
114         private final View expanderContainer;
115         private final View amountExpanderContainer;
116         private final View lLastUpdate;
117         private final TextView tvLastUpdate;
118         private final View vAccountNameLayout;
119         LedgerAccount mAccount;
120         private AccountListItem.Type lastType;
121         private Observer lastUpdateObserver;
122         public LedgerRowHolder(@NonNull View itemView) {
123             super(itemView);
124
125             row = itemView.findViewById(R.id.account_summary_row);
126             vAccountNameLayout = itemView.findViewById(R.id.account_name_layout);
127             tvAccountName = itemView.findViewById(R.id.account_row_acc_name);
128             tvAccountAmounts = itemView.findViewById(R.id.account_row_acc_amounts);
129             expanderContainer = itemView.findViewById(R.id.account_expander_container);
130             ImageView expander = itemView.findViewById(R.id.account_expander);
131             amountExpanderContainer =
132                     itemView.findViewById(R.id.account_row_amounts_expander_container);
133             lLastUpdate = itemView.findViewById(R.id.last_update_container);
134             tvLastUpdate = itemView.findViewById(R.id.last_update_text);
135
136             itemView.setOnLongClickListener(this::onItemLongClick);
137             tvAccountName.setOnLongClickListener(this::onItemLongClick);
138             tvAccountAmounts.setOnLongClickListener(this::onItemLongClick);
139             expanderContainer.setOnLongClickListener(this::onItemLongClick);
140             expander.setOnLongClickListener(this::onItemLongClick);
141             row.setOnLongClickListener(this::onItemLongClick);
142
143             tvAccountName.setOnClickListener(v -> toggleAccountExpanded());
144             expanderContainer.setOnClickListener(v -> toggleAccountExpanded());
145             expander.setOnClickListener(v -> toggleAccountExpanded());
146             tvAccountAmounts.setOnClickListener(v -> toggleAmountsExpanded());
147
148         }
149         private void toggleAccountExpanded() {
150             if (!mAccount.hasSubAccounts())
151                 return;
152             debug("accounts", "Account expander clicked");
153
154             // make sure we use the same object as the one in the allAccounts list
155             MobileLedgerProfile profile = mAccount.getProfile();
156             if (profile == null) {
157                 return;
158             }
159             try (Locker ignored = model.lockAccountsForWriting()) {
160                 LedgerAccount realAccount = model.locateAccount(mAccount.getName());
161                 if (realAccount == null)
162                     return;
163
164                 mAccount = realAccount;
165                 mAccount.toggleExpanded();
166             }
167             expanderContainer.animate()
168                              .rotation(mAccount.isExpanded() ? 0 : 180);
169             model.updateDisplayedAccounts();
170
171             DbOpQueue.add("update accounts set expanded=? where name=? and profile=?",
172                     new Object[]{mAccount.isExpanded(), mAccount.getName(), profile.getId()
173                     });
174
175         }
176         private void toggleAmountsExpanded() {
177             if (mAccount.getAmountCount() <= AMOUNT_LIMIT)
178                 return;
179
180             mAccount.toggleAmountsExpanded();
181             if (mAccount.amountsExpanded()) {
182                 tvAccountAmounts.setText(mAccount.getAmountsString());
183                 amountExpanderContainer.setVisibility(View.GONE);
184             }
185             else {
186                 tvAccountAmounts.setText(mAccount.getAmountsString(AMOUNT_LIMIT));
187                 amountExpanderContainer.setVisibility(View.VISIBLE);
188             }
189
190             MobileLedgerProfile profile = mAccount.getProfile();
191             if (profile == null)
192                 return;
193
194             DbOpQueue.add("update accounts set amounts_expanded=? where name=? and profile=?",
195                     new Object[]{mAccount.amountsExpanded(), mAccount.getName(), profile.getId()
196                     });
197
198         }
199         private boolean onItemLongClick(View v) {
200             MainActivity activity = (MainActivity) v.getContext();
201             AlertDialog.Builder builder = new AlertDialog.Builder(activity);
202             final String accountName = mAccount.getName();
203             builder.setTitle(accountName);
204             builder.setItems(R.array.acc_ctx_menu, (dialog, which) -> {
205                 if (which == 0) {// show transactions
206                     activity.showAccountTransactions(accountName);
207                 }
208                 else {
209                     throw new RuntimeException(String.format("Unknown menu item id (%d)", which));
210                 }
211                 dialog.dismiss();
212             });
213             builder.show();
214             return true;
215         }
216         public void bindToAccount(AccountListItem item) {
217             final AccountListItem.Type newType = item.getType();
218             setType(newType);
219
220             switch (newType) {
221                 case ACCOUNT:
222                     LedgerAccount acc = item.getAccount();
223
224                     debug("accounts", String.format(Locale.US, "Binding to '%s'", acc.getName()));
225                     Context ctx = row.getContext();
226                     Resources rm = ctx.getResources();
227                     mAccount = acc;
228
229                     row.setTag(acc);
230
231                     tvAccountName.setText(acc.getShortName());
232
233                     ConstraintLayout.LayoutParams lp =
234                             (ConstraintLayout.LayoutParams) tvAccountName.getLayoutParams();
235                     lp.setMarginStart(
236                             acc.getLevel() * rm.getDimensionPixelSize(R.dimen.thumb_row_height) /
237                             3);
238
239                     if (acc.hasSubAccounts()) {
240                         expanderContainer.setVisibility(View.VISIBLE);
241                         expanderContainer.setRotation(acc.isExpanded() ? 0 : 180);
242                     }
243                     else {
244                         expanderContainer.setVisibility(View.GONE);
245                     }
246
247                     int amounts = acc.getAmountCount();
248                     if ((amounts > AMOUNT_LIMIT) && !acc.amountsExpanded()) {
249                         tvAccountAmounts.setText(acc.getAmountsString(AMOUNT_LIMIT));
250                         amountExpanderContainer.setVisibility(View.VISIBLE);
251                     }
252                     else {
253                         tvAccountAmounts.setText(acc.getAmountsString());
254                         amountExpanderContainer.setVisibility(View.GONE);
255                     }
256
257                     break;
258                 case HEADER:
259                     setLastUpdateText(Data.lastAccountsUpdateText.get());
260                     break;
261                 default:
262                     throw new IllegalStateException("Unexpected value: " + newType);
263             }
264
265         }
266         void setLastUpdateText(String text) {
267             tvLastUpdate.setText(text);
268         }
269         private void initLastUpdateObserver() {
270             if (lastUpdateObserver != null)
271                 return;
272
273             lastUpdateObserver = (o, arg) -> setLastUpdateText(Data.lastAccountsUpdateText.get());
274
275             Data.lastAccountsUpdateText.addObserver(lastUpdateObserver);
276         }
277         private void dropLastUpdateObserver() {
278             if (lastUpdateObserver == null)
279                 return;
280
281             Data.lastAccountsUpdateText.deleteObserver(lastUpdateObserver);
282             lastUpdateObserver = null;
283         }
284         private void setType(AccountListItem.Type newType) {
285             if (newType == lastType)
286                 return;
287
288             switch (newType) {
289                 case ACCOUNT:
290                     row.setLongClickable(true);
291                     amountExpanderContainer.setVisibility(View.VISIBLE);
292                     vAccountNameLayout.setVisibility(View.VISIBLE);
293                     tvAccountAmounts.setVisibility(View.VISIBLE);
294                     lLastUpdate.setVisibility(View.GONE);
295                     dropLastUpdateObserver();
296                     break;
297                 case HEADER:
298                     row.setLongClickable(false);
299                     tvAccountAmounts.setVisibility(View.GONE);
300                     amountExpanderContainer.setVisibility(View.GONE);
301                     vAccountNameLayout.setVisibility(View.GONE);
302                     lLastUpdate.setVisibility(View.VISIBLE);
303                     initLastUpdateObserver();
304                     break;
305                 default:
306                     throw new IllegalStateException("Unexpected value: " + newType);
307             }
308
309             lastType = newType;
310         }
311     }
312 }