package org.solovyev.android.calculator.plot; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.graphics.RectF; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.TextInputLayout; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; import org.solovyev.android.calculator.App; import org.solovyev.android.calculator.AppComponent; import org.solovyev.android.calculator.BaseDialogFragment; import org.solovyev.android.calculator.BaseFragment; import org.solovyev.android.calculator.R; import org.solovyev.android.plotter.Check; import org.solovyev.android.plotter.Plot; import org.solovyev.android.plotter.Plotter; import java.util.Locale; import javax.annotation.Nonnull; import javax.inject.Inject; import butterknife.Bind; import butterknife.ButterKnife; public class PlotDimensionsFragment extends BaseDialogFragment implements TextView.OnEditorActionListener { private static final String ARG_BOUNDS = "arg-bounds"; private static final String ARG_3D = "arg-3d"; private class MyTextWatcher implements TextWatcher { @NonNull private final TextInputLayout input; private final boolean x; private MyTextWatcher(@NonNull TextInputLayout input, boolean x) { this.input = input; this.x = x; } @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) { if (TextUtils.isEmpty(input.getError())) { return; } final RectF bounds = collectData(); if (x) { validXBounds(bounds); } else { validYBounds(bounds); } } } @Inject Plotter plotter; @Bind(R.id.plot_x_min) EditText xMin; @Bind(R.id.plot_x_min_label) TextInputLayout xMinLabel; @Bind(R.id.plot_x_max) EditText xMax; @Bind(R.id.plot_x_max_label) TextInputLayout xMaxLabel; @Bind(R.id.plot_y_min) EditText yMin; @Bind(R.id.plot_y_min_label) TextInputLayout yMinLabel; @Bind(R.id.plot_y_max) EditText yMax; @Bind(R.id.plot_y_max_label) TextInputLayout yMaxLabel; @Bind(R.id.y_bounds) View yBounds; @NonNull private RectF bounds = new RectF(); private boolean d3; public PlotDimensionsFragment() { } public static void show(@NonNull RectF bounds, boolean d3, @Nonnull FragmentManager fm) { App.showDialog(create(bounds, d3), "plot-dimensions", fm); } @NonNull private static PlotDimensionsFragment create(@NonNull RectF bounds, boolean d3) { final PlotDimensionsFragment dialog = new PlotDimensionsFragment(); final Bundle args = new Bundle(); args.putParcelable(ARG_BOUNDS, bounds); args.putBoolean(ARG_3D, d3); dialog.setArguments(args); return dialog; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Bundle arguments = getArguments(); Check.isNotNull(arguments); bounds = BaseFragment.getParcelable(arguments, ARG_BOUNDS); d3 = arguments.getBoolean(ARG_3D); } @Override protected void inject(@NonNull AppComponent component) { super.inject(component); component.inject(this); } @NonNull @Override public AlertDialog onCreateDialog(Bundle savedInstanceState) { final AlertDialog dialog = super.onCreateDialog(savedInstanceState); dialog.setCanceledOnTouchOutside(false); return dialog; } @Override protected void onShowDialog(@NonNull AlertDialog dialog, boolean firstTime) { super.onShowDialog(dialog, firstTime); if (firstTime) { final InputMethodManager imm = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(xMin, InputMethodManager.SHOW_IMPLICIT); } } @Override protected void onPrepareDialog(@NonNull AlertDialog.Builder builder) { builder.setTitle(R.string.cpp_plot_range); builder.setPositiveButton(R.string.cpp_done, null); } @NonNull @Override protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, Bundle savedInstanceState) { @SuppressLint("InflateParams") final View view = LayoutInflater.from(context).inflate(R.layout.fragment_plot_dimensions, null); ButterKnife.bind(this, view); setDimension(xMin, bounds.left); setDimension(xMax, bounds.right); setDimension(yMin, bounds.top); setDimension(yMax, bounds.bottom); xMin.addTextChangedListener(new MyTextWatcher(xMinLabel, true)); xMax.addTextChangedListener(new MyTextWatcher(xMaxLabel, true)); yMin.addTextChangedListener(new MyTextWatcher(yMinLabel, false)); yMax.addTextChangedListener(new MyTextWatcher(yMaxLabel, false)); if (d3) { yBounds.setVisibility(View.GONE); } return view; } @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: tryClose(); return; default: super.onClick(dialog, which); return; } } private void setDimension(@NonNull EditText view, float value) { view.setOnEditorActionListener(this); view.setText(String.format(Locale.getDefault(), "%.2f", value)); } private void tryClose() { if (validate()) { applyData(); dismiss(); } } private boolean validate() { final RectF bounds = collectData(); if (!validXBounds(bounds) | !validYBounds(bounds)) { return false; } return true; } private boolean validYBounds(@NonNull RectF bounds) { if (validNumbers(this.bounds.top, this.bounds.bottom, yMinLabel, yMaxLabel)) { return false; } if (bounds.top >= bounds.bottom) { setError(yMinLabel, " "); setError(yMaxLabel, "max ≯ min"); return false; } clearError(yMinLabel); clearError(yMaxLabel); return true; } private boolean validXBounds(@NonNull RectF bounds) { if (validNumbers(bounds.left, bounds.right, xMinLabel, xMaxLabel)) { return false; } if (bounds.left >= bounds.right) { setError(xMinLabel, " "); setError(xMaxLabel, "max ≯ min"); return false; } clearError(xMinLabel); clearError(xMaxLabel); return true; } private boolean validNumbers(float l, float r, @NonNull TextInputLayout lInput, @NonNull TextInputLayout rInput) { final boolean nanLeft = Float.isNaN(l); final boolean nanRight = Float.isNaN(r); if (nanLeft || nanRight) { if (nanLeft) { setError(lInput, R.string.cpp_nan); } else { clearError(lInput); } if (nanRight) { setError(rInput, R.string.cpp_nan); } else { clearError(rInput); } return true; } return false; } @NonNull private RectF collectData() { return new RectF(getDimension(xMin), getDimension(yMin), getDimension(xMax), getDimension(yMax)); } private void applyData() { final RectF bounds = collectData(); Plot.setGraphBounds(null, plotter, bounds, d3); } private float getDimension(@NonNull EditText view) { try { return Float.parseFloat(view.getText().toString().replace(",", ".").replace("−", "-")); } catch (NumberFormatException e) { return Float.NaN; } } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_ACTION_DONE) { tryClose(); return true; } return false; } }