From 0170c3bb6a530dc667962ec888871f84c5de378a Mon Sep 17 00:00:00 2001 From: serso Date: Sat, 30 Jan 2016 21:12:24 +0100 Subject: [PATCH] Variable validation --- .../android/calculator/AppComponent.java | 7 +- .../calculator/BaseDialogFragment.java | 10 +- .../solovyev/android/calculator/Engine.java | 22 ++-- .../functions/EditFunctionFragment.java | 8 +- .../keyboard/FloatingKeyboardWindow.java | 10 ++ .../variables/EditVariableFragment.java | 107 +++++++++++++----- .../variables/GreekFloatingKeyboard.java | 8 +- .../variables/VariablesFragment.java | 2 +- .../calculator/view/EditTextCompat.java | 16 +-- .../res/layout/fragment_function_edit.xml | 3 +- .../res/layout/fragment_variable_edit.xml | 9 +- 11 files changed, 130 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/org/solovyev/android/calculator/AppComponent.java b/app/src/main/java/org/solovyev/android/calculator/AppComponent.java index d994dfde..fa283910 100644 --- a/app/src/main/java/org/solovyev/android/calculator/AppComponent.java +++ b/app/src/main/java/org/solovyev/android/calculator/AppComponent.java @@ -1,16 +1,16 @@ package org.solovyev.android.calculator; +import dagger.Component; import org.solovyev.android.calculator.functions.EditFunctionFragment; import org.solovyev.android.calculator.history.BaseHistoryFragment; import org.solovyev.android.calculator.history.EditHistoryFragment; import org.solovyev.android.calculator.math.edit.FunctionsFragment; -import org.solovyev.android.calculator.variables.VariablesFragment; import org.solovyev.android.calculator.onscreen.CalculatorOnscreenService; +import org.solovyev.android.calculator.variables.EditVariableFragment; +import org.solovyev.android.calculator.variables.VariablesFragment; import javax.inject.Singleton; -import dagger.Component; - @Singleton @Component(modules = AppModule.class) public interface AppComponent { @@ -21,6 +21,7 @@ public interface AppComponent { void inject(BaseHistoryFragment fragment); void inject(BaseDialogFragment fragment); void inject(EditFunctionFragment fragment); + void inject(EditVariableFragment fragment); void inject(EditHistoryFragment fragment); void inject(FunctionsFragment fragment); void inject(VariablesFragment fragment); diff --git a/app/src/main/java/org/solovyev/android/calculator/BaseDialogFragment.java b/app/src/main/java/org/solovyev/android/calculator/BaseDialogFragment.java index 8d4223ad..89ebbb3e 100644 --- a/app/src/main/java/org/solovyev/android/calculator/BaseDialogFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/BaseDialogFragment.java @@ -6,14 +6,13 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.support.design.widget.TextInputLayout; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; -import android.view.Window; -import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import javax.inject.Inject; @@ -62,12 +61,15 @@ public abstract class BaseDialogFragment extends DialogFragment { @NonNull protected abstract View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState); - protected static void setError(@NonNull TextInputLayout textInput, @NonNull String error) { + protected void setError(@NonNull TextInputLayout textInput, @StringRes int error, Object... errorArgs) { + setError(textInput, getString(error, errorArgs)); + } + protected void setError(@NonNull TextInputLayout textInput, @NonNull String error) { textInput.setError(error); textInput.setErrorEnabled(true); } - protected static void clearError(@NonNull TextInputLayout textInput) { + protected void clearError(@NonNull TextInputLayout textInput) { textInput.setError(null); textInput.setErrorEnabled(false); } diff --git a/app/src/main/java/org/solovyev/android/calculator/Engine.java b/app/src/main/java/org/solovyev/android/calculator/Engine.java index e6e86eba..227ed75e 100644 --- a/app/src/main/java/org/solovyev/android/calculator/Engine.java +++ b/app/src/main/java/org/solovyev/android/calculator/Engine.java @@ -23,9 +23,12 @@ package org.solovyev.android.calculator; import android.content.SharedPreferences; - import com.squareup.otto.Bus; - +import jscl.AngleUnit; +import jscl.JsclMathEngine; +import jscl.MathEngine; +import jscl.NumeralBase; +import jscl.math.operator.Operator; import org.solovyev.android.Check; import org.solovyev.android.calculator.functions.FunctionsRegistry; import org.solovyev.android.prefs.BooleanPreference; @@ -36,21 +39,14 @@ import org.solovyev.common.text.EnumMapper; import org.solovyev.common.text.NumberMapper; import org.solovyev.common.text.Strings; +import javax.annotation.Nonnull; +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; -import javax.annotation.Nonnull; -import javax.inject.Inject; -import javax.inject.Singleton; - -import jscl.AngleUnit; -import jscl.JsclMathEngine; -import jscl.MathEngine; -import jscl.NumeralBase; -import jscl.math.operator.Operator; - @Singleton public class Engine implements SharedPreferences.OnSharedPreferenceChangeListener { @@ -82,7 +78,7 @@ public class Engine implements SharedPreferences.OnSharedPreferenceChangeListene } @Inject - public Engine(@Nonnull SharedPreferences preferences, @Nonnull JsclMathEngine mathEngine) { + public Engine(@Nonnull JsclMathEngine mathEngine) { this.mathEngine = mathEngine; this.mathEngine.setRoundResult(true); diff --git a/app/src/main/java/org/solovyev/android/calculator/functions/EditFunctionFragment.java b/app/src/main/java/org/solovyev/android/calculator/functions/EditFunctionFragment.java index 32986d18..0c32a7a3 100644 --- a/app/src/main/java/org/solovyev/android/calculator/functions/EditFunctionFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/functions/EditFunctionFragment.java @@ -65,9 +65,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements View.OnC private static final int MENU_CATEGORY = Menu.FIRST + 2; @NonNull - private final VariablesRegistry constantsRegistry = Locator.getInstance().getEngine().getVariablesRegistry(); - @NonNull - private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(); + private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(null); @NonNull private final KeyboardUser keyboardUser = new KeyboardUser(); @Bind(R.id.function_params) @@ -86,6 +84,8 @@ public class EditFunctionFragment extends BaseDialogFragment implements View.OnC Calculator calculator; @Inject FunctionsRegistry functionsRegistry; + @Inject + VariablesRegistry variablesRegistry; @Nullable private CppFunction function; @@ -453,7 +453,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements View.OnC final int id = v.getId(); if (id == R.id.function_body) { menu.clear(); - addEntities(menu, getNamesSorted(constantsRegistry), MENU_CONSTANT); + addEntities(menu, getNamesSorted(variablesRegistry), MENU_CONSTANT); unregisterForContextMenu(bodyView); } } diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/FloatingKeyboardWindow.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/FloatingKeyboardWindow.java index 7966e328..b49c24e5 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/FloatingKeyboardWindow.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/FloatingKeyboardWindow.java @@ -19,11 +19,18 @@ import org.solovyev.android.calculator.R; public class FloatingKeyboardWindow { + @javax.annotation.Nullable + private final PopupWindow.OnDismissListener dismissListener; + @Nullable private PopupWindow window; @Nullable private Dialog dialog; + public FloatingKeyboardWindow(@javax.annotation.Nullable PopupWindow.OnDismissListener dismissListener) { + this.dismissListener = dismissListener; + } + private static void hideIme(@NonNull View view) { final IBinder token = view.getWindowToken(); if (token != null) { @@ -67,6 +74,9 @@ public class FloatingKeyboardWindow { @Override public void onDismiss() { window = null; + if (dismissListener != null) { + dismissListener.onDismiss(); + } } }); // see http://stackoverflow.com/a/4713487/720489 diff --git a/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java b/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java index 80decdad..932ffd44 100644 --- a/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/variables/EditVariableFragment.java @@ -31,8 +31,6 @@ import android.support.design.widget.TextInputLayout; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; -import android.text.Editable; -import android.text.TextWatcher; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -40,19 +38,24 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; -import android.widget.Toast; +import android.widget.PopupWindow; import butterknife.Bind; import butterknife.ButterKnife; +import jscl.math.function.IConstant; import org.solovyev.android.Activities; import org.solovyev.android.Check; import org.solovyev.android.calculator.*; +import org.solovyev.android.calculator.functions.FunctionsRegistry; import org.solovyev.android.calculator.keyboard.FloatingKeyboard; import org.solovyev.android.calculator.keyboard.FloatingKeyboardWindow; +import org.solovyev.android.calculator.math.MathType; +import org.solovyev.android.calculator.math.edit.VarEditorSaver; import org.solovyev.android.calculator.view.EditTextCompat; import org.solovyev.common.text.Strings; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.inject.Inject; import java.util.Arrays; import java.util.List; @@ -61,10 +64,14 @@ import static org.solovyev.android.calculator.functions.CppFunction.NO_ID; public class EditVariableFragment extends BaseDialogFragment implements CalculatorEventListener, View.OnFocusChangeListener, View.OnKeyListener, View.OnClickListener { private static final String ARG_VARIABLE = "variable"; - private final static String greekAlphabet = "αβγδεζηθικλμνξοπρστυφχψω"; - private final static List acceptableChars = Arrays.asList(Strings.toObjects(("1234567890abcdefghijklmnopqrstuvwxyzйцукенгшщзхъфывапролджэячсмитьбюё_" + greekAlphabet).toCharArray())); + private final static List ACCEPTABLE_CHARACTERS = Arrays.asList(Strings.toObjects(("1234567890abcdefghijklmnopqrstuvwxyzйцукенгшщзхъфывапролджэячсмитьбюё_" + GreekFloatingKeyboard.ALPHABET).toCharArray())); @NonNull - private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(); + private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + nameView.setShowSoftInputOnFocusCompat(true); + } + }); @NonNull private final KeyboardUser keyboardUser = new KeyboardUser(); @Bind(R.id.variable_name_label) @@ -79,6 +86,12 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat EditText valueView; @Bind(R.id.variable_description) EditText descriptionView; + @Inject + Calculator calculator; + @Inject + FunctionsRegistry functionsRegistry; + @Inject + VariablesRegistry variablesRegistry; @Nullable private CppVariable variable; @@ -124,6 +137,12 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat } } + @Override + protected void inject(@NonNull AppComponent component) { + super.inject(component); + component.inject(this); + } + @Override protected void onPrepareDialog(@NonNull AlertDialog.Builder builder) { builder.setNegativeButton(R.string.c_cancel, null); @@ -189,13 +208,61 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat } private boolean validateValue() { - return false; + final String value = valueView.getText().toString(); + if (!Strings.isEmpty(value)) { + // value is not empty => must be a number + if (!VariablesFragment.isValidValue(value)) { + setError(valueLabel, R.string.c_value_is_not_a_number); + return false; + } + } + + clearError(valueLabel); + return true; } private boolean validateName() { - return false; - } + final String name = nameView.getText().toString(); + if (!VarEditorSaver.isValidName(name)) { + setError(nameLabel, getString(R.string.c_name_is_not_valid)); + return false; + } + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + if (!ACCEPTABLE_CHARACTERS.contains(Character.toLowerCase(c))) { + setError(nameLabel, getString(R.string.c_char_is_not_accepted, c)); + return false; + } + } + final IConstant existingVariable = variablesRegistry.get(name); + if (existingVariable != null) { + if (!existingVariable.isIdDefined()) { + Check.shouldNotHappen(); + setError(nameLabel, getString(R.string.c_var_already_exists)); + return false; + } + if (isNewVariable()) { + // trying to create a new variable with existing name + setError(nameLabel, getString(R.string.c_var_already_exists)); + return false; + } + Check.isNotNull(variable); + if (!existingVariable.getId().equals(variable.id)) { + // trying to change the name of existing variable to some other variable's name + setError(nameLabel, getString(R.string.c_var_already_exists)); + return false; + } + } + final MathType.Result type = MathType.getType(name, 0, false); + if (type.type != MathType.text && type.type != MathType.constant) { + setError(nameLabel, getString(R.string.c_var_name_clashes)); + return false; + } + + clearError(nameLabel); + return true; + } @Override public void onResume() { @@ -295,28 +362,6 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat keyboardWindow.show(new GreekFloatingKeyboard(keyboardUser), getDialog()); } - private class NameWatcher implements TextWatcher { - - @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) { - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (!acceptableChars.contains(Character.toLowerCase(c))) { - s.delete(i, i + 1); - Toast.makeText(getActivity(), String.format(getString(R.string.c_char_is_not_accepted), c), Toast.LENGTH_SHORT).show(); - } - } - } - } - private class KeyboardUser implements FloatingKeyboard.User { @NonNull @Override diff --git a/app/src/main/java/org/solovyev/android/calculator/variables/GreekFloatingKeyboard.java b/app/src/main/java/org/solovyev/android/calculator/variables/GreekFloatingKeyboard.java index 62515e36..e47e09af 100644 --- a/app/src/main/java/org/solovyev/android/calculator/variables/GreekFloatingKeyboard.java +++ b/app/src/main/java/org/solovyev/android/calculator/variables/GreekFloatingKeyboard.java @@ -19,7 +19,7 @@ import java.util.Locale; public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View.OnClickListener { - private final static String GREEK_ALPHABET = "αβγδεζηθικλμνξοπρστυφχψω"; + final static String ALPHABET = "αβγδεζηθικλμνξοπρστυφχψω"; public GreekFloatingKeyboard(@NonNull User user) { super(user); @@ -41,8 +41,8 @@ public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View. } else { makeLastColumnLand(rowView, row); } - } else if (letter < GREEK_ALPHABET.length()) { - final Button button = addButton(rowView, View.NO_ID, String.valueOf(GREEK_ALPHABET.charAt(letter))); + } else if (letter < ALPHABET.length()) { + final Button button = addButton(rowView, View.NO_ID, String.valueOf(ALPHABET.charAt(letter))); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { fixCapitalization(button); } @@ -146,7 +146,7 @@ public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View. @Override public void process(@Nonnull Button key) { final String letter = key.getText().toString(); - if (!GREEK_ALPHABET.contains(letter.toLowerCase(Locale.US))) { + if (!ALPHABET.contains(letter.toLowerCase(Locale.US))) { return; } if (upperCase) { diff --git a/app/src/main/java/org/solovyev/android/calculator/variables/VariablesFragment.java b/app/src/main/java/org/solovyev/android/calculator/variables/VariablesFragment.java index d03864b6..d86eff86 100644 --- a/app/src/main/java/org/solovyev/android/calculator/variables/VariablesFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/variables/VariablesFragment.java @@ -66,7 +66,7 @@ public class VariablesFragment extends BaseEntitiesFragment implement final List constants = expression.getUndefinedVars(); return constants.isEmpty(); } catch (RuntimeException e) { - return true; + return false; } } diff --git a/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java b/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java index ed7da7fe..526adaa4 100644 --- a/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java +++ b/app/src/main/java/org/solovyev/android/calculator/view/EditTextCompat.java @@ -7,12 +7,10 @@ import android.text.InputType; import android.util.AttributeSet; import android.util.Log; import android.widget.EditText; - import org.solovyev.android.Check; -import java.lang.reflect.Method; - import javax.annotation.Nullable; +import java.lang.reflect.Method; public class EditTextCompat extends EditText { @@ -38,23 +36,27 @@ public class EditTextCompat extends EditText { } public void dontShowSoftInputOnFocusCompat() { + setShowSoftInputOnFocusCompat(false); + } + + public void setShowSoftInputOnFocusCompat(boolean show) { Check.isMainThread(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - setShowSoftInputOnFocus(false); + setShowSoftInputOnFocus(show); } else { - dontShowSoftInputOnFocusPreLollipop(); + dontShowSoftInputOnFocusPreLollipop(show); } } - private void dontShowSoftInputOnFocusPreLollipop() { + private void dontShowSoftInputOnFocusPreLollipop(boolean show) { final Method method = getSetShowSoftInputOnFocusMethod(); if (method == null) { disableSoftInputFromAppearing(); return; } try { - method.invoke(this, false); + method.invoke(this, show); } catch (Exception e) { Log.w("EditTextCompat", e.getMessage(), e); } diff --git a/app/src/main/res/layout/fragment_function_edit.xml b/app/src/main/res/layout/fragment_function_edit.xml index 81a19de8..dc8303cf 100644 --- a/app/src/main/res/layout/fragment_function_edit.xml +++ b/app/src/main/res/layout/fragment_function_edit.xml @@ -73,7 +73,8 @@ a:layout_width="match_parent" a:layout_height="wrap_content" a:hint="@string/c_function_description" - a:inputType="text" /> + a:inputType="textMultiLine" + a:maxLines="4" /> diff --git a/app/src/main/res/layout/fragment_variable_edit.xml b/app/src/main/res/layout/fragment_variable_edit.xml index 7516a248..7189a88e 100644 --- a/app/src/main/res/layout/fragment_variable_edit.xml +++ b/app/src/main/res/layout/fragment_variable_edit.xml @@ -55,12 +55,12 @@ a:id="@+id/variable_keyboard_button" a:layout_width="wrap_content" a:layout_height="wrap_content" - a:layout_gravity="end|center_vertical" + a:layout_gravity="end|top" a:background="?attr/selectableItemBackgroundBorderless" + a:minWidth="0dp" + a:padding="@dimen/cpp_image_button_padding" a:text="@string/cpp_show_greek_keyboard" a:textAllCaps="false" - a:padding="@dimen/cpp_image_button_padding" - a:minWidth="0dp" a:textAppearance="?android:attr/textAppearanceSmall" tools:ignore="UnusedAttribute" /> @@ -87,7 +87,8 @@ a:layout_width="match_parent" a:layout_height="wrap_content" a:hint="@string/c_var_description" - a:inputType="text" /> + a:inputType="textMultiLine" + a:maxLines="4" />