/* * Author: Balch * Created: 9/4/14 12:26 AM * * This file is part of MockTrade. * * MockTrade 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. * * MockTrade 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 MockTrade. If not, see <http://www.gnu.org/licenses/>. * * Copyright (C) 2014 */ package com.balch.mocktrade.order; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.InputFilter; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; import android.view.ViewGroup; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import com.balch.android.app.framework.domain.ColumnDescriptor; import com.balch.android.app.framework.domain.EditState; import com.balch.android.app.framework.domain.ValidatorException; import com.balch.android.app.framework.domain.ViewHint; import com.balch.android.app.framework.domain.widget.ControlMapper; import com.balch.android.app.framework.domain.widget.EditLayout; import com.balch.android.app.framework.types.Money; import com.balch.mocktrade.TradeModelProvider; import com.balch.mocktrade.R; import com.balch.mocktrade.finance.FinanceModel; import com.balch.mocktrade.finance.GoogleFinanceModel; import com.balch.mocktrade.finance.Quote; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; public class StockSymbolLayout extends LinearLayout implements EditLayout, TextWatcher { private static final String TAG = StockSymbolLayout.class.getSimpleName(); private static final int TEXT_CHANGE_DELAY_MS = 500; private TextView mLabel; private EditText mValue; private TextView mDescription; private TextView mPrice; private ColumnDescriptor mDescriptor; private EditLayoutListener mEditLayoutListener; private FinanceModel mFinanceModel; private boolean mAllowEmpty; private Handler mTextChangeHandler = new Handler(Looper.getMainLooper()); private Runnable mTxtChangeRunnable = new Runnable() { @Override public void run() { try { doTextChanged(); } catch (Exception ex) { Log.e(TAG, "Exception in TextChanged Runnable", ex); } } }; public StockSymbolLayout(Context context) { super(context); initialize(); } public StockSymbolLayout(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public StockSymbolLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } protected void initialize() { setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); int padding = getResources().getDimensionPixelSize(R.dimen.edit_control_padding); setPadding(0, padding, 0, padding); setOrientation(VERTICAL); inflate(getContext(), com.balch.mocktrade.R.layout.symbol_edit_control, this); this.mLabel = (TextView) findViewById(R.id.symbol_edit_label); this.mValue = (EditText) findViewById(R.id.symbol_edit_value); this.mDescription = (TextView)findViewById(com.balch.mocktrade.R.id.symbol_edit_description); this.mPrice = (TextView)findViewById(com.balch.mocktrade.R.id.symbol_edit_price); TradeModelProvider modelProvider = (TradeModelProvider)this.getContext().getApplicationContext(); this.mFinanceModel = new GoogleFinanceModel(modelProvider.getContext(), modelProvider.getNetworkRequestProvider(), modelProvider.getSettings()); this.mValue.setHint(R.string.order_symbol_hint); } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); this.mValue.setEnabled(enabled); } protected void setInvestmentData(String description, Money price) { this.mDescription.setText(description); this.mPrice.setText((price != null) ? price.getFormatted(2) : ""); } @Override public void bind(ColumnDescriptor descriptor) { this.mDescriptor = descriptor; this.mLabel.setText(descriptor.getLabelResId()); this.mValue.removeTextChangedListener(this); this.mValue.setLines(1); boolean enabled = (descriptor.getState() == EditState.CHANGEABLE); this.mAllowEmpty = true; List<InputFilter> filters = getInputFilters(); try { if (descriptor.getField() != null) { Object obj = descriptor.getField().get(descriptor.getItem()); this.mValue.setText(this.getValueAsString(obj)); } // check the hints associated with this field for (ViewHint hint : descriptor.getHints()) { if (hint.getHint() == ViewHint.Hint.MAX_CHARS) { filters.add(new InputFilter.LengthFilter(hint.getIntValue())); } else if (hint.getHint() == ViewHint.Hint.DISPLAY_LINES) { this.mValue.setLines(hint.getIntValue()); } else if (hint.getHint() == ViewHint.Hint.NOT_EMPTY) { this.mAllowEmpty = !hint.getBoolValue(); } } } catch (IllegalAccessException e) { Log.e(TAG, "bind error", e); this.mValue.setText(R.string.get_value_error); enabled = false; } this.mValue.setEnabled(enabled); this.mValue.setFilters(filters.toArray(new InputFilter[filters.size()])); if (!TextUtils.isEmpty(this.mValue.getText())) { doTextChanged(); } this.mValue.addTextChangedListener(this); } protected List<InputFilter> getInputFilters() { return new ArrayList<>(); } protected String getValueAsString(Object obj) { String value = ""; if (obj != null) { value = obj.toString(); } return value; } @Override public ColumnDescriptor getDescriptor() { return this.mDescriptor; } @Override public void validate() throws ValidatorException { String val = this.mValue.getText().toString(); // empty string validation if (!this.mAllowEmpty) { if (TextUtils.isEmpty(val)) { throw new ValidatorException(getResources().getString(R.string.error_empty_string)); } } } @Override public Object getValue() { return this.mValue.getText().toString(); } @Override public void setValue(Object value) { this.mValue.setText(this.getValueAsString(value)); } @Override public void setEditControlListener(EditLayoutListener listener) { this.mEditLayoutListener = listener; } @Override public void setControlMapper(ControlMapper controlMapper) { } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { this.mTextChangeHandler.removeCallbacks(this.mTxtChangeRunnable); } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { this.mTextChangeHandler.postDelayed(this.mTxtChangeRunnable, TEXT_CHANGE_DELAY_MS); } @Override public void afterTextChanged(Editable s) { for (int x = 0; x < s.length(); x++) { char c = s.charAt(x); if (Character.isLowerCase(c)) { s.replace(x, x+1, String.valueOf(Character.toUpperCase(c))); } } } protected void doTextChanged() { final String symbol = mValue.getText().toString(); boolean hasError = false; try { this.validate(); mValue.setError(null); } catch (ValidatorException e) { this.mValue.setError(e.getMessage()); hasError = true; } if (!hasError) { new GetQuoteTask(this, mFinanceModel).execute(symbol); } } protected void callListenerOnChanged(boolean hasError) { if (this.mEditLayoutListener != null) { try { this.mEditLayoutListener.onChanged(this.mDescriptor, this.getValue(), hasError); } catch (ValidatorException e) { this.mValue.setError(e.getMessage()); } } } public Money getPrice() { return new Money(mPrice.getText().toString()); } private static class GetQuoteTask extends AsyncTask<String, Void, Quote> { private final FinanceModel mFinanceModel; private final WeakReference<StockSymbolLayout> mStockSymbolLayout; GetQuoteTask(StockSymbolLayout layout, FinanceModel financeModel) { mStockSymbolLayout = new WeakReference<>(layout); mFinanceModel = financeModel; } @Override protected Quote doInBackground(String... symbols) { return mFinanceModel.getQuote(symbols[0]); } @Override protected void onPostExecute(Quote quote) { final StockSymbolLayout layout = mStockSymbolLayout.get(); if (layout != null) { if (quote != null) { layout.setInvestmentData(quote.getName(), quote.getPrice()); layout.mValue.setError(null); layout.callListenerOnChanged(false); } else { layout.setInvestmentData("", null); String message = layout.getResources().getString(R.string.error_invalid_symbol); layout.mValue.setError(message); layout.callListenerOnChanged(true); } } } } }