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.
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.
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/>.
18 package net.ktnx.mobileledger.ui.templates;
20 import androidx.lifecycle.LiveData;
21 import androidx.lifecycle.MutableLiveData;
22 import androidx.lifecycle.Observer;
23 import androidx.lifecycle.ViewModel;
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;
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;
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;
56 public void setDefaultTemplateName(String name) {
57 mDefaultTemplateName = name;
60 public void resetItems() {
61 applyList(new ArrayList<>());
63 public void applyList(List<TemplateDetailsItem> srcList) {
64 applyList(srcList, false);
66 public void applyList(List<TemplateDetailsItem> srcList, boolean async) {
68 if (srcList == null) {
69 srcList = new ArrayList<>(items.getValue());
75 srcList = Collections.unmodifiableList(srcList);
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(),
84 ArrayList<TemplateDetailsItem> newList = new ArrayList<>();
86 boolean hasEmptyItem = false;
88 if (srcList.size() < 1) {
89 final TemplateDetailsItem.Header header = TemplateDetailsItem.createHeader();
95 newList.add(srcList.get(0));
98 for (int i = 1; i < srcList.size(); i++) {
99 final TemplateDetailsItem.AccountRow accRow = srcList.get(i)
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());
109 changes = true; // row skipped
114 accRow.setPosition(newList.size());
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);
130 final TemplateDetailsItem.AccountRow accountRow =
131 TemplateDetailsItem.createAccountRow();
132 accountRow.setId(genItemId());
133 accountRow.setPosition(newList.size());
134 newList.add(accountRow);
139 Logger.debug(TAG, "Changes detected, applying new list");
142 items.postValue(newList);
144 items.setValue(newList);
147 Logger.debug(TAG, "No changes, ignoring new list");
149 public int genItemId() {
150 return syntheticItemId.decrementAndGet();
152 public LiveData<List<TemplateDetailsItem>> getItems(Long patternId) {
153 if (itemsLoaded && Objects.equals(patternId, this.mPatternId))
156 if (patternId != null && patternId <= 0)
157 throw new IllegalArgumentException("Pattern ID " + patternId + " is invalid");
159 mPatternId = patternId;
161 if (mPatternId == null) {
168 LiveData<TemplateWithAccounts> dbList = db.getTemplateDAO()
169 .getTemplateWithAccounts(mPatternId);
170 Observer<TemplateWithAccounts> observer = new Observer<TemplateWithAccounts>() {
172 public void onChanged(TemplateWithAccounts src) {
173 ArrayList<TemplateDetailsItem> l = new ArrayList<>();
175 TemplateDetailsItem header = TemplateDetailsItem.fromRoomObject(src.header);
176 Logger.debug(DB_TAG, "Got header template item with id of " + header.getId());
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));
184 for (TemplateDetailsItem i : l) {
185 Logger.debug(DB_TAG, "Loaded pattern item " + i);
190 dbList.removeObserver(this);
193 dbList.observeForever(observer);
197 public void setTestText(String text) {
198 List<TemplateDetailsItem> list = new ArrayList<>(items.getValue());
199 TemplateDetailsItem.Header header = new TemplateDetailsItem.Header(list.get(0)
201 header.setTestText(text);
204 items.setValue(list);
206 public void onSaveTemplate() {
207 Logger.debug("flow", "PatternDetailsViewModel.onSavePattern(); model=" + this);
208 final List<TemplateDetailsItem> list = Objects.requireNonNull(items.getValue());
210 BaseDAO.runAsync(() -> {
211 boolean newPattern = mPatternId == null || mPatternId <= 0;
213 TemplateDetailsItem.Header modelHeader = list.get(0)
216 modelHeader.setName(Misc.trim(modelHeader.getName()));
217 if (modelHeader.getName()
219 modelHeader.setName(getDefaultTemplateName());
221 TemplateHeaderDAO headerDAO = DB.get()
223 TemplateHeader dbHeader = modelHeader.toDBO();
226 dbHeader.setId(mPatternId = headerDAO.insertSync(dbHeader));
229 headerDAO.updateSync(dbHeader);
231 Logger.debug("pattern-db",
232 String.format(Locale.US, "Stored pattern header %d, item=%s", dbHeader.getId(),
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)
242 TemplateAccount dbAccount = accRowItem.toDBO(dbHeader.getId());
243 dbAccount.setTemplateId(mPatternId);
244 dbAccount.setPosition(i);
245 if (dbAccount.getId() < 0) {
247 dbAccount.setId(taDAO.insertSync(dbAccount));
250 taDAO.updateSync(dbAccount);
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));
257 taDAO.finishSave(mPatternId);
260 private ArrayList<TemplateDetailsItem> copyItems() {
261 List<TemplateDetailsItem> oldList = items.getValue();
264 return new ArrayList<>();
266 ArrayList<TemplateDetailsItem> result = new ArrayList<>(oldList.size());
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()));
274 throw new RuntimeException("Unexpected item " + item);
279 public void moveItem(int sourcePos, int targetPos) {
280 final List<TemplateDetailsItem> newList = copyItems();
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);
287 String.format(Locale.US, " %d: id %d, pos %d", i, item.getId(),
288 item.getPosition()));
293 TemplateDetailsItem item = newList.remove(sourcePos);
294 newList.add(targetPos, item);
297 // adjust affected items' positions
299 int startPos, endPos;
300 if (sourcePos < targetPos) {
302 startPos = sourcePos;
307 startPos = targetPos;
311 for (int i = startPos; i <= endPos; i++) {
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);
322 String.format(Locale.US, " %d: id %d, pos %d", i, item.getId(),
323 item.getPosition()));
327 items.setValue(newList);
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++)