/******************************************************************************* * Copyright (c) 2010 Denis Solonenko. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v2.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * Denis Solonenko - initial API and implementation ******************************************************************************/ package ru.orangesoftware.financisto2.widget; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.text.Editable; import android.text.InputType; import android.text.Spanned; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.text.method.NumberKeyListener; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import android.widget.EditText; import android.widget.ImageSwitcher; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ViewSwitcher; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Click; import org.androidannotations.annotations.EViewGroup; import org.androidannotations.annotations.ViewById; import org.androidannotations.annotations.res.ColorRes; import org.androidannotations.annotations.res.DimensionPixelSizeRes; import org.androidannotations.annotations.res.DrawableRes; import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicInteger; import ru.orangesoftware.financisto2.R; import ru.orangesoftware.financisto2.model.Currency; import ru.orangesoftware.financisto2.utils.Utils; @EViewGroup(R.layout.amount_input) public class AmountInput extends LinearLayout { public static interface OnAmountChangedListener { void onAmountChanged(long oldAmount, long newAmount); } public static final String EXTRA_AMOUNT = "amount"; public static final String EXTRA_CURRENCY = "currency"; private static final AtomicInteger EDIT_AMOUNT_REQUEST = new AtomicInteger(2000); protected Activity owner; private Currency currency; private int decimals; @ViewById(R.id.signSwitcher) protected ImageSwitcher signSwitcher; @ViewById(R.id.primary) protected EditText primary; @ViewById(R.id.secondary) protected EditText secondary; @DimensionPixelSizeRes(R.dimen.select_entry_height_no_label) protected int minHeight; @DrawableRes(R.drawable.ic_action_add) protected Drawable plusDrawable; @ColorRes(R.color.positive_amount) protected int plusColor; @DrawableRes(R.drawable.ic_action_minus) protected Drawable minusDrawable; @ColorRes(R.color.negative_amount) protected int minusColor; private int requestId; private OnAmountChangedListener onAmountChangedListener; private boolean incomeExpenseEnabled = true; private boolean isExpense = true; protected AmountInput(Context context, AttributeSet attrs) { super(context, attrs); } protected AmountInput(Context context) { super(context); } public void disableIncomeExpenseButton() { incomeExpenseEnabled = false; signSwitcher.setEnabled(false); } public boolean isIncomeExpenseEnabled() { return incomeExpenseEnabled; } public void setIncome() { if (isExpense) { onClickSignSwitcher(); } } public void setExpense() { if (!isExpense) { onClickSignSwitcher(); } } public boolean isExpense() { return isExpense; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); Utils.setEnabled(this, enabled); if (!incomeExpenseEnabled) { disableIncomeExpenseButton(); } } public void setOnAmountChangedListener( OnAmountChangedListener onAmountChangedListener) { this.onAmountChangedListener = onAmountChangedListener; } private final TextWatcher textWatcher = new TextWatcher() { private long oldAmount; @Override public void afterTextChanged(Editable s) { if (onAmountChangedListener != null) { long amount = getAmount(); onAmountChangedListener.onAmountChanged(oldAmount, amount); oldAmount = amount; } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { oldAmount = getAmount(); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } }; @AfterViews protected void initialize() { setMinimumHeight(minHeight); plusDrawable.mutate().setColorFilter(plusColor, PorterDuff.Mode.SRC_ATOP); minusDrawable.mutate().setColorFilter(minusColor, PorterDuff.Mode.SRC_ATOP); requestId = EDIT_AMOUNT_REQUEST.incrementAndGet(); signSwitcher.setFactory(new ViewSwitcher.ViewFactory() { public View makeView() { ImageView v = new ImageView(getContext()); v.setScaleType(ImageView.ScaleType.FIT_CENTER); v.setLayoutParams(new ImageSwitcher.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); return v; } }); signSwitcher.setImageDrawable(minusDrawable); primary.setKeyListener(keyListener); primary.addTextChangedListener(textWatcher); primary.setOnFocusChangeListener(selectAllOnFocusListener); secondary.setKeyListener(new DigitsKeyListener(false, false) { @Override public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_DEL) { if (content.length() == 0) { primary.requestFocus(); int pos = primary.getText().length(); primary.setSelection(pos, pos); return true; } } return super.onKeyDown(view, content, keyCode, event); } @Override public int getInputType() { return InputType.TYPE_CLASS_PHONE; } }); secondary.addTextChangedListener(textWatcher); secondary.setOnFocusChangeListener(selectAllOnFocusListener); } @Click(R.id.calculator) protected void onClickCalculator() { startInputActivity(CalculatorInput.class); } @Click(R.id.signSwitcher) protected void onClickSignSwitcher() { if (isExpense) { isExpense = false; signSwitcher.setImageDrawable(plusDrawable); notifyAmountChangedListener(); } else { isExpense = true; signSwitcher.setImageDrawable(minusDrawable); notifyAmountChangedListener(); } } private void notifyAmountChangedListener() { if (onAmountChangedListener != null) { long amount = getAmount(); onAmountChangedListener.onAmountChanged(-amount, amount); } } private static final char[] acceptedChars = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; private static final char[] commaChars = new char[]{'.', ','}; private final NumberKeyListener keyListener = new NumberKeyListener() { @Override public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (end - start == 1) { char c = source.charAt(0); if (c == '.' || c == ',') { onDotOrComma(); return ""; } if (isIncomeExpenseEnabled()) { if (c == '-') { setExpense(); return ""; } if (c == '+') { setIncome(); return ""; } } } return super.filter(source, start, end, dest, dstart, dend); } @Override public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { char c = event.getMatch(commaChars); if (c == '.' || c == ',') { onDotOrComma(); return true; } return super.onKeyDown(view, content, keyCode, event); } @Override protected char[] getAcceptedChars() { return acceptedChars; } @Override public int getInputType() { return InputType.TYPE_CLASS_PHONE; } }; private final View.OnFocusChangeListener selectAllOnFocusListener = new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { EditText t = (EditText) v; if (hasFocus) { t.selectAll(); } } }; protected <T extends Activity> void startInputActivity(Class<T> clazz) { Intent intent = new Intent(getContext(), clazz); if (currency != null) { intent.putExtra(EXTRA_CURRENCY, currency.id); } intent.putExtra(EXTRA_AMOUNT, getAbsAmountString()); owner.startActivityForResult(intent, requestId); } protected void onDotOrComma() { secondary.requestFocus(); } public Currency getCurrency() { return currency; } public int getDecimals() { return decimals; } public void setCurrency(Currency currency) { this.currency = currency; } public void setOwner(Activity owner) { this.owner = owner; } public boolean processActivityResult(int requestCode, Intent data) { if (requestCode == requestId) { String amount = data.getStringExtra(EXTRA_AMOUNT); if (amount != null) { try { BigDecimal d = new BigDecimal(amount).setScale(2, BigDecimal.ROUND_HALF_UP); boolean isExpense = isExpense(); setAmount(d.unscaledValue().longValue()); if (isExpense) { setExpense(); } return true; } catch (NumberFormatException ex) { return false; } } } return false; } public void setAmount(long amount) { long absAmount = Math.abs(amount); long x = absAmount / 100; long y = absAmount - 100 * x; primary.setText(String.valueOf(x)); secondary.setText(String.format("%02d", y)); if (isIncomeExpenseEnabled() && amount != 0) { if (amount > 0) { setIncome(); } else { setExpense(); } } } public long getAmount() { String p = primary.getText().toString(); String s = secondary.getText().toString(); long x = 100 * toLong(p); long y = toLong(s); long amount = x + (s.length() == 1 ? 10 * y : y); return isExpense() ? -amount : amount; } private String getAbsAmountString() { String p = primary.getText().toString().trim(); String s = secondary.getText().toString().trim(); return (Utils.isNotEmpty(p) ? p : "0") + "." + (Utils.isNotEmpty(s) ? s : "0"); } private long toLong(String s) { return s == null || s.length() == 0 ? 0 : Long.parseLong(s); } public void setColor(int color) { primary.setTextColor(color); secondary.setTextColor(color); } public void openCalculator() { startInputActivity(CalculatorInput.class); } }