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;
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<Boolean> showCurrency = new MutableLiveData<>(false);
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);
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);
return this.isSubmittable;
}
void reset() {
+ Logger.debug("new-trans", "Resetting model");
List<Item> list = new ArrayList<>();
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<Item> list = items.getValue();
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())
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) {
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);
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()
else
item.resetAmount();
}
+ if (BuildConfig.DEBUG)
+ dumpItemList("Loaded previous transaction", newList);
if (singleNegativeIndex != -1) {
firstNegative.resetAmount();
moveItemLast(newList, singlePositiveIndex);
}
- noteFocusChanged(1, FocusedElement.Description);
-
- newList.add(new BottomFiller());
-
setItems(newList);
+
+ noteFocusChanged(1, FocusedElement.Amount);
}
/**
* A transaction is submittable if:
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();
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())) {
+ // 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);
- if (!liveListCopied) {
- list = copyList(list);
- liveListCopied = true;
- }
list.set(i, newAcc);
listChanged = true;
}
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;
}
}
// drop extra empty rows, not needed
for (String currName : emptyRowsForCurrency.currencies()) {
List<Item> emptyItems = emptyRowsForCurrency.getList(currName);
- while ((list.size() > 4) && (emptyItems.size() > 1)) {
+ while ((list.size() > MIN_ITEMS) && (emptyItems.size() > 1)) {
if (workingWithLiveList && !liveListCopied) {
list = copyList(list);
liveListCopied = true;
}
- Item item = emptyItems.remove(1);
- list.remove(item);
+ // 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<Item> currItems = itemsForCurrency.getList(currName);
if (currItems.size() == 1) {
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;
}
}
// 6) at least two rows need to be present in the ledger
// (the list also contains header and trailer)
- while (list.size() < 4) {
+ while (list.size() < MIN_ITEMS) {
if (workingWithLiveList && !liveListCopied) {
list = copyList(list);
liveListCopied = true;
}
- list.add(list.size() - 1, new TransactionAccount(""));
+ list.add(new TransactionAccount(""));
listChanged = true;
}
-
Logger.debug("submittable", submittable ? "YES" : "NO");
isSubmittable.setValue(submittable);
setItemsWithoutSubmittableChecks(list);
}
}
+ private void removeItemById(@NotNull List<Item> 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<Item> 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()));
setItems(newList);
}
- public LiveData<Integer> getAccountCount() {
- return accountCount;
- }
public boolean accountListIsEmpty() {
List<Item> items = Objects.requireNonNull(this.items.getValue());
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);
}
public int getId() {
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);
}
}
}
- 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);
- }
- }
-
public static class TransactionAccount extends Item {
private String accountName;
private String amountHint;
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;
accountName = origin.accountName;
currency = origin.currency;
amountValid = origin.amountValid;
focusedElement = origin.focusedElement;
+ isLast = origin.isLast;
+ accountNameCursorPosition = origin.accountNameCursorPosition;
}
public TransactionAccount(LedgerTransactionAccount account) {
super();
this.accountName = accountName;
this.currency = currency;
}
+ public boolean isLast() {
+ return isLast;
+ }
public boolean isAmountSet() {
return amountSet;
}
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 = TextUtils.equals(accountName, other.accountName);
+ equal = equal && TextUtils.equals(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)
+ : !other.amountHintIsSet);
+ equal = equal && TextUtils.equals(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 {