]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/templates/TemplateDetailsViewModel.java
5d8ef18a084dddb696721a574aa7c25b92bcf3c1
[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.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
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.Objects;
41 import java.util.concurrent.atomic.AtomicInteger;
42
43 public class TemplateDetailsViewModel extends ViewModel {
44     private final MutableLiveData<List<TemplateDetailsItem>> items =
45             new MutableLiveData<>(Collections.emptyList());
46     private Long mPatternId;
47     private String mDefaultPatternName;
48     private boolean itemsLoaded = false;
49     private final AtomicInteger syntheticItemId = new AtomicInteger(0);
50
51     public String getDefaultPatternName() {
52         return mDefaultPatternName;
53     }
54     public void setDefaultPatternName(String name) {
55         mDefaultPatternName = name;
56     }
57
58     public void resetItems() {
59         applyList(new ArrayList<>());
60     }
61     public void applyList(List<TemplateDetailsItem> srcList) {
62         applyList(srcList, false);
63     }
64     public void applyList(List<TemplateDetailsItem> srcList, boolean async) {
65         boolean changes;
66         if (srcList == null) {
67             srcList = new ArrayList<>(items.getValue());
68             changes = false;
69         }
70         else
71             changes = true;
72
73         srcList = Collections.unmodifiableList(srcList);
74
75         {
76             Logger.debug("tmpl", "Considering old list");
77             for (TemplateDetailsItem item : srcList)
78                 Logger.debug("tmpl", String.format(Locale.US, " id %d pos %d", item.getId(),
79                         item.getPosition()));
80         }
81
82         ArrayList<TemplateDetailsItem> newList = new ArrayList<>();
83
84         boolean hasEmptyItem = false;
85
86         if (srcList.size() < 1) {
87             final TemplateDetailsItem.Header header = TemplateDetailsItem.createHeader();
88             header.setName(mDefaultPatternName);
89             header.setId(0);
90             newList.add(header);
91             changes = true;
92         }
93         else {
94             newList.add(srcList.get(0));
95         }
96
97         for (int i = 1; i < srcList.size(); i++) {
98             final TemplateDetailsItem.AccountRow accRow = srcList.get(i)
99                                                                  .asAccountRowItem();
100             if (accRow.isEmpty()) {
101                 // it is normal to have two empty rows if they are at the
102                 // top (position 1 and 2)
103                 if (!hasEmptyItem || i < 3) {
104                     accRow.setPosition(newList.size());
105                     newList.add(accRow);
106                 }
107                 else
108                     changes = true; // row skipped
109
110                 hasEmptyItem = true;
111             }
112             else {
113                 accRow.setPosition(newList.size());
114                 newList.add(accRow);
115             }
116         }
117
118         while (newList.size() < 3) {
119             final TemplateDetailsItem.AccountRow accountRow =
120                     TemplateDetailsItem.createAccountRow();
121             accountRow.setId(genItemId());
122             accountRow.setPosition(newList.size());
123             newList.add(accountRow);
124             changes = true;
125             hasEmptyItem = true;
126         }
127
128         if (!hasEmptyItem) {
129             final TemplateDetailsItem.AccountRow accountRow =
130                     TemplateDetailsItem.createAccountRow();
131             accountRow.setId(genItemId());
132             accountRow.setPosition(newList.size());
133             newList.add(accountRow);
134             changes = true;
135         }
136
137         if (changes) {
138             Logger.debug("tmpl", "Changes detected, applying new list");
139
140             if (async)
141                 items.postValue(newList);
142             else
143                 items.setValue(newList);
144         }
145         else
146             Logger.debug("tmpl", "No changes, ignoring new list");
147     }
148     public int genItemId() {
149         return syntheticItemId.decrementAndGet();
150     }
151     public LiveData<List<TemplateDetailsItem>> getItems(Long patternId) {
152         if (itemsLoaded && Objects.equals(patternId, this.mPatternId))
153             return items;
154
155         if (patternId != null && patternId <= 0)
156             throw new IllegalArgumentException("Pattern ID " + patternId + " is invalid");
157
158         mPatternId = patternId;
159
160         if (mPatternId == null) {
161             resetItems();
162             itemsLoaded = true;
163             return items;
164         }
165
166         DB db = DB.get();
167         LiveData<TemplateWithAccounts> dbList = db.getTemplateDAO()
168                                                   .getTemplateWithAccounts(mPatternId);
169         Observer<TemplateWithAccounts> observer = new Observer<TemplateWithAccounts>() {
170             @Override
171             public void onChanged(TemplateWithAccounts src) {
172                 ArrayList<TemplateDetailsItem> l = new ArrayList<>();
173
174                 TemplateDetailsItem header = TemplateDetailsItem.fromRoomObject(src.header);
175                 l.add(header);
176                 Collections.sort(src.accounts,
177                         (o1, o2) -> Long.compare(o1.getPosition(), o2.getPosition()));
178                 for (TemplateAccount acc : src.accounts) {
179                     l.add(TemplateDetailsItem.fromRoomObject(acc));
180                 }
181
182                 for (TemplateDetailsItem i : l) {
183                     Logger.debug("patterns-db", "Loaded pattern item " + i);
184                 }
185                 applyList(l, true);
186                 itemsLoaded = true;
187
188                 dbList.removeObserver(this);
189             }
190         };
191         dbList.observeForever(observer);
192
193         return items;
194     }
195     public void setTestText(String text) {
196         List<TemplateDetailsItem> list = new ArrayList<>(items.getValue());
197         TemplateDetailsItem.Header header = new TemplateDetailsItem.Header(list.get(0)
198                                                                                .asHeaderItem());
199         header.setTestText(text);
200         list.set(0, header);
201
202         items.setValue(list);
203     }
204     public void onSaveTemplate() {
205         Logger.debug("flow", "PatternDetailsViewModel.onSavePattern(); model=" + this);
206         final List<TemplateDetailsItem> list = Objects.requireNonNull(items.getValue());
207
208         AsyncTask.execute(() -> {
209             boolean newPattern = mPatternId == null || mPatternId <= 0;
210
211             TemplateDetailsItem.Header modelHeader = list.get(0)
212                                                          .asHeaderItem();
213             TemplateHeaderDAO headerDAO = DB.get()
214                                             .getTemplateDAO();
215             TemplateHeader dbHeader = modelHeader.toDBO();
216             if (newPattern) {
217                 dbHeader.setId(null);
218                 dbHeader.setId(mPatternId = headerDAO.insertSync(dbHeader));
219             }
220             else
221                 headerDAO.updateSync(dbHeader);
222
223             Logger.debug("pattern-db",
224                     String.format(Locale.US, "Stored pattern header %d, item=%s", dbHeader.getId(),
225                             modelHeader));
226
227
228             TemplateAccountDAO taDAO = DB.get()
229                                          .getTemplateAccountDAO();
230             taDAO.prepareForSave(mPatternId);
231             for (int i = 1; i < list.size(); i++) {
232                 final TemplateDetailsItem.AccountRow accRowItem = list.get(i)
233                                                                       .asAccountRowItem();
234                 TemplateAccount dbAccount = accRowItem.toDBO(dbHeader.getId());
235                 dbAccount.setTemplateId(mPatternId);
236                 dbAccount.setPosition(i);
237                 if (dbAccount.getId() < 0) {
238                     dbAccount.setId(null);
239                     dbAccount.setId(taDAO.insertSync(dbAccount));
240                 }
241                 else
242                     taDAO.updateSync(dbAccount);
243
244                 Logger.debug("pattern-db", String.format(Locale.US,
245                         "Stored pattern account %d, account=%s, comment=%s, neg=%s, item=%s",
246                         dbAccount.getId(), dbAccount.getAccountName(),
247                         dbAccount.getAccountComment(), dbAccount.getNegateAmount(), accRowItem));
248             }
249             taDAO.finishSave(mPatternId);
250         });
251     }
252     private ArrayList<TemplateDetailsItem> copyItems() {
253         List<TemplateDetailsItem> oldList = items.getValue();
254         ArrayList<TemplateDetailsItem> result = new ArrayList<>(oldList.size());
255
256         for (TemplateDetailsItem item : oldList) {
257             if (item instanceof TemplateDetailsItem.Header)
258                 result.add(new TemplateDetailsItem.Header(item.asHeaderItem()));
259             else if (item instanceof TemplateDetailsItem.AccountRow)
260                 result.add(new TemplateDetailsItem.AccountRow(item.asAccountRowItem()));
261             else
262                 throw new RuntimeException("Unexpected item " + item);
263         }
264
265         return result;
266     }
267     public void moveItem(int sourcePos, int targetPos) {
268         ArrayList<TemplateDetailsItem> newList = new ArrayList<>(items.getValue());
269         TemplateDetailsItem item = newList.remove(sourcePos);
270         newList.add(targetPos, item);
271         items.setValue(newList);
272     }
273     public void removeItem(int position) {
274         ArrayList<TemplateDetailsItem> newList = new ArrayList<>(items.getValue());
275         newList.remove(position);
276         applyList(newList);
277     }
278 }