]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateDetailsViewModel.java
more pronounced day/month delimiters in the transaction list
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / templates / TemplateDetailsViewModel.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.templates;
19
20 import androidx.lifecycle.LiveData;
21 import androidx.lifecycle.MutableLiveData;
22 import androidx.lifecycle.Observer;
23 import androidx.lifecycle.ViewModel;
24
25 import net.ktnx.mobileledger.BuildConfig;
26 import net.ktnx.mobileledger.dao.BaseDAO;
27 import net.ktnx.mobileledger.dao.TemplateAccountDAO;
28 import net.ktnx.mobileledger.dao.TemplateHeaderDAO;
29 import net.ktnx.mobileledger.db.DB;
30 import net.ktnx.mobileledger.db.TemplateAccount;
31 import net.ktnx.mobileledger.db.TemplateHeader;
32 import net.ktnx.mobileledger.db.TemplateWithAccounts;
33 import net.ktnx.mobileledger.model.TemplateDetailsItem;
34 import net.ktnx.mobileledger.utils.Logger;
35 import net.ktnx.mobileledger.utils.Misc;
36
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Objects;
42 import java.util.concurrent.atomic.AtomicInteger;
43
44 public class TemplateDetailsViewModel extends ViewModel {
45     static final String TAG = "template-details-model";
46     static final String DB_TAG = TAG + "-db";
47     private final MutableLiveData<List<TemplateDetailsItem>> items =
48             new MutableLiveData<>(Collections.emptyList());
49     private final AtomicInteger syntheticItemId = new AtomicInteger(0);
50     private Long mPatternId;
51     private String mDefaultTemplateName;
52     private boolean itemsLoaded = false;
53     public String getDefaultTemplateName() {
54         return mDefaultTemplateName;
55     }
56     public void setDefaultTemplateName(String name) {
57         mDefaultTemplateName = name;
58     }
59
60     public void resetItems() {
61         applyList(new ArrayList<>());
62     }
63     public void applyList(List<TemplateDetailsItem> srcList) {
64         applyList(srcList, false);
65     }
66     public void applyList(List<TemplateDetailsItem> srcList, boolean async) {
67         boolean changes;
68         if (srcList == null) {
69             srcList = new ArrayList<>(items.getValue());
70             changes = false;
71         }
72         else
73             changes = true;
74
75         srcList = Collections.unmodifiableList(srcList);
76
77         if (BuildConfig.DEBUG) {
78             Logger.debug(TAG, "Considering old list");
79             for (TemplateDetailsItem item : srcList)
80                 Logger.debug(TAG, String.format(Locale.US, " id %d pos %d", item.getId(),
81                         item.getPosition()));
82         }
83
84         ArrayList<TemplateDetailsItem> newList = new ArrayList<>();
85
86         boolean hasEmptyItem = false;
87
88         if (srcList.size() < 1) {
89             final TemplateDetailsItem.Header header = TemplateDetailsItem.createHeader();
90             header.setId(0);
91             newList.add(header);
92             changes = true;
93         }
94         else {
95             newList.add(srcList.get(0));
96         }
97
98         for (int i = 1; i < srcList.size(); i++) {
99             final TemplateDetailsItem.AccountRow accRow = srcList.get(i)
100                                                                  .asAccountRowItem();
101             if (accRow.isEmpty()) {
102                 // it is normal to have two empty rows if they are at the
103                 // top (position 1 and 2)
104                 if (!hasEmptyItem || i < 3) {
105                     accRow.setPosition(newList.size());
106                     newList.add(accRow);
107                 }
108                 else
109                     changes = true; // row skipped
110
111                 hasEmptyItem = true;
112             }
113             else {
114                 accRow.setPosition(newList.size());
115                 newList.add(accRow);
116             }
117         }
118
119         while (newList.size() < 3) {
120             final TemplateDetailsItem.AccountRow accountRow =
121                     TemplateDetailsItem.createAccountRow();
122             accountRow.setId(genItemId());
123             accountRow.setPosition(newList.size());
124             newList.add(accountRow);
125             changes = true;
126             hasEmptyItem = true;
127         }
128
129         if (!hasEmptyItem) {
130             final TemplateDetailsItem.AccountRow accountRow =
131                     TemplateDetailsItem.createAccountRow();
132             accountRow.setId(genItemId());
133             accountRow.setPosition(newList.size());
134             newList.add(accountRow);
135             changes = true;
136         }
137
138         if (changes) {
139             Logger.debug(TAG, "Changes detected, applying new list");
140
141             if (async)
142                 items.postValue(newList);
143             else
144                 items.setValue(newList);
145         }
146         else
147             Logger.debug(TAG, "No changes, ignoring new list");
148     }
149     public int genItemId() {
150         return syntheticItemId.decrementAndGet();
151     }
152     public LiveData<List<TemplateDetailsItem>> getItems(Long patternId) {
153         if (itemsLoaded && Objects.equals(patternId, this.mPatternId))
154             return items;
155
156         if (patternId != null && patternId <= 0)
157             throw new IllegalArgumentException("Pattern ID " + patternId + " is invalid");
158
159         mPatternId = patternId;
160
161         if (mPatternId == null) {
162             resetItems();
163             itemsLoaded = true;
164             return items;
165         }
166
167         DB db = DB.get();
168         LiveData<TemplateWithAccounts> dbList = db.getTemplateDAO()
169                                                   .getTemplateWithAccounts(mPatternId);
170         Observer<TemplateWithAccounts> observer = new Observer<TemplateWithAccounts>() {
171             @Override
172             public void onChanged(TemplateWithAccounts src) {
173                 ArrayList<TemplateDetailsItem> l = new ArrayList<>();
174
175                 TemplateDetailsItem header = TemplateDetailsItem.fromRoomObject(src.header);
176                 Logger.debug(DB_TAG, "Got header template item with id of " + header.getId());
177                 l.add(header);
178                 Collections.sort(src.accounts,
179                         (o1, o2) -> Long.compare(o1.getPosition(), o2.getPosition()));
180                 for (TemplateAccount acc : src.accounts) {
181                     l.add(TemplateDetailsItem.fromRoomObject(acc));
182                 }
183
184                 for (TemplateDetailsItem i : l) {
185                     Logger.debug(DB_TAG, "Loaded pattern item " + i);
186                 }
187                 applyList(l, true);
188                 itemsLoaded = true;
189
190                 dbList.removeObserver(this);
191             }
192         };
193         dbList.observeForever(observer);
194
195         return items;
196     }
197     public void setTestText(String text) {
198         List<TemplateDetailsItem> list = new ArrayList<>(items.getValue());
199         TemplateDetailsItem.Header header = new TemplateDetailsItem.Header(list.get(0)
200                                                                                .asHeaderItem());
201         header.setTestText(text);
202         list.set(0, header);
203
204         items.setValue(list);
205     }
206     public void onSaveTemplate() {
207         Logger.debug("flow", "PatternDetailsViewModel.onSavePattern(); model=" + this);
208         final List<TemplateDetailsItem> list = Objects.requireNonNull(items.getValue());
209
210         BaseDAO.runAsync(() -> {
211             boolean newPattern = mPatternId == null || mPatternId <= 0;
212
213             TemplateDetailsItem.Header modelHeader = list.get(0)
214                                                          .asHeaderItem();
215
216             modelHeader.setName(Misc.trim(modelHeader.getName()));
217             if (modelHeader.getName()
218                            .isEmpty())
219                 modelHeader.setName(getDefaultTemplateName());
220
221             TemplateHeaderDAO headerDAO = DB.get()
222                                             .getTemplateDAO();
223             TemplateHeader dbHeader = modelHeader.toDBO();
224             if (newPattern) {
225                 dbHeader.setId(0L);
226                 dbHeader.setId(mPatternId = headerDAO.insertSync(dbHeader));
227             }
228             else
229                 headerDAO.updateSync(dbHeader);
230
231             Logger.debug("pattern-db",
232                     String.format(Locale.US, "Stored pattern header %d, item=%s", dbHeader.getId(),
233                             modelHeader));
234
235
236             TemplateAccountDAO taDAO = DB.get()
237                                          .getTemplateAccountDAO();
238             taDAO.prepareForSave(mPatternId);
239             for (int i = 1; i < list.size(); i++) {
240                 final TemplateDetailsItem.AccountRow accRowItem = list.get(i)
241                                                                       .asAccountRowItem();
242                 TemplateAccount dbAccount = accRowItem.toDBO(dbHeader.getId());
243                 dbAccount.setTemplateId(mPatternId);
244                 dbAccount.setPosition(i);
245                 if (dbAccount.getId() < 0) {
246                     dbAccount.setId(0);
247                     dbAccount.setId(taDAO.insertSync(dbAccount));
248                 }
249                 else
250                     taDAO.updateSync(dbAccount);
251
252                 Logger.debug("pattern-db", String.format(Locale.US,
253                         "Stored pattern account %d, account=%s, comment=%s, neg=%s, item=%s",
254                         dbAccount.getId(), dbAccount.getAccountName(),
255                         dbAccount.getAccountComment(), dbAccount.getNegateAmount(), accRowItem));
256             }
257             taDAO.finishSave(mPatternId);
258         });
259     }
260     private ArrayList<TemplateDetailsItem> copyItems() {
261         List<TemplateDetailsItem> oldList = items.getValue();
262
263         if (oldList == null)
264             return new ArrayList<>();
265
266         ArrayList<TemplateDetailsItem> result = new ArrayList<>(oldList.size());
267
268         for (TemplateDetailsItem item : oldList) {
269             if (item instanceof TemplateDetailsItem.Header)
270                 result.add(new TemplateDetailsItem.Header(item.asHeaderItem()));
271             else if (item instanceof TemplateDetailsItem.AccountRow)
272                 result.add(new TemplateDetailsItem.AccountRow(item.asAccountRowItem()));
273             else
274                 throw new RuntimeException("Unexpected item " + item);
275         }
276
277         return result;
278     }
279     public void moveItem(int sourcePos, int targetPos) {
280         final List<TemplateDetailsItem> newList = copyItems();
281
282         if (BuildConfig.DEBUG) {
283             Logger.debug("drag", "Before move:");
284             for (int i = 1; i < newList.size(); i++) {
285                 final TemplateDetailsItem item = newList.get(i);
286                 Logger.debug("drag",
287                         String.format(Locale.US, "  %d: id %d, pos %d", i, item.getId(),
288                                 item.getPosition()));
289             }
290         }
291
292         {
293             TemplateDetailsItem item = newList.remove(sourcePos);
294             newList.add(targetPos, item);
295         }
296
297         // adjust affected items' positions
298         {
299             int startPos, endPos;
300             if (sourcePos < targetPos) {
301                 // moved down
302                 startPos = sourcePos;
303                 endPos = targetPos;
304             }
305             else {
306                 // moved up
307                 startPos = targetPos;
308                 endPos = sourcePos;
309             }
310
311             for (int i = startPos; i <= endPos; i++) {
312                 newList.get(i)
313                        .setPosition(i);
314             }
315         }
316
317         if (BuildConfig.DEBUG) {
318             Logger.debug("drag", "After move:");
319             for (int i = 1; i < newList.size(); i++) {
320                 final TemplateDetailsItem item = newList.get(i);
321                 Logger.debug("drag",
322                         String.format(Locale.US, "  %d: id %d, pos %d", i, item.getId(),
323                                 item.getPosition()));
324             }
325         }
326
327         items.setValue(newList);
328     }
329     public void removeItem(int position) {
330         Logger.debug(TAG, "Removing item at position " + position);
331         ArrayList<TemplateDetailsItem> newList = copyItems();
332         newList.remove(position);
333         for (int i = position; i < newList.size(); i++)
334             newList.get(i)
335                    .setPosition(i);
336         applyList(newList);
337     }
338 }