/* * Copyright 2011-2014 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package devcoin.wallet.ui; import java.math.BigInteger; import java.util.Currency; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; import android.text.Editable; import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import com.google.devcoin.core.Transaction; import devcoin.wallet.Constants; import devcoin.wallet.util.GenericUtils; import devcoin.wallet.util.WalletUtils; import devcoin.wallet.R; /** * @author Andreas Schildbach */ public final class CurrencyAmountView extends FrameLayout { public static interface Listener { void changed(); void done(); void focusChanged(final boolean hasFocus); } private int significantColor, lessSignificantColor, errorColor; private Drawable deleteButtonDrawable, contextButtonDrawable; private Drawable currencySymbolDrawable; private int inputPrecision = 0; private int hintPrecision = 0; private int shift = 0; private boolean amountSigned = false; private boolean smallerInsignificant = true; private boolean validateAmount = true; private TextView textView; private View contextButton; private Listener listener; private OnClickListener contextButtonClickListener; public CurrencyAmountView(final Context context) { super(context); init(context); } public CurrencyAmountView(final Context context, final AttributeSet attrs) { super(context, attrs); init(context); } private void init(final Context context) { final Resources resources = context.getResources(); significantColor = resources.getColor(R.color.fg_significant); lessSignificantColor = resources.getColor(R.color.fg_less_significant); errorColor = resources.getColor(R.color.fg_error); deleteButtonDrawable = resources.getDrawable(R.drawable.ic_input_delete); } @Override protected void onFinishInflate() { super.onFinishInflate(); final Context context = getContext(); textView = (TextView) getChildAt(0); textView.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); textView.setHintTextColor(lessSignificantColor); textView.setHorizontalFadingEdgeEnabled(true); textView.setSingleLine(); setHint(null); setValidateAmount(textView instanceof EditText); textView.addTextChangedListener(textViewListener); textView.setOnFocusChangeListener(textViewListener); textView.setOnEditorActionListener(textViewListener); contextButton = new View(context) { @Override protected void onMeasure(final int wMeasureSpec, final int hMeasureSpec) { setMeasuredDimension(textView.getCompoundPaddingRight(), textView.getMeasuredHeight()); } }; final LayoutParams chooseViewParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); chooseViewParams.gravity = Gravity.RIGHT; contextButton.setLayoutParams(chooseViewParams); this.addView(contextButton); updateAppearance(); } public void setCurrencySymbol(@Nullable final String currencyCode) { if (Constants.CURRENCY_CODE_BTC.equals(currencyCode)) { currencySymbolDrawable = getResources().getDrawable(R.drawable.currency_symbol_btc); } else if (Constants.CURRENCY_CODE_MBTC.equals(currencyCode)) { currencySymbolDrawable = getResources().getDrawable(R.drawable.currency_symbol_mbtc); } else if (currencyCode != null) { final String currencySymbol = currencySymbol(currencyCode); final float textSize = textView.getTextSize(); final float smallerTextSize = textSize * (smallerInsignificant ? (20f / 24f) : 1); currencySymbolDrawable = new CurrencySymbolDrawable(currencySymbol, smallerTextSize, lessSignificantColor, textSize * 0.37f); } else { currencySymbolDrawable = null; } updateAppearance(); } public void setInputPrecision(final int inputPrecision) { this.inputPrecision = inputPrecision; } public void setHintPrecision(final int hintPrecision) { this.hintPrecision = hintPrecision; } public void setShift(final int shift) { this.shift = shift; } public void setAmountSigned(final boolean amountSigned) { this.amountSigned = amountSigned; } public void setSmallerInsignificant(final boolean smallerInsignificant) { this.smallerInsignificant = smallerInsignificant; } public void setValidateAmount(final boolean validateAmount) { this.validateAmount = validateAmount; } public void setContextButton(final int contextButtonResId, @Nonnull final OnClickListener contextButtonClickListener) { this.contextButtonDrawable = getContext().getResources().getDrawable(contextButtonResId); this.contextButtonClickListener = contextButtonClickListener; updateAppearance(); } public void setListener(@Nonnull final Listener listener) { this.listener = listener; } @CheckForNull public BigInteger getAmount() { if (isValidAmount(false)) return GenericUtils.toNanoCoins(textView.getText().toString().trim(), shift); else return null; } public void setAmount(@Nullable final BigInteger amount, final boolean fireListener) { if (!fireListener) textViewListener.setFire(false); if (amount != null) textView.setText(amountSigned ? GenericUtils.formatValue(amount, Constants.CURRENCY_PLUS_SIGN, Constants.CURRENCY_MINUS_SIGN, inputPrecision, shift) : GenericUtils.formatValue(amount, inputPrecision, shift)); else textView.setText(null); if (!fireListener) textViewListener.setFire(true); } public void setHint(@Nullable final BigInteger amount) { final SpannableStringBuilder hint; if (amount != null) hint = new SpannableStringBuilder(GenericUtils.formatValue(amount, hintPrecision, shift)); else hint = new SpannableStringBuilder("0.00"); WalletUtils.formatSignificant(hint, smallerInsignificant ? WalletUtils.SMALLER_SPAN : null); textView.setHint(hint); } @Override public void setEnabled(final boolean enabled) { super.setEnabled(enabled); textView.setEnabled(enabled); updateAppearance(); } public void setTextColor(final int color) { significantColor = color; updateAppearance(); } public void setStrikeThru(final boolean strikeThru) { if (strikeThru) textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); else textView.setPaintFlags(textView.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); } private boolean isValidAmount(final boolean zeroIsValid) { final String amount = textView.getText().toString().trim(); try { if (!amount.isEmpty()) { final BigInteger nanoCoins = GenericUtils.toNanoCoins(amount, shift); // exactly zero if (zeroIsValid && nanoCoins.signum() == 0) return true; // too small if (nanoCoins.compareTo(Transaction.MIN_NONDUST_OUTPUT) < 0) return false; return true; } } catch (final Exception x) { } return false; } private final OnClickListener deleteClickListener = new OnClickListener() { @Override public void onClick(final View v) { setAmount(null, true); textView.requestFocus(); } }; private void updateAppearance() { final boolean enabled = textView.isEnabled(); contextButton.setEnabled(enabled); final String amount = textView.getText().toString().trim(); if (enabled && !amount.isEmpty()) { textView.setCompoundDrawablesWithIntrinsicBounds(currencySymbolDrawable, null, deleteButtonDrawable, null); contextButton.setOnClickListener(deleteClickListener); } else if (enabled && contextButtonDrawable != null) { textView.setCompoundDrawablesWithIntrinsicBounds(currencySymbolDrawable, null, contextButtonDrawable, null); contextButton.setOnClickListener(contextButtonClickListener); } else { textView.setCompoundDrawablesWithIntrinsicBounds(currencySymbolDrawable, null, null, null); contextButton.setOnClickListener(null); } contextButton.requestLayout(); textView.setTextColor(!validateAmount || isValidAmount(true) ? significantColor : errorColor); } @Override protected Parcelable onSaveInstanceState() { final Bundle state = new Bundle(); state.putParcelable("super_state", super.onSaveInstanceState()); state.putParcelable("child_textview", textView.onSaveInstanceState()); state.putSerializable("amount", getAmount()); return state; } @Override protected void onRestoreInstanceState(final Parcelable state) { if (state instanceof Bundle) { final Bundle bundle = (Bundle) state; super.onRestoreInstanceState(bundle.getParcelable("super_state")); textView.onRestoreInstanceState(bundle.getParcelable("child_textview")); setAmount((BigInteger) bundle.getSerializable("amount"), false); } else { super.onRestoreInstanceState(state); } } private final TextViewListener textViewListener = new TextViewListener(); private final class TextViewListener implements TextWatcher, OnFocusChangeListener, OnEditorActionListener { private boolean fire = true; public void setFire(final boolean fire) { this.fire = fire; } @Override public void afterTextChanged(final Editable s) { // workaround for German keyboards final String original = s.toString(); final String replaced = original.replace(',', '.'); if (!replaced.equals(original)) { s.clear(); s.append(replaced); } WalletUtils.formatSignificant(s, smallerInsignificant ? WalletUtils.SMALLER_SPAN : null); } @Override public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } @Override public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { updateAppearance(); if (listener != null && fire) listener.changed(); } @Override public void onFocusChange(final View v, final boolean hasFocus) { if (!hasFocus) { final BigInteger amount = getAmount(); if (amount != null) setAmount(amount, false); } if (listener != null && fire) listener.focusChanged(hasFocus); } @Override public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE && listener != null && fire) listener.done(); return false; } } private static String currencySymbol(@Nonnull final String currencyCode) { try { final Currency currency = Currency.getInstance(currencyCode); return currency.getSymbol(); } catch (final IllegalArgumentException x) { return currencyCode; } } }