]> git.ktnx.net Git - mobile-ledger.git/blobdiff - app/src/main/java/net/ktnx/mobileledger/ui/new_transaction/NewTransactionModel.java
new transaction: currency can't be null
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / new_transaction / NewTransactionModel.java
index 5ad014f2f71f29d105e9d1993a673d34eee298b5..b3e9eeb3c8b6b26d5a7be7b0aaa911fdb9cb9f92 100644 (file)
@@ -18,8 +18,6 @@
 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;
@@ -31,15 +29,17 @@ import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModel;
 
 import net.ktnx.mobileledger.BuildConfig;
+import net.ktnx.mobileledger.db.Currency;
 import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
 import net.ktnx.mobileledger.db.TemplateAccount;
 import net.ktnx.mobileledger.db.TemplateHeader;
+import net.ktnx.mobileledger.db.TransactionWithAccounts;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.InertMutableLiveData;
 import net.ktnx.mobileledger.model.LedgerTransaction;
 import net.ktnx.mobileledger.model.LedgerTransactionAccount;
 import net.ktnx.mobileledger.model.MatchedTemplate;
-import net.ktnx.mobileledger.model.MobileLedgerProfile;
 import net.ktnx.mobileledger.utils.Globals;
 import net.ktnx.mobileledger.utils.Logger;
 import net.ktnx.mobileledger.utils.Misc;
@@ -73,7 +73,7 @@ public class NewTransactionModel extends ViewModel {
     private final MutableLiveData<Boolean> simulateSave = new InertMutableLiveData<>(false);
     private final AtomicInteger busyCounter = new AtomicInteger(0);
     private final MutableLiveData<Boolean> busyFlag = new InertMutableLiveData<>(false);
-    private final Observer<MobileLedgerProfile> profileObserver = profile -> {
+    private final Observer<Profile> profileObserver = profile -> {
         showCurrency.postValue(profile.getShowCommodityByDefault());
         showComments.postValue(profile.getShowCommentsByDefault());
     };
@@ -92,8 +92,27 @@ public class NewTransactionModel extends ViewModel {
         checkTransactionSubmittable(newList);
         setItemsWithoutSubmittableChecks(newList);
     }
+    private void replaceItems(@NonNull List<Item> newList) {
+        renumberItems();
+
+        setItems(newList);
+    }
+    /**
+     * make old items replaceable in-place. makes the new values visually blend in
+     */
+    private void renumberItems() {
+        renumberItems(items.getValue());
+    }
+    private void renumberItems(List<Item> list) {
+        if (list == null) {
+            return;
+        }
+
+        int id = 0;
+        for (Item item : list)
+            item.id = id++;
+    }
     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)
@@ -112,14 +131,13 @@ public class NewTransactionModel extends ViewModel {
             list.set(cnt - 1, replacement);
         }
 
+        if (BuildConfig.DEBUG)
+            dumpItemList("Before setValue()", list);
         items.setValue(list);
     }
     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) {
@@ -128,7 +146,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();
 
@@ -137,7 +155,7 @@ public class NewTransactionModel extends ViewModel {
             for (Item item : oldList) {
                 if (i++ == position)
                     continue;
-                copy.add(item);
+                copy.add(Item.from(item));
             }
         }
 
@@ -172,10 +190,14 @@ public class NewTransactionModel extends ViewModel {
     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(""));
+        final String defaultCurrency = Objects.requireNonNull(Data.getProfile())
+                                              .getDefaultCommodity();
+        list.add(new TransactionAccount("", defaultCurrency));
+        list.add(new TransactionAccount("", defaultCurrency));
         noteFocusChanged(0, FocusedElement.Description);
+        renumberItems();
         isSubmittable.setValue(false);
         setItemsWithoutSubmittableChecks(list);
     }
@@ -272,25 +294,33 @@ public class NewTransactionModel extends ViewModel {
                   if (amount != null && acc.getNegateAmount() != null && acc.getNegateAmount())
                       amount = -amount;
 
-                  // TODO currency
                   TransactionAccount accRow = new TransactionAccount(accountName);
                   accRow.setComment(accountComment);
                   if (amount != null)
                       accRow.setAmount(amount);
+                  accRow.setCurrency(
+                          extractCurrencyFromMatches(matchResult, acc.getCurrencyMatchGroup(),
+                                  acc.getCurrencyObject()));
 
                   newItems.add(accRow);
               }
 
