package org.solovyev.android.calculator.converter; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.graphics.Typeface; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.TextInputLayout; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import org.solovyev.android.calculator.App; import org.solovyev.android.calculator.AppComponent; import org.solovyev.android.calculator.AppModule; import org.solovyev.android.calculator.BaseDialogFragment; import org.solovyev.android.calculator.Clipboard; import org.solovyev.android.calculator.Editor; import org.solovyev.android.calculator.Keyboard; import org.solovyev.android.calculator.Named; import org.solovyev.android.calculator.R; import org.solovyev.android.calculator.keyboard.FloatingKeyboard; import org.solovyev.android.calculator.keyboard.FloatingKeyboardWindow; import org.solovyev.android.calculator.keyboard.FloatingNumberKeyboard; import org.solovyev.android.calculator.math.MathUtils; import org.solovyev.android.calculator.text.NaturalComparator; import org.solovyev.android.calculator.view.EditTextCompat; import javax.annotation.Nonnull; import javax.inject.Inject; import butterknife.Bind; import butterknife.ButterKnife; import static org.solovyev.android.calculator.UiPreferences.Converter.lastDimension; import static org.solovyev.android.calculator.UiPreferences.Converter.lastUnitsFrom; import static org.solovyev.android.calculator.UiPreferences.Converter.lastUnitsTo; public class ConverterFragment extends BaseDialogFragment implements AdapterView.OnItemSelectedListener, View.OnFocusChangeListener, TextView.OnEditorActionListener, View.OnClickListener, TextWatcher { private static final int NUMBER_INPUT_TYPE = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED; private static final String STATE_SELECTION_FROM = "selection.from"; private static final String STATE_SELECTION_TO = "selection.to"; private static final String EXTRA_VALUE = "value"; public static final int NONE = -1; @NonNull private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(null); @Inject Typeface typeface; @Inject Clipboard clipboard; @Inject Keyboard keyboard; @javax.inject.Named(AppModule.PREFS_UI) @Inject SharedPreferences uiPreferences; @Inject Editor editor; @Bind(R.id.converter_dimensions_spinner) Spinner dimensionsSpinner; @Bind(R.id.converter_spinner_from) Spinner spinnerFrom; @Bind(R.id.converter_label_from) TextInputLayout labelFrom; @Bind(R.id.converter_edittext_from) EditTextCompat editTextFrom; @Bind(R.id.converter_spinner_to) Spinner spinnerTo; @Bind(R.id.converter_label_to) TextInputLayout labelTo; @Bind(R.id.converter_edittext_to) EditText editTextTo; @Bind(R.id.converter_swap_button) ImageButton swapButton; private ArrayAdapter<Named<ConvertibleDimension>> dimensionsAdapter; private ArrayAdapter<Named<Convertible>> adapterFrom; private ArrayAdapter<Named<Convertible>> adapterTo; private int pendingFromSelection = NONE; private int pendingToSelection = NONE; private boolean useSystemKeyboard = true; public static void show(@Nonnull FragmentActivity activity) { show(activity, 1d); } public static void show(@Nonnull FragmentActivity activity, double value) { final ConverterFragment fragment = new ConverterFragment(); final Bundle args = new Bundle(1); args.putDouble(EXTRA_VALUE, value); fragment.setArguments(args); App.showDialog(fragment, "converter", activity.getSupportFragmentManager()); } @NonNull @Override public AlertDialog onCreateDialog(@Nullable Bundle savedInstanceState) { final AlertDialog dialog = super.onCreateDialog(savedInstanceState); dialog.setCanceledOnTouchOutside(false); return dialog; } @Override protected void onPrepareDialog(@NonNull AlertDialog.Builder builder) { builder.setPositiveButton(R.string.c_use, null); builder.setNegativeButton(R.string.cpp_cancel, null); builder.setNeutralButton(R.string.cpp_copy, null); } @Override protected void inject(@NonNull AppComponent component) { super.inject(component); component.inject(this); } @SuppressLint("InflateParams") @Nullable @Override protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.cpp_unit_converter, null); ButterKnife.bind(this, view); dimensionsAdapter = App.makeSimpleSpinnerAdapter(context); for (ConvertibleDimension dimension : UnitDimension.values()) { dimensionsAdapter.add(dimension.named(context)); } dimensionsAdapter.add(NumeralBaseDimension.get().named(context)); adapterFrom = App.makeSimpleSpinnerAdapter(context); adapterTo = App.makeSimpleSpinnerAdapter(context); dimensionsSpinner.setAdapter(dimensionsAdapter); spinnerFrom.setAdapter(adapterFrom); spinnerTo.setAdapter(adapterTo); dimensionsSpinner.setOnItemSelectedListener(this); spinnerFrom.setOnItemSelectedListener(this); spinnerTo.setOnItemSelectedListener(this); editTextFrom.setOnFocusChangeListener(this); editTextFrom.setOnEditorActionListener(this); editTextFrom.addTextChangedListener(this); editTextFrom.setOnClickListener(this); onKeyboardTypeChanged(); swapButton.setOnClickListener(this); swapButton.setImageResource(App.getTheme().light ? R.drawable.ic_swap_vert_black_24dp : R.drawable.ic_swap_vert_white_24dp); if (savedInstanceState == null) { editTextFrom.setText(String.valueOf(getArguments().getDouble(EXTRA_VALUE, 1f))); pendingFromSelection = lastUnitsFrom.getPreference(uiPreferences); pendingToSelection = lastUnitsTo.getPreference(uiPreferences); dimensionsSpinner.setSelection(MathUtils.clamp(lastDimension.getPreference(uiPreferences), 0, dimensionsAdapter.getCount() - 1)); } else { pendingFromSelection = savedInstanceState.getInt(STATE_SELECTION_FROM, NONE); pendingToSelection = savedInstanceState.getInt(STATE_SELECTION_TO, NONE); } return view; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_SELECTION_FROM, spinnerFrom.getSelectedItemPosition()); outState.putInt(STATE_SELECTION_TO, spinnerTo.getSelectedItemPosition()); } @Override public void onPause() { saveLastUsedValues(); super.onPause(); } private void saveLastUsedValues() { final SharedPreferences.Editor editor = uiPreferences.edit(); lastDimension.putPreference(editor, dimensionsSpinner.getSelectedItemPosition()); lastUnitsFrom.putPreference(editor, spinnerFrom.getSelectedItemPosition()); lastUnitsTo.putPreference(editor, spinnerTo.getSelectedItemPosition()); editor.apply(); } @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { switch (parent.getId()) { case R.id.converter_dimensions_spinner: onDimensionChanged(dimensionsAdapter.getItem(position).item); break; case R.id.converter_spinner_from: onUnitFromChanged(adapterFrom.getItem(position).item); break; case R.id.converter_spinner_to: convert(); break; } } private void onUnitFromChanged(@NonNull Convertible unit) { final int dimensionPosition = dimensionsSpinner.getSelectedItemPosition(); updateUnitsTo(dimensionsAdapter.getItem(dimensionPosition).item, unit); convert(); } private void onDimensionChanged(@NonNull ConvertibleDimension dimension) { updateUnitsFrom(dimension); updateUnitsTo(dimension, adapterFrom.getItem(spinnerFrom.getSelectedItemPosition()).item); convert(); checkKeyboardType(dimension); } private void checkKeyboardType(@NonNull ConvertibleDimension dimension) { keyboardWindow.hide(); useSystemKeyboard = !(dimension instanceof NumeralBaseDimension); onKeyboardTypeChanged(); } private void onKeyboardTypeChanged() { editTextFrom.setInputType(useSystemKeyboard ? NUMBER_INPUT_TYPE : InputType.TYPE_CLASS_TEXT); editTextFrom.setShowSoftInputOnFocusCompat(useSystemKeyboard); } private void updateUnitsFrom(@NonNull ConvertibleDimension dimension) { adapterFrom.setNotifyOnChange(false); adapterFrom.clear(); for (Convertible unit : dimension.getUnits()) { adapterFrom.add(unit.named(getActivity())); } adapterFrom.sort(NaturalComparator.INSTANCE); adapterFrom.setNotifyOnChange(true); adapterFrom.notifyDataSetChanged(); if (pendingFromSelection != NONE) { spinnerFrom.setSelection(MathUtils.clamp(pendingFromSelection, 0, adapterFrom.getCount() - 1)); pendingFromSelection = NONE; } else { spinnerFrom.setSelection(MathUtils.clamp(spinnerFrom.getSelectedItemPosition(), 0, adapterFrom.getCount() - 1)); } } private void updateUnitsTo(@NonNull ConvertibleDimension dimension, @NonNull Convertible except) { final Convertible selectedUnit; if (pendingToSelection > NONE) { selectedUnit = null; } else { final int selectedPosition = spinnerTo.getSelectedItemPosition(); selectedUnit = selectedPosition >= 0 && selectedPosition < adapterTo.getCount() ? adapterTo.getItem(selectedPosition).item : null; } adapterTo.setNotifyOnChange(false); adapterTo.clear(); for (Convertible unit : dimension.getUnits()) { if (!except.equals(unit)) { adapterTo.add(unit.named(getActivity())); } } adapterTo.sort(NaturalComparator.INSTANCE); adapterTo.setNotifyOnChange(true); adapterTo.notifyDataSetChanged(); if (selectedUnit != null && !except.equals(selectedUnit)) { for (int i = 0; i < adapterTo.getCount(); i++) { final Convertible unit = adapterTo.getItem(i).item; if (unit.equals(selectedUnit)) { spinnerTo.setSelection(i); return; } } } if (pendingToSelection != NONE) { spinnerTo.setSelection(MathUtils.clamp(pendingToSelection, 0, adapterTo.getCount() - 1)); pendingToSelection = NONE; } else { spinnerTo.setSelection(MathUtils.clamp(spinnerTo.getSelectedItemPosition(), 0, adapterTo.getCount() - 1)); } } @Override public void onNothingSelected(AdapterView<?> parent) { } @Override public void onFocusChange(View v, boolean hasFocus) { switch (v.getId()) { case R.id.converter_edittext_from: if (!hasFocus) { convert(); } else { clearError(labelFrom); showKeyboard(); } break; } } private void showKeyboard() { if (useSystemKeyboard) { return; } keyboardWindow.show(new FloatingNumberKeyboard(new KeyboardUser()), null); } private void convert() { convert(true); } private void convert(boolean validate) { final String value = editTextFrom.getText().toString(); if (TextUtils.isEmpty(value)) { if (validate) { setError(labelFrom, R.string.cpp_nan); } return; } try { final Convertible from = adapterFrom.getItem(spinnerFrom.getSelectedItemPosition()).item; final Convertible to = adapterTo.getItem(spinnerTo.getSelectedItemPosition()).item; editTextTo.setText(from.convert(to, value)); clearError(labelFrom); } catch (RuntimeException e) { editTextTo.setText(""); if (validate) { setError(labelFrom, R.string.cpp_nan); } } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { switch (v.getId()) { case R.id.converter_edittext_from: if (actionId == EditorInfo.IME_ACTION_DONE) { App.hideIme(editTextFrom); convert(); return true; } break; } return false; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.converter_swap_button: keyboardWindow.hide(); swap(); break; case R.id.converter_edittext_from: showKeyboard(); break; default: super.onClick(v); break; } } @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { dismiss(); return; } final String text = editTextTo.getText().toString(); try { switch (which) { case DialogInterface.BUTTON_POSITIVE: editor.insert(text); dismiss(); break; case DialogInterface.BUTTON_NEUTRAL: clipboard.setText(text); Toast.makeText(getActivity(), getString(R.string.cpp_text_copied), Toast.LENGTH_SHORT).show(); break; } } catch (RuntimeException ignored) { } } private void swap() { editTextFrom.setText(editTextTo.getText()); final Convertible oldFromUnit = adapterFrom.getItem(spinnerFrom.getSelectedItemPosition()).item; final Convertible oldToUnit = adapterTo.getItem(spinnerTo.getSelectedItemPosition()).item; pendingToSelection = NONE; for (int i = 0; i < adapterFrom.getCount(); i++) { pendingToSelection++; final Convertible unit = adapterFrom.getItem(i).item; if (unit.equals(oldToUnit)) { pendingToSelection--; } else if (unit.equals(oldFromUnit)) { break; } } for (int i = 0; i < adapterFrom.getCount(); i++) { final Convertible unit = adapterFrom.getItem(i).item; if (unit.equals(oldToUnit)) { spinnerFrom.setSelection(i); break; } } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { convert(false); } @Override public void dismiss() { App.hideIme(this); super.dismiss(); } private class KeyboardUser implements FloatingKeyboard.User { @NonNull @Override public Context getContext() { return getActivity(); } @NonNull @Override public EditText getEditor() { return editTextFrom; } @NonNull @Override public ViewGroup getKeyboard() { return keyboardWindow.getContentView(); } @Override public void done() { keyboardWindow.hide(); convert(); } @Override public void showIme() { final InputMethodManager keyboard = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); keyboard.showSoftInput(getEditor(), InputMethodManager.SHOW_FORCED); keyboardWindow.hide(); } @Override public boolean isVibrateOnKeypress() { return keyboard.isVibrateOnKeypress(); } @NonNull @Override public Typeface getTypeface() { return typeface; } } }