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