]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java
running totals when filtering transactions by account
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / transaction_list / TransactionListAdapter.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.transaction_list;
19
20 import android.view.LayoutInflater;
21 import android.view.ViewGroup;
22
23 import androidx.annotation.NonNull;
24 import androidx.recyclerview.widget.AsyncListDiffer;
25 import androidx.recyclerview.widget.DiffUtil;
26 import androidx.recyclerview.widget.RecyclerView;
27
28 import net.ktnx.mobileledger.databinding.LastUpdateLayoutBinding;
29 import net.ktnx.mobileledger.databinding.TransactionDelimiterBinding;
30 import net.ktnx.mobileledger.databinding.TransactionListRowBinding;
31 import net.ktnx.mobileledger.model.LedgerTransaction;
32 import net.ktnx.mobileledger.model.TransactionListItem;
33 import net.ktnx.mobileledger.utils.Logger;
34 import net.ktnx.mobileledger.utils.Misc;
35
36 import java.util.List;
37 import java.util.Locale;
38
39 public class TransactionListAdapter extends RecyclerView.Adapter<TransactionRowHolderBase> {
40     private final AsyncListDiffer<TransactionListItem> listDiffer;
41     public TransactionListAdapter() {
42         super();
43
44         setHasStableIds(true);
45
46         listDiffer = new AsyncListDiffer<>(this, new DiffUtil.ItemCallback<TransactionListItem>() {
47             @Override
48             public boolean areItemsTheSame(@NonNull TransactionListItem oldItem,
49                                            @NonNull TransactionListItem newItem) {
50                 if (oldItem.getType() != newItem.getType())
51                     return false;
52                 switch (oldItem.getType()) {
53                     case DELIMITER:
54                         return (oldItem.getDate()
55                                        .equals(newItem.getDate()));
56                     case TRANSACTION:
57                         return oldItem.getTransaction()
58                                       .getLedgerId() == newItem.getTransaction()
59                                                                .getLedgerId();
60                     case HEADER:
61                         return true;    // there can be only one header
62                     default:
63                         throw new IllegalStateException(
64                                 String.format(Locale.US, "Unexpected transaction item type %s",
65                                         oldItem.getType()));
66                 }
67             }
68             @Override
69             public boolean areContentsTheSame(@NonNull TransactionListItem oldItem,
70                                               @NonNull TransactionListItem newItem) {
71                 switch (oldItem.getType()) {
72                     case DELIMITER:
73                         return oldItem.isMonthShown() == newItem.isMonthShown();
74                     case TRANSACTION:
75                         return oldItem.getTransaction()
76                                       .equals(newItem.getTransaction()) &&
77                                Misc.equalStrings(oldItem.getBoldAccountName(),
78                                        newItem.getBoldAccountName());
79                     case HEADER:
80                         // headers don't differ in their contents. they observe the last update
81                         // date and react to its changes
82                         return true;
83                     default:
84                         throw new IllegalStateException(
85                                 String.format(Locale.US, "Unexpected transaction item type %s",
86                                         oldItem.getType()));
87
88                 }
89             }
90         });
91     }
92     @Override
93     public long getItemId(int position) {
94         TransactionListItem item = listDiffer.getCurrentList()
95                                              .get(position);
96         switch (item.getType()) {
97             case HEADER:
98                 return -1;
99             case TRANSACTION:
100                 return item.getTransaction()
101                            .getLedgerId();
102             case DELIMITER:
103                 return -item.getDate()
104                             .toDate()
105                             .getTime();
106             default:
107                 throw new IllegalStateException("Unexpected value: " + item.getType());
108         }
109     }
110     @Override
111     public int getItemViewType(int position) {
112         return listDiffer.getCurrentList()
113                          .get(position)
114                          .getType()
115                          .ordinal();
116     }
117     public void onBindViewHolder(@NonNull TransactionRowHolderBase holder, int position) {
118         TransactionListItem item = listDiffer.getCurrentList()
119                                              .get(position);
120
121         // in a race when transaction value is reduced, but the model hasn't been notified yet
122         // the view will disappear when the notifications reaches the model, so by simply omitting
123         // the out-of-range get() call nothing bad happens - just a to-be-deleted view remains
124         // a bit longer
125         if (item == null)
126             return;
127
128         final TransactionListItem.Type newType = item.getType();
129
130         switch (newType) {
131             case TRANSACTION:
132                 holder.asTransaction()
133                       .bind(item, item.getBoldAccountName());
134
135                 break;
136             case DELIMITER:
137                 holder.asDelimiter()
138                       .bind(item);
139                 break;
140             case HEADER:
141                 holder.asHeader()
142                       .bind();
143
144                 break;
145             default:
146                 throw new IllegalStateException("Unexpected value: " + newType);
147         }
148     }
149     @NonNull
150     @Override
151     public TransactionRowHolderBase onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
152 //        debug("perf", "onCreateViewHolder called");
153         final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
154
155         switch (TransactionListItem.Type.valueOf(viewType)) {
156             case TRANSACTION:
157                 return new TransactionRowHolder(
158                         TransactionListRowBinding.inflate(inflater, parent, false));
159             case DELIMITER:
160                 return new TransactionListDelimiterRowHolder(
161                         TransactionDelimiterBinding.inflate(inflater, parent, false));
162             case HEADER:
163                 return new TransactionListLastUpdateRowHolder(
164                         LastUpdateLayoutBinding.inflate(inflater, parent, false));
165             default:
166                 throw new IllegalStateException("Unexpected value: " + viewType);
167         }
168     }
169
170     @Override
171     public int getItemCount() {
172         return listDiffer.getCurrentList()
173                          .size();
174     }
175     public void setTransactions(List<TransactionListItem> newList) {
176         Logger.debug("transactions",
177                 String.format(Locale.US, "Got new transaction list (%d items)", newList.size()));
178         listDiffer.submitList(newList);
179     }
180 }