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=d8f387b72696983f1fe9f87d0ae12e0a5b4d1be8;hp=5ad014f2f71f29d105e9d1993a673d34eee298b5;hb=dd7e3b83b17546504605f93b3a462d1a1be3b382;hpb=aee86347cbc843e454392751c181417945a271f5 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 5ad014f2..d8f387b7 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Damyan Ivanov. + * Copyright © 2022 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -18,8 +18,7 @@ package net.ktnx.mobileledger.ui.new_transaction; import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.Looper; +import android.os.Build; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -31,15 +30,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,14 +74,15 @@ public class NewTransactionModel extends ViewModel { private final MutableLiveData simulateSave = new InertMutableLiveData<>(false); private final AtomicInteger busyCounter = new AtomicInteger(0); private final MutableLiveData busyFlag = new InertMutableLiveData<>(false); - private final Observer profileObserver = profile -> { - showCurrency.postValue(profile.getShowCommodityByDefault()); - showComments.postValue(profile.getShowCommentsByDefault()); + private final Observer profileObserver = profile -> { + if (profile != null) { + showCurrency.postValue(profile.getShowCommodityByDefault()); + showComments.postValue(profile.getShowCommentsByDefault()); + } }; private final MutableLiveData focusInfo = new MutableLiveData<>(); private boolean observingDataProfile; public NewTransactionModel() { - reset(); } public LiveData getShowCurrency() { return showCurrency; @@ -92,8 +94,27 @@ public class NewTransactionModel extends ViewModel { checkTransactionSubmittable(newList); setItemsWithoutSubmittableChecks(newList); } + private void replaceItems(@NonNull List 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 list) { + if (list == null) { + return; + } + + int id = 0; + for (Item item : list) + item.id = id++; + } 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) @@ -112,14 +133,13 @@ public class NewTransactionModel extends ViewModel { list.set(cnt - 1, replacement); } + if (BuildConfig.DEBUG) + dumpItemList("Before setValue()", list); items.setValue(list); } 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) { @@ -128,7 +148,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(); @@ -137,14 +157,14 @@ public class NewTransactionModel extends ViewModel { for (Item item : oldList) { if (i++ == position) continue; - copy.add(item); + copy.add(Item.from(item)); } } return copy; } private List shallowCopyList() { - return new ArrayList<>(items.getValue()); + return new ArrayList<>(Objects.requireNonNull(items.getValue())); } LiveData getShowComments() { return showComments; @@ -172,10 +192,14 @@ public class NewTransactionModel extends ViewModel { 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("")); + 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 +296,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 +333,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 +352,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,10 +364,14 @@ 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) { + void noteFocusChanged(int position, @Nullable FocusedElement element) { FocusInfo present = focusInfo.getValue(); if (present == null || present.position != position || present.element != element) focusInfo.setValue(new FocusInfo(position, element)); @@ -346,6 +383,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) { @@ -368,10 +410,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 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,18 +441,17 @@ public class NewTransactionModel extends ViewModel { List list = Objects.requireNonNull(items.getValue()); TransactionHead head = list.get(0) .toTransactionHead(); - SimpleDate date = head.getDate(); LedgerTransaction tr = head.asLedgerTransaction(); tr.setComment(head.getComment()); - LedgerTransactionAccount emptyAmountAccount = null; - float emptyAmountAccountBalance = 0; + HashMap> emptyAmountAccounts = new HashMap<>(); + HashMap emptyAmountAccountBalance = new HashMap<>(); for (int i = 1; i < list.size(); i++) { TransactionAccount item = list.get(i) .toTransactionAccount(); + String currency = item.getCurrency(); LedgerTransactionAccount acc = new LedgerTransactionAccount(item.getAccountName() - .trim(), - item.getCurrency()); + .trim(), currency); if (acc.getAccountName() .isEmpty()) continue; @@ -419,46 +460,83 @@ public class NewTransactionModel extends ViewModel { if (item.isAmountSet()) { acc.setAmount(item.getAmount()); - emptyAmountAccountBalance += item.getAmount(); + Float emptyCurrBalance = emptyAmountAccountBalance.get(currency); + if (emptyCurrBalance == null) { + emptyAmountAccountBalance.put(currency, item.getAmount()); + } + else { + emptyAmountAccountBalance.put(currency, emptyCurrBalance + item.getAmount()); + } } else { - emptyAmountAccount = acc; + List emptyCurrAccounts = + emptyAmountAccounts.get(currency); + if (emptyCurrAccounts == null) + emptyAmountAccounts.put(currency, emptyCurrAccounts = new ArrayList<>()); + emptyCurrAccounts.add(acc); } tr.addAccount(acc); } - if (emptyAmountAccount != null) - emptyAmountAccount.setAmount(-emptyAmountAccountBalance); + if (emptyAmountAccounts.size() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + emptyAmountAccounts.forEach((currency, accounts) -> { + if (accounts.size() != 1) + throw new RuntimeException(String.format(Locale.US, + "Should not happen: approved transaction has %d accounts for " + + "currency %s", accounts.size(), currency)); + accounts.get(0) + .setAmount(-Objects.requireNonNull( + emptyAmountAccountBalance.get(currency))); + }); + } + else { + for (String currency : emptyAmountAccounts.keySet()) { + List accounts = + Objects.requireNonNull(emptyAmountAccounts.get(currency)); + + if (accounts.size() != 1) + throw new RuntimeException(String.format(Locale.US, + "Should not happen: approved transaction has %d accounts for " + + "currency %s", accounts.size(), currency)); + accounts.get(0) + .setAmount(-Objects.requireNonNull( + emptyAmountAccountBalance.get(currency))); + } + } + } return tr; } - void loadTransactionIntoModel(String profileUUID, int transactionId) { + void loadTransactionIntoModel(@NonNull TransactionWithAccounts tr) { List 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 accounts = tr.getAccounts(); + List accounts = new ArrayList<>(); + for (net.ktnx.mobileledger.db.TransactionAccount acc : tr.accounts) { + accounts.add(new LedgerTransactionAccount(acc)); + } TransactionAccount firstNegative = null; TransactionAccount firstPositive = null; int singleNegativeIndex = -1; int singlePositiveIndex = -1; int negativeCount = 0; + boolean hasCurrency = false; for (int i = 0; i < accounts.size(); i++) { LedgerTransactionAccount acc = accounts.get(i); - TransactionAccount item = - new TransactionAccount(acc.getAccountName(), acc.getCurrency()); + TransactionAccount item = new TransactionAccount(acc.getAccountName(), + Misc.nullIsEmpty(acc.getCurrency())); newList.add(item); item.setAccountName(acc.getAccountName()); @@ -484,6 +562,10 @@ public class NewTransactionModel extends ViewModel { } else item.resetAmount(); + + if (item.getCurrency() + .length() > 0) + hasCurrency = true; } if (BuildConfig.DEBUG) dumpItemList("Loaded previous transaction", newList); @@ -497,9 +579,13 @@ public class NewTransactionModel extends ViewModel { moveItemLast(newList, singlePositiveIndex); } - setItems(newList); - - noteFocusChanged(1, FocusedElement.Amount); + final boolean foundTransactionHasCurrency = hasCurrency; + Misc.onMainThread(() -> { + setItems(newList); + noteFocusChanged(1, FocusedElement.Amount); + if (foundTransactionHasCurrency) + showCurrency.setValue(true); + }); } /** * A transaction is submittable if: @@ -521,14 +607,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(); @@ -554,6 +640,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 +671,18 @@ 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() && item.isAmountValid()) { 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)); + submittable = false; + hasInvalidAmount = true; + } + itemsWithEmptyAmountForCurrency.add(currName, item); if (!accName.isEmpty()) @@ -627,21 +717,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 +739,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; @@ -685,20 +769,14 @@ public class NewTransactionModel extends ViewModel { continue; if (item == receiver) { - final String hint = String.format("%1.2f", -currencyBalance); + final String hint = Data.formatNumber(-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); + String.format("Setting amount hint of {%s} to %s [%s]", acc, + hint, balCurrency)); + acc.setAmountHint(hint); listChanged = true; } } @@ -709,13 +787,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 +797,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(); @@ -743,26 +816,23 @@ public class NewTransactionModel extends ViewModel { // if (Misc.isZero(balance.get(itemCurrencyName))) { // item.setCurrency(Currency.loadByName(balCurrency)); // item.setAmountHint( -// String.format("%1.2f", -balance.get(balCurrency))); +// Data.formatNumber(-balance.get(balCurrency))); // foundIt = true; // break; // } // } // // 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(Data.formatNumber(-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 +840,6 @@ public class NewTransactionModel extends ViewModel { for (String currName : emptyRowsForCurrency.currencies()) { List 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 +851,6 @@ public class NewTransactionModel extends ViewModel { List 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 +861,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); @@ -883,7 +940,7 @@ public class NewTransactionModel extends ViewModel { public static class FocusInfo { int position; FocusedElement element; - public FocusInfo(int position, FocusedElement element) { + public FocusInfo(int position, @Nullable FocusedElement element) { this.position = position; this.element = element; } @@ -893,9 +950,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 +967,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 +1018,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,10 +1067,10 @@ 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())); + b.append(String.format("@%s", date)); if (!TextUtils.isEmpty(comment)) b.append(String.format(" /%s/", comment)); @@ -1028,15 +1094,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,21 +1111,25 @@ public class NewTransactionModel extends ViewModel { private String accountName; private String amountHint; private String comment; - private String currency; + @NotNull + private String currency = ""; private float amount; private boolean amountSet; private boolean amountValid = true; + @NotNull + private String amountText = ""; 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; amountHint = origin.amountHint; amountHintIsSet = origin.amountHintIsSet; + amountText = origin.amountText; comment = origin.comment; currency = origin.currency; amountValid = origin.amountValid; @@ -1066,20 +1137,55 @@ public class NewTransactionModel extends ViewModel { isLast = origin.isLast; accountNameCursorPosition = origin.accountNameCursorPosition; } - public TransactionAccount(LedgerTransactionAccount account) { - super(); - currency = account.getCurrency(); - amount = account.getAmount(); - } public TransactionAccount(String accountName) { super(); this.accountName = accountName; } - public TransactionAccount(String accountName, String currency) { + public TransactionAccount(String accountName, @NotNull String currency) { super(); this.accountName = accountName; this.currency = currency; } + public @NotNull String getAmountText() { + return amountText; + } + public void setAmountText(@NotNull String amountText) { + this.amountText = amountText; + } + public boolean setAndCheckAmountText(@NotNull String amountText) { + String amtText = amountText.trim(); + this.amountText = amtText; + + boolean significantChange = false; + + if (amtText.isEmpty()) { + if (amountSet) { + significantChange = true; + } + resetAmount(); + } + else { + try { + amtText = amtText.replace(Data.getDecimalSeparator(), Data.decimalDot); + final float parsedAmount = Float.parseFloat(amtText); + if (!amountSet || !amountValid || !Misc.equalFloats(parsedAmount, amount)) + significantChange = true; + amount = parsedAmount; + amountSet = true; + amountValid = true; + } + catch (NumberFormatException e) { + Logger.debug("new-trans", String.format( + "assuming amount is not set due to number format exception. " + + "input was '%s'", amtText)); + if (amountValid) // it was valid and now it's not + significantChange = true; + amountValid = false; + } + } + + return significantChange; + } public boolean isLast() { return isLast; } @@ -1100,9 +1206,13 @@ public class NewTransactionModel extends ViewModel { public void setAmount(float amount) { this.amount = amount; amountSet = true; + amountValid = true; + amountText = Data.formatNumber(amount); } public void resetAmount() { amountSet = false; + amountValid = true; + amountText = ""; } @Override public ItemType getType() { @@ -1121,11 +1231,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; @@ -1151,6 +1262,7 @@ public class NewTransactionModel extends ViewModel { } @SuppressLint("DefaultLocale") @Override + @NotNull public String toString() { StringBuilder b = new StringBuilder(); b.append(String.format("id:%d/%s", id, Integer.toHexString(hashCode()))); @@ -1158,9 +1270,13 @@ public class NewTransactionModel extends ViewModel { b.append(String.format(" acc'%s'", accountName)); if (amountSet) - b.append(String.format(" %4.2f", amount)); + b.append(amountText) + .append(" [") + .append(amountValid ? "valid" : "invalid") + .append("] ") + .append(String.format(Locale.ROOT, " {raw %4.2f}", amount)); else if (amountHintIsSet) - b.append(String.format(" (%s)", amountHint)); + b.append(String.format(" (hint %s)", amountHint)); if (!TextUtils.isEmpty(currency)) b.append(" ") @@ -1178,20 +1294,21 @@ public class NewTransactionModel extends ViewModel { if (other == null) return false; - boolean equal = TextUtils.equals(accountName, other.accountName); - equal = equal && TextUtils.equals(comment, other.comment) && - (amountSet ? other.amountSet && amount == other.amount : !other.amountSet); + boolean equal = Misc.equalStrings(accountName, other.accountName); + equal = equal && Misc.equalStrings(comment, other.comment) && + (amountSet ? other.amountSet && amountValid == other.amountValid && + Misc.equalStrings(amountText, other.amountText) + : !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(), - equal)); + String.format("Comparing {%s} and {%s}: %s", this, other, equal)); return equal; } public int getAccountNameCursorPosition() { @@ -1224,9 +1341,9 @@ public class NewTransactionModel extends ViewModel { } private static class ItemsForCurrency { - private final HashMap> hashMap = new HashMap<>(); + private final HashMap<@NotNull String, List> hashMap = new HashMap<>(); @NonNull - List getList(@Nullable String currencyName) { + List getList(@NotNull String currencyName) { List list = hashMap.get(currencyName); if (list == null) { list = new ArrayList<>(); @@ -1234,11 +1351,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 currencies() {