qty.setDecimalMantissa(Math.round(acc.getAmount() * 100));
amt.setAquantity(qty);
ParsedStyle style = new ParsedStyle();
- style.setAscommodityside('L');
- style.setAscommodityspaced(false);
+ style.setAscommodityside(getCommoditySide());
+ style.setAscommodityspaced(getCommoditySpaced());
style.setAsprecision(2);
style.setAsdecimalpoint('.');
amt.setAstyle(style);
+ if (acc.getCurrency() != null)
+ amt.setAcommodity(acc.getCurrency());
amounts.add(amt);
result.setPamount(amounts);
return result;
qty.setDecimalMantissa(Math.round(acc.getAmount() * 100));
amt.setAquantity(qty);
ParsedStyle style = new ParsedStyle();
- style.setAscommodityside('L');
- style.setAscommodityspaced(false);
+ style.setAscommodityside(getCommoditySide());
+ style.setAscommodityspaced(getCommoditySpaced());
style.setAsprecision(2);
style.setAsdecimalpoint('.');
amt.setAstyle(style);
+ if (acc.getCurrency() != null)
+ amt.setAcommodity(acc.getCurrency());
amounts.add(amt);
result.setPamount(amounts);
return result;
--- /dev/null
+/*
+ * Copyright © 2019 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.model;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+
+import net.ktnx.mobileledger.App;
+
+public class Currency {
+ public static final DiffUtil.ItemCallback<Currency> DIFF_CALLBACK =
+ new DiffUtil.ItemCallback<Currency>() {
+ @Override
+ public boolean areItemsTheSame(@NonNull Currency oldItem,
+ @NonNull Currency newItem) {
+ return oldItem.id == newItem.id;
+ }
+ @Override
+ public boolean areContentsTheSame(@NonNull Currency oldItem,
+ @NonNull Currency newItem) {
+ return oldItem.name.equals(newItem.name) &&
+ oldItem.position.equals(newItem.position) &&
+ (oldItem.hasGap == newItem.hasGap);
+ }
+ };
+ private int id;
+ private String name;
+ private Position position;
+ private boolean hasGap;
+ public Currency(int id, String name) {
+ this.id = id;
+ this.name = name;
+ position = Position.after;
+ hasGap = true;
+ }
+ public Currency(int id, String name, Position position, boolean hasGap) {
+ this.id = id;
+ this.name = name;
+ this.position = position;
+ this.hasGap = hasGap;
+ }
+ public Currency(MobileLedgerProfile profile, String name, Position position, boolean hasGap) {
+ SQLiteDatabase db = App.getDatabase();
+ int attempts = 0;
+ while (true) {
+ if (++attempts > 10)
+ throw new RuntimeException("Giving up getting next ID after 10 attempts");
+
+ try (Cursor c = db.rawQuery("select max(rowid) from currencies", null)) {
+ c.moveToNext();
+ int nextId = c.getInt(0) + 1;
+ db.execSQL("insert into currencies(id, name, position, has_gap) values(?, ?, ?, ?)",
+ new Object[]{nextId, name, position.toString(), hasGap});
+
+ this.id = nextId;
+ break;
+ }
+ }
+
+ this.name = name;
+ this.position = position;
+ this.hasGap = hasGap;
+ }
+ public int getId() {
+ return id;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public Position getPosition() {
+ return position;
+ }
+ public void setPosition(Position position) {
+ this.position = position;
+ }
+ public boolean hasGap() {
+ return hasGap;
+ }
+ public void setHasGap(boolean hasGap) {
+ this.hasGap = hasGap;
+ }
+ public enum Position {
+ before(-1), after(1), unknown(0), none(-2);
+ private int value;
+ Position(int value) {
+ this.value = value;
+ }
+ static Position valueOf(int value) {
+ switch (value) {
+ case -1:
+ return before;
+ case +1:
+ return after;
+ case 0:
+ return unknown;
+ case -2:
+ return none;
+ default:
+ throw new IllegalStateException(String.format("Unexpected value (%d)", value));
+ }
+ }
+ }
+}
return amount;
}
-
public void setAmount(float account_amount) {
this.amount = account_amount;
this.amountSet = true;
}
-
public void resetAmount() {
this.amountSet = false;
}
-
public boolean isAmountSet() {
return amountSet;
}
public String getCurrency() {
return currency;
}
+ public void setCurrency(String currency) {
+ this.currency = currency;
+ }
@NonNull
public String toString() {
if (!amountSet)
db.endTransaction();
}
}
+ public List<Currency> getCurrencies() {
+ SQLiteDatabase db = App.getDatabase();
+
+ ArrayList<Currency> result = new ArrayList<>();
+
+ try (Cursor c = db.rawQuery("SELECT c.id, c.name, c.position, c.has_gap FROM currencies c",
+ new String[]{}))
+ {
+ while (c.moveToNext()) {
+ Currency currency = new Currency(c.getInt(0), c.getString(1),
+ Currency.Position.valueOf(c.getInt(2)), c.getInt(3) == 1);
+ result.add(currency);
+ }
+ }
+
+ return result;
+ }
public enum FutureDates {
None(0), OneWeek(7), TwoWeeks(14), OneMonth(30), TwoMonths(60), ThreeMonths(90),
SixMonths(180), OneYear(365), All(-1);
--- /dev/null
+/*
+ * Copyright © 2019 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatDialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.ktnx.mobileledger.App;
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.Currency;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.model.MobileLedgerProfile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A fragment representing a list of Items.
+ * <p/>
+ * Activities containing this fragment MUST implement the {@link OnCurrencySelectedListener}
+ * interface.
+ */
+public class CurrencySelectorFragment extends AppCompatDialogFragment
+ implements OnCurrencySelectedListener, OnCurrencyLongClickListener {
+
+ public static final int DEFAULT_COLUMN_COUNT = 2;
+ private static final String ARG_COLUMN_COUNT = "column-count";
+ private int mColumnCount = DEFAULT_COLUMN_COUNT;
+ private OnCurrencySelectedListener mListener;
+ private CurrencySelectorModel model;
+
+ /**
+ * Mandatory empty constructor for the fragment manager to instantiate the
+ * fragment (e.g. upon screen orientation changes).
+ */
+ public CurrencySelectorFragment() {
+ }
+ @SuppressWarnings("unused")
+ public static CurrencySelectorFragment newInstance() {
+ return newInstance(DEFAULT_COLUMN_COUNT);
+ }
+ public static CurrencySelectorFragment newInstance(int columnCount) {
+ CurrencySelectorFragment fragment = new CurrencySelectorFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_COLUMN_COUNT, columnCount);
+ fragment.setArguments(args);
+ return fragment;
+ }
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getArguments() != null) {
+ mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT, DEFAULT_COLUMN_COUNT);
+ }
+ }
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ Context context = Objects.requireNonNull(getContext());
+ Dialog csd = new Dialog(context);
+ csd.setContentView(R.layout.fragment_currency_selector_list);
+ csd.setTitle(R.string.choose_currency_label);
+
+ RecyclerView recyclerView = csd.findViewById(R.id.list);
+
+ if (mColumnCount <= 1) {
+ recyclerView.setLayoutManager(new LinearLayoutManager(context));
+ }
+ else {
+ recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
+ }
+ model = new ViewModelProvider(this).get(CurrencySelectorModel.class);
+ MobileLedgerProfile profile = Objects.requireNonNull(Data.profile.getValue());
+
+ model.currencies.setValue(new CopyOnWriteArrayList<>(profile.getCurrencies()));
+ CurrencySelectorRecyclerViewAdapter adapter = new CurrencySelectorRecyclerViewAdapter();
+ model.currencies.observe(this, list -> adapter.submitList(list));
+
+ recyclerView.setAdapter(adapter);
+ adapter.setCurrencySelectedListener(this);
+ adapter.setCurrencyLongClickListener(this);
+
+ final TextView tvNewCurrName = csd.findViewById(R.id.new_currency_name);
+ final TextView tvNoCurrBtn = csd.findViewById(R.id.btn_no_currency);
+ final TextView tvAddCurrOkBtn = csd.findViewById(R.id.btn_add_currency);
+ final TextView tvAddCurrBtn = csd.findViewById(R.id.btn_add_new);
+
+ tvNewCurrName.setVisibility(View.GONE);
+ tvAddCurrOkBtn.setVisibility(View.GONE);
+ tvNoCurrBtn.setVisibility(View.VISIBLE);
+ tvAddCurrBtn.setVisibility(View.VISIBLE);
+
+ tvAddCurrBtn.setOnClickListener(v -> {
+ tvNewCurrName.setVisibility(View.VISIBLE);
+ tvAddCurrOkBtn.setVisibility(View.VISIBLE);
+
+ tvNoCurrBtn.setVisibility(View.GONE);
+ tvAddCurrBtn.setVisibility(View.GONE);
+
+ tvNewCurrName.setText(null);
+ tvNewCurrName.requestFocus();
+ net.ktnx.mobileledger.utils.Misc.showSoftKeyboard(this);
+ });
+
+ tvAddCurrOkBtn.setOnClickListener(v -> {
+
+
+ String currName = String.valueOf(tvNewCurrName.getText());
+ if (!currName.isEmpty()) {
+ List<Currency> list = new ArrayList<>( model.currencies.getValue());
+ // FIXME hardcoded position and gap setting
+ list.add(new Currency(profile, String.valueOf(tvNewCurrName.getText()),
+ Currency.Position.after, false));
+ model.currencies.setValue(list);
+ }
+
+ tvNewCurrName.setVisibility(View.GONE);
+ tvAddCurrOkBtn.setVisibility(View.GONE);
+
+ tvNoCurrBtn.setVisibility(View.VISIBLE);
+ tvAddCurrBtn.setVisibility(View.VISIBLE);
+ });
+
+ tvNoCurrBtn.setOnClickListener(v -> {
+ adapter.notifyCurrencySelected(null);
+ dismiss();
+ });
+
+ RadioButton rbPositionLeft = csd.findViewById(R.id.currency_position_left);
+ RadioButton rbPositionRight = csd.findViewById(R.id.currency_position_right);
+
+ if (Data.currencySymbolPosition.getValue() == Currency.Position.before)
+ rbPositionLeft.toggle();
+ else
+ rbPositionRight.toggle();
+
+ RadioGroup rgPosition = csd.findViewById(R.id.position_radio_group);
+ rgPosition.setOnCheckedChangeListener((group, checkedId) -> {
+ if (checkedId == R.id.currency_position_left)
+ Data.currencySymbolPosition.setValue(Currency.Position.before);
+ else
+ Data.currencySymbolPosition.setValue(Currency.Position.after);
+ });
+
+ Switch gap = csd.findViewById(R.id.currency_gap);
+
+ gap.setChecked(Data.currencyGap.getValue());
+
+ gap.setOnCheckedChangeListener((v, checked) -> {
+ Data.currencyGap.setValue(checked);
+ });
+
+ return csd;
+ }
+ public void setOnCurrencySelectedListener(OnCurrencySelectedListener listener) {
+ mListener = listener;
+ }
+ public void resetOnCurrencySelectedListener() {
+ mListener = null;
+ }
+ @Override
+ public void onCurrencySelected(Currency item) {
+ if (mListener != null)
+ mListener.onCurrencySelected(item);
+
+ dismiss();
+ }
+
+ @Override
+ public void onCurrencyLongClick(Currency item) {
+ ArrayList<Currency> list = new ArrayList<>(model.currencies.getValue());
+ App.getDatabase().execSQL("delete from currencies where id=?", new Object[]{item.getId()});
+ list.remove(item);
+ model.currencies.setValue(list);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2020 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import net.ktnx.mobileledger.model.Currency;
+
+import java.util.List;
+
+public class CurrencySelectorModel extends ViewModel {
+ public final MutableLiveData<List<Currency>> currencies;
+ public CurrencySelectorModel() {
+ this.currencies = new MutableLiveData<>();
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.ListAdapter;
+import androidx.recyclerview.widget.RecyclerView;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.model.Currency;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * {@link RecyclerView.Adapter} that can display a {@link Currency} and makes a call to the
+ * specified {@link OnCurrencySelectedListener}.
+ */
+public class CurrencySelectorRecyclerViewAdapter
+ extends ListAdapter<Currency, CurrencySelectorRecyclerViewAdapter.ViewHolder> {
+
+ private OnCurrencySelectedListener currencySelectedListener;
+ private OnCurrencyLongClickListener currencyLongClickListener;
+ public CurrencySelectorRecyclerViewAdapter() {
+ super(Currency.DIFF_CALLBACK);
+ }
+ @NotNull
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.fragment_currency_selector, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ holder.bindTo(getItem(position));
+ }
+ public void setCurrencySelectedListener(OnCurrencySelectedListener listener) {
+ this.currencySelectedListener = listener;
+ }
+ public void resetCurrencySelectedListener() {
+ currencySelectedListener = null;
+ }
+ public void notifyCurrencySelected(Currency currency) {
+ if (null != currencySelectedListener)
+ currencySelectedListener.onCurrencySelected(currency);
+ }
+ public void setCurrencyLongClickListener(OnCurrencyLongClickListener listener) {
+ this.currencyLongClickListener = listener;
+ }
+ public void resetCurrencyLockClickListener() { currencyLongClickListener = null; }
+ private void notifyCurrencyLongClicked(Currency mItem) {
+ if (null != currencyLongClickListener)
+ currencyLongClickListener.onCurrencyLongClick(mItem);
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ private final TextView mNameView;
+ private Currency mItem;
+
+ ViewHolder(View view) {
+ super(view);
+ mNameView = view.findViewById(R.id.content);
+
+ view.setOnClickListener(v -> notifyCurrencySelected(mItem));
+ view.setOnLongClickListener(v -> {
+ notifyCurrencyLongClicked(mItem);
+ return false;
+ });
+ }
+
+ @NotNull
+ @Override
+ public String toString() {
+ return super.toString() + " '" + mNameView.getText() + "'";
+ }
+ void bindTo(Currency item) {
+ mItem = item;
+ mNameView.setText(item.getName());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2020 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import net.ktnx.mobileledger.model.Currency;
+
+/**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ * <p/>
+ * See the Android Training lesson <a href=
+ * "http://developer.android.com/training/basics/fragments/communicating.html"
+ * >Communicating with Other Fragments</a> for more information.
+ */
+public interface OnCurrencyLongClickListener {
+ void onCurrencyLongClick(Currency item);
+}
--- /dev/null
+/*
+ * Copyright © 2020 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import net.ktnx.mobileledger.model.Currency;
+
+/**
+ * This interface must be implemented by activities that contain this
+ * fragment to allow an interaction in this fragment to be communicated
+ * to the activity and potentially other fragments contained in that
+ * activity.
+ * <p/>
+ * See the Android Training lesson <a href=
+ * "http://developer.android.com/training/basics/fragments/communicating.html"
+ * >Communicating with Other Fragments</a> for more information.
+ */
+public interface OnCurrencySelectedListener {
+ void onCurrencySelected(Currency item);
+}
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.DescriptionSelectedCallback;
+import net.ktnx.mobileledger.model.Currency;
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.ui.CurrencySelectorFragment;
import net.ktnx.mobileledger.ui.DatePickerFragment;
+import net.ktnx.mobileledger.ui.OnCurrencySelectedListener;
import net.ktnx.mobileledger.ui.TextViewClearHelper;
import net.ktnx.mobileledger.utils.Colors;
+import net.ktnx.mobileledger.utils.DimensionUtils;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.MLDB;
import net.ktnx.mobileledger.utils.Misc;
import static net.ktnx.mobileledger.ui.activity.NewTransactionModel.ItemType;
class NewTransactionItemHolder extends RecyclerView.ViewHolder
- implements DatePickerFragment.DatePickedListener, DescriptionSelectedCallback {
+ implements DatePickerFragment.DatePickedListener, DescriptionSelectedCallback,
+ OnCurrencySelectedListener {
private final String decimalSeparator;
private final String decimalDot;
+ private final TextView tvCurrency;
private NewTransactionModel.Item item;
private TextView tvDate;
private AutoCompleteTextView tvDescription;
private Observer<Boolean> editableObserver;
private Observer<Boolean> commentVisibleObserver;
private Observer<String> commentObserver;
+ private Observer<Currency.Position> currencyPositionObserver;
+ private Observer<Boolean> currencyGapObserver;
private Observer<Locale> localeObserver;
+ private Observer<Currency> currencyObserver;
private boolean inUpdate = false;
private boolean syncingData = false;
private View commentButton;
+ //TODO multiple amounts with different currencies per posting
NewTransactionItemHolder(@NonNull View itemView, NewTransactionItemsAdapter adapter) {
super(itemView);
tvAccount = itemView.findViewById(R.id.account_row_acc_name);
new TextViewClearHelper().attachToTextView((EditText) tvComment);
commentButton = itemView.findViewById(R.id.comment_button);
tvAmount = itemView.findViewById(R.id.account_row_acc_amounts);
+ tvCurrency = itemView.findViewById(R.id.currency);
tvDate = itemView.findViewById(R.id.new_transaction_date);
tvDescription = itemView.findViewById(R.id.new_transaction_description);
lHead = itemView.findViewById(R.id.ntr_data);
final TextWatcher amountWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ Logger.debug("num",
+ String.format(Locale.US, "beforeTextChanged: start=%d, count=%d, after=%d",
+ start, count, after));
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
-
+ Logger.debug("num",
+ String.format(Locale.US, "onTextChanged: start=%d, before=%d, count=%d",
+ start, before, count));
}
@Override
public void afterTextChanged(Editable s) {
tvComment.addTextChangedListener(tw);
tvAmount.addTextChangedListener(amountWatcher);
+ tvCurrency.setOnClickListener(v -> {
+ CurrencySelectorFragment cpf = new CurrencySelectorFragment();
+ cpf.setOnCurrencySelectedListener(this);
+ final AppCompatActivity activity = (AppCompatActivity) v.getContext();
+ cpf.show(activity.getSupportFragmentManager(), "currency-selector");
+ });
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
tvAmount.setKeyListener(DigitsKeyListener.getInstance(Locale.getDefault(), true, true));
else
tvAmount.setImeOptions(EditorInfo.IME_ACTION_NEXT);
};
+ currencyPositionObserver = position -> {
+ updateCurrencyPositionAndPadding(position, Data.currencyGap.getValue());
+ };
+
+ currencyGapObserver = hasGap -> {
+ updateCurrencyPositionAndPadding(Data.currencySymbolPosition.getValue(), hasGap);
+ };
+
localeObserver = locale -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
tvAmount.setKeyListener(DigitsKeyListener.getInstance(locale, true, true));
};
+
+ currencyObserver = this::setCurrency;
+ }
+ private void updateCurrencyPositionAndPadding(Currency.Position position, boolean hasGap) {
+ ConstraintLayout.LayoutParams amountLP =
+ (ConstraintLayout.LayoutParams) tvAmount.getLayoutParams();
+ ConstraintLayout.LayoutParams currencyLP =
+ (ConstraintLayout.LayoutParams) tvCurrency.getLayoutParams();
+
+ if (position == Currency.Position.before) {
+ currencyLP.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
+ currencyLP.endToEnd = ConstraintLayout.LayoutParams.UNSET;
+
+ amountLP.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
+ amountLP.endToStart = ConstraintLayout.LayoutParams.UNSET;
+ amountLP.startToStart = ConstraintLayout.LayoutParams.UNSET;
+ amountLP.startToEnd = tvCurrency.getId();
+
+ tvCurrency.setGravity(Gravity.END);
+ }
+ else {
+ currencyLP.startToStart = ConstraintLayout.LayoutParams.UNSET;
+ currencyLP.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
+
+ amountLP.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;
+ amountLP.startToEnd = ConstraintLayout.LayoutParams.UNSET;
+ amountLP.endToEnd = ConstraintLayout.LayoutParams.UNSET;
+ amountLP.endToStart = tvCurrency.getId();
+
+ tvCurrency.setGravity(Gravity.START);
+ }
+
+ amountLP.resolveLayoutDirection(tvAmount.getLayoutDirection());
+ currencyLP.resolveLayoutDirection(tvCurrency.getLayoutDirection());
+
+ tvAmount.setLayoutParams(amountLP);
+ tvCurrency.setLayoutParams(currencyLP);
+
+ // distance between the amount and the currency symbol
+ int gapSize = DimensionUtils.sp2px(tvCurrency.getContext(), 5);
+
+ if (position == Currency.Position.before) {
+ tvCurrency.setPaddingRelative(0, 0, hasGap ? gapSize : 0, 0);
+ }
+ else {
+ tvCurrency.setPaddingRelative(hasGap ? gapSize : 0, 0, 0, 0);
+ }
+ }
+ private void setCurrencyString(String currency) {
+ if ((currency == null) || currency.isEmpty()) {
+ tvCurrency.setText(R.string.currency_symbol);
+ tvCurrency.setTextColor(0x7f000000 + (0x00ffffff & Colors.defaultTextColor));
+ }
+ else {
+ tvCurrency.setText(currency);
+ tvCurrency.setTextColor(Colors.defaultTextColor);
+ }
+ }
+ private void setCurrency(Currency currency) {
+ setCurrencyString((currency == null) ? null : currency.getName());
}
private void setEditable(Boolean editable) {
tvDate.setEnabled(editable);
if (amount.isEmpty()) {
account.resetAmount();
+ account.setCurrency(null);
}
else {
try {
"input was '%s'", amount));
account.resetAmount();
}
+ final String curr = String.valueOf(tvCurrency.getText());
+ if (curr.equals(tvCurrency.getContext()
+ .getResources()
+ .getString(R.string.currency_symbol)) ||
+ curr.isEmpty())
+ account.setCurrency(null);
+ else
+ account.setCurrency(curr);
}
break;
.stopObservingFocusedItem(focusedAccountObserver);
this.item.getModel()
.stopObservingAccountCount(accountCountObserver);
+ Data.currencySymbolPosition.removeObserver(currencyPositionObserver);
+ Data.currencyGap.removeObserver(currencyGapObserver);
Data.locale.removeObserver(localeObserver);
+ this.item.stopObservingCurrency(currencyObserver);
this.item = null;
}
// tvAmount.setHint(R.string.zero_amount);
}
tvAmount.setHint(item.getAmountHint());
+ setCurrencyString(acc.getCurrency());
lHead.setVisibility(View.GONE);
lAccount.setVisibility(View.VISIBLE);
lPadding.setVisibility(View.GONE);
.observeFocusedItem(activity, focusedAccountObserver);
item.getModel()
.observeAccountCount(activity, accountCountObserver);
+ Data.currencySymbolPosition.observe(activity, currencyPositionObserver);
+ Data.currencyGap.observe(activity, currencyGapObserver);
Data.locale.observe(activity, localeObserver);
+ item.observeCurrency(activity, currencyObserver);
}
}
finally {
}
@Override
+ public void onCurrencySelected(Currency item) {
+ this.item.setCurrency(item);
+ }
+ @Override
public void descriptionSelected(String description) {
tvAccount.setText(description);
tvAmount.requestFocus(View.FOCUS_FORWARD);
import androidx.lifecycle.ViewModel;
import net.ktnx.mobileledger.BuildConfig;
+import net.ktnx.mobileledger.model.Currency;
import net.ktnx.mobileledger.model.LedgerTransactionAccount;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.Misc;
private FocusedElement focusedElement = FocusedElement.Account;
private MutableLiveData<String> comment = new MutableLiveData<>(null);
private MutableLiveData<Boolean> commentVisible = new MutableLiveData<>(false);
+ private MutableLiveData<Currency> currency = new MutableLiveData<>(null);
public Item(NewTransactionModel model) {
this.model = model;
type = ItemType.bottomFiller;
public void stopObservingCommentVisible(Observer<Boolean> observer) {
commentVisible.removeObserver(observer);
}
- public void observeComment(NewTransactionActivity activity,
- Observer<String> observer) {
+ public void observeComment(NewTransactionActivity activity, Observer<String> observer) {
comment.observe(activity, observer);
}
public void stopObservingComment(Observer<String> observer) {
getAccount().setComment(comment);
this.comment.postValue(comment);
}
+ public Currency getCurrency() {
+ return this.currency.getValue();
+ }
+ public void setCurrency(Currency currency) {
+ getAccount().setCurrency((currency != null && !currency.getName()
+ .isEmpty()) ? currency.getName()
+ : null);
+ this.currency.setValue(currency);
+ }
+ public void observeCurrency(NewTransactionActivity activity, Observer<Currency> observer) {
+ currency.observe(activity, observer);
+ }
+ public void stopObservingCurrency(Observer<Currency> observer) {
+ currency.removeObserver(observer);
+ }
}
}
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
context.getResources().getDisplayMetrics()));
}
+ public static int sp2px(Context context, float sp) {
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
+ context.getResources().getDisplayMetrics()));
+ }
}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright © 2019 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
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your opinion), any later version.
+ ~
+ ~ MoLe is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License terms for details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:longClickable="true">
+
+ <TextView
+ android:id="@+id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:text="USD"
+ android:textAppearance="?attr/textAppearanceListItem"
+ tools:ignore="HardcodedText"
+ android:minWidth="20dp"
+ android:gravity="center_horizontal"
+ />
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright © 2019 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
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your opinion), any later version.
+ ~
+ ~ MoLe is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License terms for details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minWidth="60dp"
+ android:animateLayoutChanges="true"
+ app:layout_constraintWidth_min="60dp"
+ android:padding="@dimen/text_margin">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/text_margin"
+ android:text="@string/choose_currency_label"
+ android:textSize="18sp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list"
+ android:name="net.ktnx.mobileledger.ui.CurrencySelectorFragment"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ app:layoutManager="LinearLayoutManager"
+ app:layout_constraintBottom_toTopOf="@id/new_currency_panel"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/label"
+ tools:context="net.ktnx.mobileledger.ui.CurrencySelectorFragment"
+ tools:listitem="@layout/fragment_currency_selector">
+
+ </androidx.recyclerview.widget.RecyclerView>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/new_currency_panel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ app:layout_constraintTop_toBottomOf="@id/list"
+ app:layout_constraintBottom_toTopOf="@id/params_panel"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <TextView
+ android:id="@+id/btn_add_new"
+ style="@style/TextAppearance.AppCompat.Widget.Button.Colored"
+ android:textColor="@color/colorPrimary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/add_button"
+ android:layout_margin="@dimen/text_margin"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/btn_no_currency"
+ app:layout_constraintTop_toTopOf="parent" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/btn_no_currency"
+ android:text="@string/btn_no_currency"
+ style="@style/TextAppearance.AppCompat.Widget.Button.Colored"
+ android:textColor="@color/colorPrimary"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/btn_add_new"
+ android:layout_margin="@dimen/text_margin"/>
+
+ <EditText
+ android:id="@+id/new_currency_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:hint="@string/new_currency_name_hint"
+ android:inputType="text"
+ android:singleLine="true"
+ android:text=""
+ android:visibility="invisible"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/btn_add_currency"
+ style="@style/TextAppearance.AppCompat.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/text_margin"
+ android:text="@string/add_button"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/new_currency_name" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/params_panel"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/new_currency_panel">
+
+ <RadioGroup
+ android:id="@+id/position_radio_group"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:orientation="horizontal"
+ app:layout_constraintBottom_toTopOf="@id/currency_gap"
+ android:layout_marginBottom="@dimen/text_margin"
+ >
+
+ <RadioButton
+ android:id="@+id/currency_position_left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/currency_position_left" />
+
+ <RadioButton
+ android:id="@+id/currency_position_right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/currency_position_right" />
+ </RadioGroup>
+
+ <Switch
+ android:id="@+id/currency_gap"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/currency_has_gap"
+ app:layout_constraintTop_toBottomOf="@id/position_radio_group"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
app:layout_constraintStart_toEndOf="@id/comment_button"
app:layout_constraintTop_toBottomOf="@id/account_row_acc_name" />
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/amount_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/comment">
<EditText
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@id/currency"
android:id="@+id/account_row_acc_amounts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberSigned|numberDecimal|number"
android:minWidth="80sp"
android:selectAllOnFocus="true"
- android:textAlignment="viewEnd" />
- </LinearLayout>
+ android:textAlignment="viewEnd"
+ app:layout_constraintWidth_min="@dimen/text_margin"/>
+
+ <TextView
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ android:id="@+id/currency"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/currency_symbol"
+ style="@style/TextAppearance.AppCompat.Widget.Button"
+ android:textAllCaps="false"
+ android:visibility="visible"
+ android:minWidth="24dp"
+ android:gravity="center_horizontal"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<string name="api_post_1_14">Версия 1.15 или по-нова</string>
<string name="api_pre_1_15">Версия 1.14.x</string>
<string name="profile_api_version_title">Версия на сървъра</string>
+ <string name="add_button">Добавяне…</string>
+ <string name="close_button">Затваряне</string>
<string name="transaction_account_comment_hint">бележка</string>
+ <string name="btn_no_currency">без</string>
+ <string name="choose_currency_label">Валута</string>
+ <string name="currency_has_gap">Отстояние от сумата</string>
+ <string name="currency_position_left">Вляво</string>
+ <string name="currency_position_right">Вдясно</string>
+ <string name="new_currency_name_hint">валута/ценност</string>
</resources>
<string name="api_post_1_14">Version 1.15 and above</string>
<string name="api_auto">Detect automaticaly</string>
<string name="profile_api_version_title">Backend server version</string>
+ <string name="currency_symbol" translatable="false">¤</string>
+ <string name="add_button">Add…</string>
+ <string name="close_button">Close</string>
<string name="transaction_account_comment_hint">comment</string>
+ <string name="choose_currency_label">Currency</string>
+ <string name="new_currency_name_hint">currency/commodity</string>
+ <string name="btn_no_currency">none</string>
+ <string name="currency_position_left">Left</string>
+ <string name="currency_position_right">Right</string>
+ <string name="currency_has_gap">Offset from the value</string>
</resources>