X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Fui%2Fnew_transaction%2FNewTransactionModel.java;h=36b457ff4bf76bc1c0973a1db085196c2f3ee4e3;hp=c84c26d20bf21b532ce6a4e982e688f8841b5a18;hb=f973784f579d42988174acf0b24593aa23180fa6;hpb=346b3c8e74a12b1822239481f807479fa81fc706 diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java index c84c26d2..36b457ff 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java @@ -18,6 +18,8 @@ package net.ktnx.mobileledger.ui.new_transaction; import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -57,17 +59,17 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.MatchResult; -enum ItemType {generalData, transactionRow, bottomFiller} +enum ItemType {generalData, transactionRow} enum FocusedElement {Account, Comment, Amount, Description, TransactionComment} public class NewTransactionModel extends ViewModel { + private static final int MIN_ITEMS = 3; private final MutableLiveData showCurrency = new MutableLiveData<>(false); private final MutableLiveData isSubmittable = new InertMutableLiveData<>(false); private final MutableLiveData showComments = new MutableLiveData<>(true); private final MutableLiveData> items = new MutableLiveData<>(); - private final MutableLiveData accountCount = new InertMutableLiveData<>(0); private final MutableLiveData simulateSave = new InertMutableLiveData<>(false); private final AtomicInteger busyCounter = new AtomicInteger(0); private final MutableLiveData busyFlag = new InertMutableLiveData<>(false); @@ -91,16 +93,31 @@ public class NewTransactionModel extends ViewModel { setItemsWithoutSubmittableChecks(newList); } private void setItemsWithoutSubmittableChecks(@NonNull List list) { - Logger.debug("new-trans", "model: Setting new item list"); + final int cnt = list.size(); + for (int i = 1; i < cnt - 1; i++) { + final TransactionAccount item = list.get(i) + .toTransactionAccount(); + if (item.isLast) { + TransactionAccount replacement = new TransactionAccount(item); + replacement.isLast = false; + list.set(i, replacement); + } + } + final TransactionAccount last = list.get(cnt - 1) + .toTransactionAccount(); + if (!last.isLast) { + TransactionAccount replacement = new TransactionAccount(last); + replacement.isLast = true; + list.set(cnt - 1, replacement); + } + + if (BuildConfig.DEBUG) + dumpItemList("Before setValue()", list); items.setValue(list); - accountCount.setValue(list.size() - 2); } private List copyList() { - return copyList(null); - } - private List copyList(@Nullable List source) { List copy = new ArrayList<>(); - List oldList = (source == null) ? items.getValue() : source; + List oldList = items.getValue(); if (oldList != null) for (Item item : oldList) { @@ -109,7 +126,7 @@ public class NewTransactionModel extends ViewModel { return copy; } - private List shallowCopyListWithoutItem(int position) { + private List copyListWithoutItem(int position) { List copy = new ArrayList<>(); List oldList = items.getValue(); @@ -118,7 +135,7 @@ public class NewTransactionModel extends ViewModel { for (Item item : oldList) { if (i++ == position) continue; - copy.add(item); + copy.add(Item.from(item)); } } @@ -151,12 +168,15 @@ public class NewTransactionModel extends ViewModel { return this.isSubmittable; } void reset() { + Logger.debug("new-trans", "Resetting model"); List list = new ArrayList<>(); + Item.resetIdDispenser(); list.add(new TransactionHead("")); list.add(new TransactionAccount("")); list.add(new TransactionAccount("")); - list.add(new BottomFiller()); - items.setValue(list); + noteFocusChanged(0, FocusedElement.Description); + isSubmittable.setValue(false); + setItemsWithoutSubmittableChecks(list); } boolean accountsInInitialState() { final List list = items.getValue(); @@ -225,7 +245,7 @@ public class NewTransactionModel extends ViewModel { newItems.add(head); - for (int i = 1; i < present.size() - 1; i++) { + for (int i = 1; i < present.size(); i++) { final TransactionAccount row = present.get(i) .toTransactionAccount(); if (!row.isEmpty()) @@ -260,9 +280,7 @@ public class NewTransactionModel extends ViewModel { newItems.add(accRow); } - newItems.add(new BottomFiller()); - - items.postValue(newItems); + new Handler(Looper.getMainLooper()).post(() -> setItems(newItems)); }); } private int extractIntFromMatches(MatchResult m, Integer group, Integer literal) { @@ -312,7 +330,11 @@ public class NewTransactionModel extends ViewModel { return null; } void removeItem(int pos) { - List newList = shallowCopyListWithoutItem(pos); + Logger.debug("new-trans", String.format(Locale.US, "Removing item at position %d", pos)); + List newList = copyListWithoutItem(pos); + final FocusInfo fi = focusInfo.getValue(); + if ((fi != null) && (pos < fi.position)) + noteFocusChanged(fi.position - 1, fi.element); setItems(newList); } void noteFocusChanged(int position, FocusedElement element) { @@ -327,6 +349,11 @@ public class NewTransactionModel extends ViewModel { List newList = shallowCopyList(); Item item = newList.remove(fromIndex); newList.add(toIndex, item); + + FocusInfo fi = focusInfo.getValue(); + if (fi != null && fi.position == fromIndex) + noteFocusChanged(toIndex, fi.element); + items.setValue(newList); // same count, same submittable state } void moveItemLast(List list, int index) { @@ -342,7 +369,20 @@ public class NewTransactionModel extends ViewModel { list.add(list.remove(index)); } void toggleCurrencyVisible() { - showCurrency.setValue(!Objects.requireNonNull(showCurrency.getValue())); + final boolean newValue = !Objects.requireNonNull(showCurrency.getValue()); + + // remove currency from all items, or reset currency to the default + // no need to clone the list, because the removal of the currency won't lead to + // visual changes -- the currency fields will be hidden or reset to default anyway + // still, there may be changes in the submittable state + final List list = Objects.requireNonNull(this.items.getValue()); + for (int i = 1; i < list.size(); i++) { + ((TransactionAccount) list.get(i)).setCurrency(newValue ? Data.getProfile() + .getDefaultCommodity() + : null); + } + checkTransactionSubmittable(null); + showCurrency.setValue(newValue); } void stopObservingBusyFlag(Observer observer) { busyFlag.removeObserver(observer); @@ -373,7 +413,7 @@ public class NewTransactionModel extends ViewModel { tr.setComment(head.getComment()); LedgerTransactionAccount emptyAmountAccount = null; float emptyAmountAccountBalance = 0; - for (int i = 1; i < list.size() - 1; i++) { + for (int i = 1; i < list.size(); i++) { TransactionAccount item = list.get(i) .toTransactionAccount(); LedgerTransactionAccount acc = new LedgerTransactionAccount(item.getAccountName() @@ -403,6 +443,7 @@ public class NewTransactionModel extends ViewModel { } void loadTransactionIntoModel(String profileUUID, int transactionId) { List newList = new ArrayList<>(); + Item.resetIdDispenser(); LedgerTransaction tr; MobileLedgerProfile profile = Data.getProfile(profileUUID); if (profile == null) @@ -453,6 +494,8 @@ public class NewTransactionModel extends ViewModel { else item.resetAmount(); } + if (BuildConfig.DEBUG) + dumpItemList("Loaded previous transaction", newList); if (singleNegativeIndex != -1) { firstNegative.resetAmount(); @@ -463,11 +506,9 @@ public class NewTransactionModel extends ViewModel { moveItemLast(newList, singlePositiveIndex); } - noteFocusChanged(1, FocusedElement.Description); - - newList.add(new BottomFiller()); - setItems(newList); + + noteFocusChanged(1, FocusedElement.Amount); } /** * A transaction is submittable if: @@ -489,14 +530,14 @@ public class NewTransactionModel extends ViewModel { @SuppressLint("DefaultLocale") void checkTransactionSubmittable(@Nullable List list) { boolean workingWithLiveList = false; - boolean liveListCopied = false; if (list == null) { - list = Objects.requireNonNull(items.getValue()); + list = copyList(); workingWithLiveList = true; } if (BuildConfig.DEBUG) - dumpItemList("Before submittable checks", list); + dumpItemList(String.format("Before submittable checks (%s)", + workingWithLiveList ? "LIVE LIST" : "custom list"), list); int accounts = 0; final BalanceForCurrency balance = new BalanceForCurrency(); @@ -522,7 +563,7 @@ public class NewTransactionModel extends ViewModel { submittable = false; } - for (int i = 1; i < list.size() - 1; i++) { + for (int i = 1; i < list.size(); i++) { TransactionAccount item = list.get(i) .toTransactionAccount(); @@ -589,27 +630,21 @@ public class NewTransactionModel extends ViewModel { float currencyBalance = balance.get(balCurrency); if (Misc.isZero(currencyBalance)) { // remove hints from all amount inputs in that currency - for (int i = 1; i < list.size() - 1; i++) { + for (int i = 1; i < list.size(); i++) { TransactionAccount acc = list.get(i) .toTransactionAccount(); if (Misc.equalStrings(acc.getCurrency(), balCurrency)) { if (BuildConfig.DEBUG) Logger.debug("submittable", - String.format("Resetting hint of '%s' [%s]", - Misc.nullIsEmpty(acc.getAccountName()), + String.format(Locale.US, "Resetting hint of %d:'%s' [%s]", + i, Misc.nullIsEmpty(acc.getAccountName()), balCurrency)); - if (acc.amountHintIsSet && !TextUtils.isEmpty(acc.getAmountHint())) { - if (workingWithLiveList && !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - final TransactionAccount newAcc = new TransactionAccount(acc); - newAcc.setAmountHint(null); - if (!liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - list.set(i, newAcc); + // skip if the amount is set, in which case the hint is not + // important/visible + if (!acc.isAmountSet() && acc.amountHintIsSet && + !TextUtils.isEmpty(acc.getAmountHint())) + { + acc.setAmountHint(null); listChanged = true; } } @@ -655,18 +690,12 @@ public class NewTransactionModel extends ViewModel { if (item == receiver) { final String hint = String.format("%1.2f", -currencyBalance); if (!acc.isAmountHintSet() || - !TextUtils.equals(acc.getAmountHint(), hint)) + !Misc.equalStrings(acc.getAmountHint(), hint)) { Logger.debug("submittable", String.format("Setting amount hint of {%s} to %s [%s]", acc.toString(), hint, balCurrency)); - if (workingWithLiveList & !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - final TransactionAccount newAcc = new TransactionAccount(acc); - newAcc.setAmountHint(hint); - list.set(i, newAcc); + acc.setAmountHint(hint); listChanged = true; } } @@ -677,13 +706,7 @@ public class NewTransactionModel extends ViewModel { Misc.nullIsEmpty(acc.getAccountName()), balCurrency)); if (acc.amountHintIsSet && !TextUtils.isEmpty(acc.getAmountHint())) { - if (workingWithLiveList && !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - final TransactionAccount newAcc = new TransactionAccount(acc); - newAcc.setAmountHint(null); - list.set(i, newAcc); + acc.setAmountHint(null); listChanged = true; } } @@ -718,10 +741,6 @@ public class NewTransactionModel extends ViewModel { // } // // if (!foundIt) - if (workingWithLiveList && !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } final TransactionAccount newAcc = new TransactionAccount("", balCurrency); final float bal = balance.get(balCurrency); if (!Misc.isZero(bal) && currAmounts == currRows) @@ -729,7 +748,7 @@ public class NewTransactionModel extends ViewModel { Logger.debug("submittable", String.format("Adding new item with %s for currency %s", newAcc.getAmountHint(), balCurrency)); - list.add(list.size() - 1, newAcc); + list.add(newAcc); listChanged = true; } } @@ -737,27 +756,20 @@ public class NewTransactionModel extends ViewModel { // drop extra empty rows, not needed for (String currName : emptyRowsForCurrency.currencies()) { List emptyItems = emptyRowsForCurrency.getList(currName); - while ((list.size() > 4) && (emptyItems.size() > 1)) { - if (workingWithLiveList && !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - Item item = emptyItems.remove(1); - list.remove(item); + while ((list.size() > MIN_ITEMS) && (emptyItems.size() > 1)) { + // the list is a copy, so the empty item is no longer present + Item itemToRemove = emptyItems.remove(1); + removeItemById(list, itemToRemove.id); listChanged = true; } // unused currency, remove last item (which is also an empty one) - if ((list.size() > 4) && (emptyItems.size() == 1)) { + if ((list.size() > MIN_ITEMS) && (emptyItems.size() == 1)) { List currItems = itemsForCurrency.getList(currName); if (currItems.size() == 1) { - if (workingWithLiveList && !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - Item item = emptyItems.get(0); - list.remove(item); + // the list is a copy, so the empty item is no longer present + removeItemById(list, emptyItems.get(0).id); listChanged = true; } } @@ -765,16 +777,11 @@ public class NewTransactionModel extends ViewModel { // 6) at least two rows need to be present in the ledger // (the list also contains header and trailer) - while (list.size() < 4) { - if (workingWithLiveList && !liveListCopied) { - list = copyList(list); - liveListCopied = true; - } - list.add(list.size() - 1, new TransactionAccount("")); + while (list.size() < MIN_ITEMS) { + list.add(new TransactionAccount("")); listChanged = true; } - Logger.debug("submittable", submittable ? "YES" : "NO"); isSubmittable.setValue(submittable); @@ -795,10 +802,23 @@ public class NewTransactionModel extends ViewModel { setItemsWithoutSubmittableChecks(list); } } + private void removeItemById(@NotNull List list, int id) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + list.removeIf(item -> item.id == id); + } + else { + for (Item item : list) { + if (item.id == id) { + list.remove(item); + break; + } + } + } + } @SuppressLint("DefaultLocale") private void dumpItemList(@NotNull String msg, @NotNull List list) { Logger.debug("submittable", "== Dump of all items " + msg); - for (int i = 1; i < list.size() - 1; i++) { + for (int i = 1; i < list.size(); i++) { TransactionAccount item = list.get(i) .toTransactionAccount(); Logger.debug("submittable", String.format("%d:%s", i, item.toString())); @@ -820,9 +840,6 @@ public class NewTransactionModel extends ViewModel { setItems(newList); } - public LiveData getAccountCount() { - return accountCount; - } public boolean accountListIsEmpty() { List items = Objects.requireNonNull(this.items.getValue()); @@ -848,21 +865,28 @@ public class NewTransactionModel extends ViewModel { static abstract class Item { private static int idDispenser = 0; - protected int id; + protected final int id; private Item() { - synchronized (Item.class) { - id = ++idDispenser; - } + if (this instanceof TransactionHead) + id = 0; + else + synchronized (Item.class) { + id = ++idDispenser; + } + } + public Item(int id) { + this.id = id; } public static Item from(Item origin) { if (origin instanceof TransactionHead) return new TransactionHead((TransactionHead) origin); if (origin instanceof TransactionAccount) return new TransactionAccount((TransactionAccount) origin); - if (origin instanceof BottomFiller) - return new BottomFiller((BottomFiller) origin); throw new RuntimeException("Don't know how to handle " + origin); } + private static void resetIdDispenser() { + idDispenser = 0; + } public int getId() { return id; } @@ -894,8 +918,6 @@ public class NewTransactionModel extends ViewModel { return ((TransactionHead) item).equalContents((TransactionHead) this); if (this instanceof TransactionAccount) return ((TransactionAccount) item).equalContents((TransactionAccount) this); - if (this instanceof BottomFiller) - return true; throw new RuntimeException("Don't know how to handle " + this); } @@ -913,7 +935,7 @@ public class NewTransactionModel extends ViewModel { this.description = description; } public TransactionHead(TransactionHead origin) { - id = origin.id; + super(origin.id); date = origin.date; description = origin.description; comment = origin.comment; @@ -962,7 +984,7 @@ public class NewTransactionModel extends ViewModel { if (TextUtils.isEmpty(description)) b.append(" «no description»"); else - b.append(String.format(" descr'%s'", description)); + b.append(String.format(" '%s'", description)); if (date != null) b.append(String.format("@%s", date.toString())); @@ -996,28 +1018,8 @@ public class NewTransactionModel extends ViewModel { return false; return Objects.equals(date, other.date) && - TextUtils.equals(description, other.description) && - TextUtils.equals(comment, other.comment); - } - } - - public static class BottomFiller extends Item { - public BottomFiller(BottomFiller origin) { - id = origin.id; - // nothing to do - } - public BottomFiller() { - super(); - } - @Override - public ItemType getType() { - return ItemType.bottomFiller; - } - @SuppressLint("DefaultLocale") - @NonNull - @Override - public String toString() { - return String.format("id:%d «bottom filler»", id); + Misc.equalStrings(description, other.description) && + Misc.equalStrings(comment, other.comment); } } @@ -1031,8 +1033,10 @@ public class NewTransactionModel extends ViewModel { private boolean amountValid = true; private FocusedElement focusedElement = FocusedElement.Account; private boolean amountHintIsSet = false; + private boolean isLast = false; + private int accountNameCursorPosition; public TransactionAccount(TransactionAccount origin) { - id = origin.id; + super(origin.id); accountName = origin.accountName; amount = origin.amount; amountSet = origin.amountSet; @@ -1042,6 +1046,8 @@ public class NewTransactionModel extends ViewModel { currency = origin.currency; amountValid = origin.amountValid; focusedElement = origin.focusedElement; + isLast = origin.isLast; + accountNameCursorPosition = origin.accountNameCursorPosition; } public TransactionAccount(LedgerTransactionAccount account) { super(); @@ -1057,6 +1063,9 @@ public class NewTransactionModel extends ViewModel { this.accountName = accountName; this.currency = currency; } + public boolean isLast() { + return isLast; + } public boolean isAmountSet() { return amountSet; } @@ -1143,25 +1152,37 @@ public class NewTransactionModel extends ViewModel { if (!TextUtils.isEmpty(comment)) b.append(String.format(" /%s/", comment)); + if (isLast) + b.append(" last"); + return b.toString(); } public boolean equalContents(TransactionAccount other) { if (other == null) return false; - boolean equal = TextUtils.equals(accountName, other.accountName) && - TextUtils.equals(comment, other.comment) && - (amountSet ? other.amountSet && amount == other.amount - : !other.amountSet) && - (amountHintIsSet ? other.amountHintIsSet && - TextUtils.equals(amountHint, other.amountHint) - : !other.amountHintIsSet) && - TextUtils.equals(currency, other.currency); + boolean equal = Misc.equalStrings(accountName, other.accountName); + equal = equal && Misc.equalStrings(comment, other.comment) && + (amountSet ? other.amountSet && amount == other.amount : !other.amountSet); + + // compare amount hint only if there is no amount + if (!amountSet) + equal = equal && (amountHintIsSet ? other.amountHintIsSet && + Misc.equalStrings(amountHint, other.amountHint) + : !other.amountHintIsSet); + equal = equal && Misc.equalStrings(currency, other.currency) && isLast == other.isLast; + Logger.debug("new-trans", String.format("Comparing {%s} and {%s}: %s", this.toString(), other.toString(), equal)); return equal; } + public int getAccountNameCursorPosition() { + return accountNameCursorPosition; + } + public void setAccountNameCursorPosition(int position) { + this.accountNameCursorPosition = position; + } } private static class BalanceForCurrency {