]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java
make transaction header row always have Id of 0
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / new_transaction / NewTransactionModel.java
index 23be3eaf0a4734495c132f8a394aaf9538d8add4..36b457ff4bf76bc1c0973a1db085196c2f3ee4e3 100644 (file)
@@ -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;
@@ -68,7 +70,6 @@ public class NewTransactionModel extends ViewModel {
     private final MutableLiveData<Boolean> isSubmittable = new InertMutableLiveData<>(false);
     private final MutableLiveData<Boolean> showComments = new MutableLiveData<>(true);
     private final MutableLiveData<List<Item>> items = new MutableLiveData<>();
-    private final MutableLiveData<Integer> accountCount = new InertMutableLiveData<>(0);
     private final MutableLiveData<Boolean> simulateSave = new InertMutableLiveData<>(false);
     private final AtomicInteger busyCounter = new AtomicInteger(0);
     private final MutableLiveData<Boolean> busyFlag = new InertMutableLiveData<>(false);
@@ -92,16 +93,31 @@ public class NewTransactionModel extends ViewModel {
         setItemsWithoutSubmittableChecks(newList);
     }
     private void setItemsWithoutSubmittableChecks(@NonNull List<Item> 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<Item> copyList() {
-        return copyList(null);
-    }
-    private List<Item> copyList(@Nullable List<Item> source) {
         List<Item> copy = new ArrayList<>();
-        List<Item> oldList = (source == null) ? items.getValue() : source;
+        List<Item> oldList = items.getValue();
 
         if (oldList != null)
             for (Item item : oldList) {
@@ -110,7 +126,7 @@ public class NewTransactionModel extends ViewModel {
 
         return copy;
     }
-    private List<Item> shallowCopyListWithoutItem(int position) {
+    private List<Item> copyListWithoutItem(int position) {
         List<Item> copy = new ArrayList<>();
         List<Item> oldList = items.getValue();
 
@@ -119,7 +135,7 @@ public class NewTransactionModel extends ViewModel {
             for (Item item : oldList) {
                 if (i++ == position)
                     continue;
-                copy.add(item);
+                copy.add(Item.from(item));
             }
         }
 
@@ -152,11 +168,15 @@ public class NewTransactionModel extends ViewModel {
         return this.isSubmittable;
     }
     void reset() {
+        Logger.debug("new-trans", "Resetting model");
         List<Item> list = new ArrayList<>();
+        Item.resetIdDispenser();
         list.add(new TransactionHead(""));
         list.add(new TransactionAccount(""));
         list.add(new TransactionAccount(""));
-        items.setValue(list);
+        noteFocusChanged(0, FocusedElement.Description);
+        isSubmittable.setValue(false);
+        setItemsWithoutSubmittableChecks(list);
     }
     boolean accountsInInitialState() {
         final List<Item> list = items.getValue();
@@ -260,7 +280,7 @@ public class NewTransactionModel extends ViewModel {
                   newItems.add(accRow);
               }
 
-              items.postValue(newItems);
+              new Handler(Looper.getMainLooper()).post(() -> setItems(newItems));
           });
     }
     private int extractIntFromMatches(MatchResult m, Integer group, Integer literal) {
@@ -310,7 +330,11 @@ public class NewTransactionModel extends ViewModel {
         return null;
     }
     void removeItem(int pos) {
-        List<Item> newList = shallowCopyListWithoutItem(pos);
+        Logger.debug("new-trans", String.format(Locale.US, "Removing item at position %d", pos));
+        List<Item> 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) {
@@ -325,6 +349,11 @@ public class NewTransactionModel extends ViewModel {
         List<Item> 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<Item> list, int index) {
@@ -340,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<Item> 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<Boolean> observer) {
         busyFlag.removeObserver(observer);
@@ -401,6 +443,7 @@ public class NewTransactionModel extends ViewModel {
     }
     void loadTransactionIntoModel(String profileUUID, int transactionId) {
         List<Item> newList = new ArrayList<>();
+        Item.resetIdDispenser();
         LedgerTransaction tr;
         MobileLedgerProfile profile = Data.getProfile(profileUUID);
         if (profile == null)
@@ -463,9 +506,9 @@ public class NewTransactionModel extends ViewModel {
             moveItemLast(newList, singlePositiveIndex);
         }
 
-        noteFocusChanged(1, FocusedElement.Description);
-
         setItems(newList);
+
+        noteFocusChanged(1, FocusedElement.Amount);
     }
     /**
      * A transaction is submittable if:
@@ -487,14 +530,14 @@ public class NewTransactionModel extends ViewModel {
     @SuppressLint("DefaultLocale")
     void checkTransactionSubmittable(@Nullable List<Item> 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();
@@ -593,21 +636,15 @@ public class NewTransactionModel extends ViewModel {
                         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;
                             }
                         }
@@ -653,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;
                             }
                         }
@@ -675,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;
                             }
                         }
@@ -716,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)
@@ -736,10 +757,6 @@ public class NewTransactionModel extends ViewModel {
             for (String currName : emptyRowsForCurrency.currencies()) {
                 List<Item> emptyItems = emptyRowsForCurrency.getList(currName);
                 while ((list.size() > MIN_ITEMS) && (emptyItems.size() > 1)) {
-                    if (workingWithLiveList && !liveListCopied) {
-                        list = copyList(list);
-                        liveListCopied = true;
-                    }
                     // the list is a copy, so the empty item is no longer present
                     Item itemToRemove = emptyItems.remove(1);
                     removeItemById(list, itemToRemove.id);
@@ -751,10 +768,6 @@ public class NewTransactionModel extends ViewModel {
                     List<Item> currItems = itemsForCurrency.getList(currName);
 
                     if (currItems.size() == 1) {
-                        if (workingWithLiveList && !liveListCopied) {
-                            list = copyList(list);
-                            liveListCopied = true;
-                        }
                         // the list is a copy, so the empty item is no longer present
                         removeItemById(list, emptyItems.get(0).id);
                         listChanged = true;
@@ -765,15 +778,10 @@ 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() < MIN_ITEMS) {
-                if (workingWithLiveList && !liveListCopied) {
-                    list = copyList(list);
-                    liveListCopied = true;
-                }
                 list.add(new TransactionAccount(""));
                 listChanged = true;
             }
 
-
             Logger.debug("submittable", submittable ? "YES" : "NO");
             isSubmittable.setValue(submittable);
 
@@ -832,9 +840,6 @@ public class NewTransactionModel extends ViewModel {
 
         setItems(newList);
     }
-    public LiveData<Integer> getAccountCount() {
-        return accountCount;
-    }
     public boolean accountListIsEmpty() {
         List<Item> items = Objects.requireNonNull(this.items.getValue());
 
@@ -860,11 +865,17 @@ 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)
@@ -873,6 +884,9 @@ public class NewTransactionModel extends ViewModel {
                 return new TransactionAccount((TransactionAccount) origin);
             throw new RuntimeException("Don't know how to handle " + origin);
         }
+        private static void resetIdDispenser() {
+            idDispenser = 0;
+        }
         public int getId() {
             return id;
         }
@@ -921,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;
@@ -970,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()));
@@ -1004,8 +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);
+                   Misc.equalStrings(description, other.description) &&
+                   Misc.equalStrings(comment, other.comment);
         }
     }
 
@@ -1019,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;
@@ -1030,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();
@@ -1045,6 +1063,9 @@ public class NewTransactionModel extends ViewModel {
             this.accountName = accountName;
             this.currency = currency;
         }
+        public boolean isLast() {
+            return isLast;
+        }
         public boolean isAmountSet() {
             return amountSet;
         }
@@ -1131,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 {