-              new Handler(Looper.getMainLooper()).post(() -> setItems(newItems));
+              renumberItems(newItems);
+              Misc.onMainThread(() -> replaceItems(newItems));
           });
     }
+    @NonNull
+    private String extractCurrencyFromMatches(MatchResult m, Integer group, Currency literal) {
+        return Misc.nullIsEmpty(
+                extractStringFromMatches(m, group, (literal == null) ? "" : literal.getName()));
+    }
     private int extractIntFromMatches(MatchResult m, Integer group, Integer literal) {
         if (literal != null)
             return literal;
 
         if (group != null) {
             int grp = group;
-            if (grp > 0 & grp <= m.groupCount())
+            if (grp > 0 && grp <= m.groupCount())
                 try {
                     return Integer.parseInt(m.group(grp));
                 }
@@ -301,13 +331,14 @@ public class NewTransactionModel extends ViewModel {
 
         return 0;
     }
+    @Nullable
     private String extractStringFromMatches(MatchResult m, Integer group, String literal) {
         if (literal != null)
             return literal;
 
         if (group != null) {
             int grp = group;
-            if (grp > 0 & grp <= m.groupCount())
+            if (grp > 0 && grp <= m.groupCount())
                 return m.group(grp);
         }
 
@@ -319,7 +350,7 @@ public class NewTransactionModel extends ViewModel {
 
         if (group != null) {
             int grp = group;
-            if (grp > 0 & grp <= m.groupCount())
+            if (grp > 0 && grp <= m.groupCount())
                 try {
                     return Float.valueOf(m.group(grp));
                 }
@@ -331,7 +362,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) {
@@ -346,6 +381,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) {
@@ -368,10 +408,10 @@ public class NewTransactionModel extends ViewModel {
         // 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());
+        final Profile profile = Objects.requireNonNull(Data.getProfile());
         for (int i = 1; i < list.size(); i++) {
-            ((TransactionAccount) list.get(i)).setCurrency(newValue ? Data.getProfile()
-                                                                          .getDefaultCommodity()
-                                                                    : null);
+            ((TransactionAccount) list.get(i)).setCurrency(
+                    newValue ? profile.getDefaultCommodity() : "");
         }
         checkTransactionSubmittable(null);
         showCurrency.setValue(newValue);
@@ -399,7 +439,6 @@ public class NewTransactionModel extends ViewModel {
         List<Item> list = Objects.requireNonNull(items.getValue());
         TransactionHead head = list.get(0)
                                    .toTransactionHead();
-        SimpleDate date = head.getDate();
         LedgerTransaction tr = head.asLedgerTransaction();
 
         tr.setComment(head.getComment());
@@ -433,22 +472,23 @@ public class NewTransactionModel extends ViewModel {
 
         return tr;
     }
-    void loadTransactionIntoModel(String profileUUID, int transactionId) {
+    void loadTransactionIntoModel(@NonNull TransactionWithAccounts tr) {
         List<Item> newList = new ArrayList<>();
-        LedgerTransaction tr;
-        MobileLedgerProfile profile = Data.getProfile(profileUUID);
-        if (profile == null)
-            throw new RuntimeException(String.format(
-                    "Unable to find profile %s, which is supposed to contain transaction %d",
-                    profileUUID, transactionId));
+        Item.resetIdDispenser();
 
-        tr = profile.loadTransaction(transactionId);
-        TransactionHead head = new TransactionHead(tr.getDescription());
-        head.setComment(tr.getComment());
+        Item currentHead = Objects.requireNonNull(items.getValue())
+                                  .get(0);
+        TransactionHead head = new TransactionHead(tr.transaction.getDescription());
+        head.setComment(tr.transaction.getComment());
+        if (currentHead instanceof TransactionHead)
+            head.setDate(((TransactionHead) currentHead).date);
 
         newList.add(head);
 
-        List<LedgerTransactionAccount> accounts = tr.getAccounts();
+        List<LedgerTransactionAccount> accounts = new ArrayList<>();
+        for (net.ktnx.mobileledger.db.TransactionAccount acc : tr.accounts) {
+            accounts.add(new LedgerTransactionAccount(acc));
+        }
 
         TransactionAccount firstNegative = null;
         TransactionAccount firstPositive = null;
@@ -497,9 +537,10 @@ public class NewTransactionModel extends ViewModel {
             moveItemLast(newList, singlePositiveIndex);
         }
 
-        setItems(newList);
-
-        noteFocusChanged(1, FocusedElement.Amount);
+        Misc.onMainThread(() -> {
+            setItems(newList);
+            noteFocusChanged(1, FocusedElement.Amount);
+        });
     }
     /**
      * A transaction is submittable if:
@@ -521,14 +562,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();
@@ -554,6 +595,8 @@ public class NewTransactionModel extends ViewModel {
                 submittable = false;
             }
 
+            boolean hasInvalidAmount = false;
+
             for (int i = 1; i < list.size(); i++) {
                 TransactionAccount item = list.get(i)
                                               .toTransactionAccount();
@@ -583,16 +626,19 @@ public class NewTransactionModel extends ViewModel {
                     itemsWithAccountForCurrency.add(currName, item);
                 }
 
-                if (!item.isAmountValid()) {
-                    Logger.debug("submittable",
-                            String.format("Not submittable: row %d has an invalid amount", i + 1));
-                    submittable = false;
-                }
-                else if (item.isAmountSet()) {
+                if (item.isAmountSet()) {
                     itemsWithAmountForCurrency.add(currName, item);
                     balance.add(currName, item.getAmount());
                 }
                 else {
+                    if (!item.isAmountValid()) {
+                        Logger.debug("submittable",
+                                String.format("Not submittable: row %d has an invalid amount",
+                                        i + 1));
+                        submittable = false;
+                        hasInvalidAmount = true;
+                    }
+
                     itemsWithEmptyAmountForCurrency.add(currName, item);
 
                     if (!accName.isEmpty())
@@ -627,21 +673,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));
                             // skip if the amount is set, in which case the hint is not
                             // important/visible
                             if (!acc.isAmountSet() && 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;
                             }
                         }
@@ -655,11 +695,11 @@ public class NewTransactionModel extends ViewModel {
                         if (BuildConfig.DEBUG) {
                             if (balanceReceiversCount == 0)
                                 Logger.debug("submittable", String.format(
-                                        "Transaction not submittable [%s]: non-zero balance " +
+                                        "Transaction not submittable [curr:%s]: non-zero balance " +
                                         "with no empty amounts with accounts", balCurrency));
                             else
                                 Logger.debug("submittable", String.format(
-                                        "Transaction not submittable [%s]: non-zero balance " +
+                                        "Transaction not submittable [curr:%s]: non-zero balance " +
                                         "with multiple empty amounts with accounts", balCurrency));
                         }
                         submittable = false;
@@ -687,18 +727,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;
                             }
                         }
@@ -709,13 +743,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;
                             }
                         }
@@ -725,16 +753,17 @@ public class NewTransactionModel extends ViewModel {
 
             // 5) a row with an empty account name or empty amount is guaranteed to exist for
             // each commodity
-            for (String balCurrency : balance.currencies()) {
-                int currEmptyRows = itemsWithEmptyAccountForCurrency.size(balCurrency);
-                int currRows = itemsForCurrency.size(balCurrency);
-                int currAccounts = itemsWithAccountForCurrency.size(balCurrency);
-                int currAmounts = itemsWithAmountForCurrency.size(balCurrency);
-                if ((currEmptyRows == 0) &&
-                    ((currRows == currAccounts) || (currRows == currAmounts)))
-                {
-                    // perhaps there already is an unused empty row for another currency that
-                    // is not used?
+            if (!hasInvalidAmount) {
+                for (String balCurrency : balance.currencies()) {
+                    int currEmptyRows = itemsWithEmptyAccountForCurrency.size(balCurrency);
+                    int currRows = itemsForCurrency.size(balCurrency);
+                    int currAccounts = itemsWithAccountForCurrency.size(balCurrency);
+                    int currAmounts = itemsWithAmountForCurrency.size(balCurrency);
+                    if ((currEmptyRows == 0) &&
+                        ((currRows == currAccounts) || (currRows == currAmounts)))
+                    {
+                        // perhaps there already is an unused empty row for another currency that
+                        // is not used?
 //                        boolean foundIt = false;
 //                        for (Item item : emptyRows) {
 //                            Currency itemCurrency = item.getCurrency();
@@ -750,19 +779,16 @@ 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)
+                            newAcc.setAmountHint(String.format("%4.2f", -bal));
+                        Logger.debug("submittable",
+                                String.format("Adding new item with %s for currency %s",
+                                        newAcc.getAmountHint(), balCurrency));
+                        list.add(newAcc);
+                        listChanged = true;
                     }
-                    final TransactionAccount newAcc = new TransactionAccount("", balCurrency);
-                    final float bal = balance.get(balCurrency);
-                    if (!Misc.isZero(bal) && currAmounts == currRows)
-                        newAcc.setAmountHint(String.format("%4.2f", -bal));
-                    Logger.debug("submittable",
-                            String.format("Adding new item with %s for currency %s",
-                                    newAcc.getAmountHint(), balCurrency));
-                    list.add(newAcc);
-                    listChanged = true;
                 }
             }
 
@@ -770,10 +796,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);
@@ -785,10 +807,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;
@@ -799,15 +817,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);
 
@@ -893,9 +906,15 @@ public class NewTransactionModel extends ViewModel {
         private static int idDispenser = 0;
         protected 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)
@@ -904,6 +923,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;
         }
@@ -952,7 +974,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;
@@ -1001,7 +1023,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()));
@@ -1028,15 +1050,16 @@ public class NewTransactionModel extends ViewModel {
             return ItemType.generalData;
         }
         public LedgerTransaction asLedgerTransaction() {
-            return new LedgerTransaction(null, date, description, Data.getProfile());
+            return new LedgerTransaction(0, (date == null) ? SimpleDate.today() : date, description,
+                    Objects.requireNonNull(Data.getProfile()));
         }
         public boolean equalContents(TransactionHead other) {
             if (other == null)
                 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);
         }
     }
 
@@ -1044,7 +1067,7 @@ public class NewTransactionModel extends ViewModel {
         private String accountName;
         private String amountHint;
         private String comment;
-        private String currency;
+        private String currency = "";
         private float amount;
         private boolean amountSet;
         private boolean amountValid = true;
@@ -1053,7 +1076,7 @@ public class NewTransactionModel extends ViewModel {
         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;
@@ -1075,7 +1098,7 @@ public class NewTransactionModel extends ViewModel {
             super();
             this.accountName = accountName;
         }
-        public TransactionAccount(String accountName, String currency) {
+        public TransactionAccount(String accountName, @NotNull String currency) {
             super();
             this.accountName = accountName;
             this.currency = currency;
@@ -1121,11 +1144,12 @@ public class NewTransactionModel extends ViewModel {
         public void setComment(String comment) {
             this.comment = comment;
         }
+        @NotNull
         public String getCurrency() {
             return currency;
         }
-        public void setCurrency(String currency) {
-            this.currency = currency;
+        public void setCurrency(@org.jetbrains.annotations.Nullable String currency) {
+            this.currency = Misc.nullIsEmpty(currency);
         }
         public boolean isAmountValid() {
             return amountValid;
@@ -1178,16 +1202,16 @@ public class NewTransactionModel extends ViewModel {
             if (other == null)
                 return false;
 
-            boolean equal = TextUtils.equals(accountName, other.accountName);
-            equal = equal && TextUtils.equals(comment, other.comment) &&
+            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 &&
-                                                    TextUtils.equals(amountHint, other.amountHint)
+                                                    Misc.equalStrings(amountHint, other.amountHint)
                                                   : !other.amountHintIsSet);
-            equal = equal && TextUtils.equals(currency, other.currency) && isLast == other.isLast;
+            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(),
@@ -1224,9 +1248,9 @@ public class NewTransactionModel extends ViewModel {
     }
 
     private static class ItemsForCurrency {
-        private final HashMap<String, List<Item>> hashMap = new HashMap<>();
+        private final HashMap<@NotNull String, List<Item>> hashMap = new HashMap<>();
         @NonNull
-        List<NewTransactionModel.Item> getList(@Nullable String currencyName) {
+        List<NewTransactionModel.Item> getList(@NotNull String currencyName) {
             List<NewTransactionModel.Item> list = hashMap.get(currencyName);
             if (list == null) {
                 list = new ArrayList<>();
@@ -1234,11 +1258,11 @@ public class NewTransactionModel extends ViewModel {
             }
             return list;
         }
-        void add(@Nullable String currencyName, @NonNull NewTransactionModel.Item item) {
-            getList(currencyName).add(item);
+        void add(@NotNull String currencyName, @NonNull NewTransactionModel.Item item) {
+            getList(Objects.requireNonNull(currencyName)).add(item);
         }
-        int size(@Nullable String currencyName) {
-            return this.getList(currencyName)
+        int size(@NotNull String currencyName) {
+            return this.getList(Objects.requireNonNull(currencyName))
                        .size();
         }
         Set<String> currencies() {