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