Variable validation

This commit is contained in:
serso 2016-01-30 21:12:24 +01:00
parent 4a9e8bf78a
commit 0170c3bb6a
11 changed files with 130 additions and 72 deletions

View File

@ -1,16 +1,16 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import dagger.Component;
import org.solovyev.android.calculator.functions.EditFunctionFragment; import org.solovyev.android.calculator.functions.EditFunctionFragment;
import org.solovyev.android.calculator.history.BaseHistoryFragment; import org.solovyev.android.calculator.history.BaseHistoryFragment;
import org.solovyev.android.calculator.history.EditHistoryFragment; import org.solovyev.android.calculator.history.EditHistoryFragment;
import org.solovyev.android.calculator.math.edit.FunctionsFragment; 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.onscreen.CalculatorOnscreenService;
import org.solovyev.android.calculator.variables.EditVariableFragment;
import org.solovyev.android.calculator.variables.VariablesFragment;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Component;
@Singleton @Singleton
@Component(modules = AppModule.class) @Component(modules = AppModule.class)
public interface AppComponent { public interface AppComponent {
@ -21,6 +21,7 @@ public interface AppComponent {
void inject(BaseHistoryFragment fragment); void inject(BaseHistoryFragment fragment);
void inject(BaseDialogFragment fragment); void inject(BaseDialogFragment fragment);
void inject(EditFunctionFragment fragment); void inject(EditFunctionFragment fragment);
void inject(EditVariableFragment fragment);
void inject(EditHistoryFragment fragment); void inject(EditHistoryFragment fragment);
void inject(FunctionsFragment fragment); void inject(FunctionsFragment fragment);
void inject(VariablesFragment fragment); void inject(VariablesFragment fragment);

View File

@ -6,14 +6,13 @@ import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import javax.inject.Inject; import javax.inject.Inject;
@ -62,12 +61,15 @@ public abstract class BaseDialogFragment extends DialogFragment {
@NonNull @NonNull
protected abstract View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState); 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.setError(error);
textInput.setErrorEnabled(true); textInput.setErrorEnabled(true);
} }
protected static void clearError(@NonNull TextInputLayout textInput) { protected void clearError(@NonNull TextInputLayout textInput) {
textInput.setError(null); textInput.setError(null);
textInput.setErrorEnabled(false); textInput.setErrorEnabled(false);
} }

View File

@ -23,9 +23,12 @@
package org.solovyev.android.calculator; package org.solovyev.android.calculator;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import com.squareup.otto.Bus; 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.Check;
import org.solovyev.android.calculator.functions.FunctionsRegistry; import org.solovyev.android.calculator.functions.FunctionsRegistry;
import org.solovyev.android.prefs.BooleanPreference; 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.NumberMapper;
import org.solovyev.common.text.Strings; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; 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 @Singleton
public class Engine implements SharedPreferences.OnSharedPreferenceChangeListener { public class Engine implements SharedPreferences.OnSharedPreferenceChangeListener {
@ -82,7 +78,7 @@ public class Engine implements SharedPreferences.OnSharedPreferenceChangeListene
} }
@Inject @Inject
public Engine(@Nonnull SharedPreferences preferences, @Nonnull JsclMathEngine mathEngine) { public Engine(@Nonnull JsclMathEngine mathEngine) {
this.mathEngine = mathEngine; this.mathEngine = mathEngine;
this.mathEngine.setRoundResult(true); this.mathEngine.setRoundResult(true);

View File

@ -65,9 +65,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements View.OnC
private static final int MENU_CATEGORY = Menu.FIRST + 2; private static final int MENU_CATEGORY = Menu.FIRST + 2;
@NonNull @NonNull
private final VariablesRegistry constantsRegistry = Locator.getInstance().getEngine().getVariablesRegistry(); private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(null);
@NonNull
private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow();
@NonNull @NonNull
private final KeyboardUser keyboardUser = new KeyboardUser(); private final KeyboardUser keyboardUser = new KeyboardUser();
@Bind(R.id.function_params) @Bind(R.id.function_params)
@ -86,6 +84,8 @@ public class EditFunctionFragment extends BaseDialogFragment implements View.OnC
Calculator calculator; Calculator calculator;
@Inject @Inject
FunctionsRegistry functionsRegistry; FunctionsRegistry functionsRegistry;
@Inject
VariablesRegistry variablesRegistry;
@Nullable @Nullable
private CppFunction function; private CppFunction function;
@ -453,7 +453,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements View.OnC
final int id = v.getId(); final int id = v.getId();
if (id == R.id.function_body) { if (id == R.id.function_body) {
menu.clear(); menu.clear();
addEntities(menu, getNamesSorted(constantsRegistry), MENU_CONSTANT); addEntities(menu, getNamesSorted(variablesRegistry), MENU_CONSTANT);
unregisterForContextMenu(bodyView); unregisterForContextMenu(bodyView);
} }
} }

View File

@ -19,11 +19,18 @@ import org.solovyev.android.calculator.R;
public class FloatingKeyboardWindow { public class FloatingKeyboardWindow {
@javax.annotation.Nullable
private final PopupWindow.OnDismissListener dismissListener;
@Nullable @Nullable
private PopupWindow window; private PopupWindow window;
@Nullable @Nullable
private Dialog dialog; private Dialog dialog;
public FloatingKeyboardWindow(@javax.annotation.Nullable PopupWindow.OnDismissListener dismissListener) {
this.dismissListener = dismissListener;
}
private static void hideIme(@NonNull View view) { private static void hideIme(@NonNull View view) {
final IBinder token = view.getWindowToken(); final IBinder token = view.getWindowToken();
if (token != null) { if (token != null) {
@ -67,6 +74,9 @@ public class FloatingKeyboardWindow {
@Override @Override
public void onDismiss() { public void onDismiss() {
window = null; window = null;
if (dismissListener != null) {
dismissListener.onDismiss();
}
} }
}); });
// see http://stackoverflow.com/a/4713487/720489 // see http://stackoverflow.com/a/4713487/720489

View File

@ -31,8 +31,6 @@ import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -40,19 +38,24 @@ import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast; import android.widget.PopupWindow;
import butterknife.Bind; import butterknife.Bind;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import jscl.math.function.IConstant;
import org.solovyev.android.Activities; import org.solovyev.android.Activities;
import org.solovyev.android.Check; import org.solovyev.android.Check;
import org.solovyev.android.calculator.*; 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.FloatingKeyboard;
import org.solovyev.android.calculator.keyboard.FloatingKeyboardWindow; 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.android.calculator.view.EditTextCompat;
import org.solovyev.common.text.Strings; import org.solovyev.common.text.Strings;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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 { public class EditVariableFragment extends BaseDialogFragment implements CalculatorEventListener, View.OnFocusChangeListener, View.OnKeyListener, View.OnClickListener {
private static final String ARG_VARIABLE = "variable"; private static final String ARG_VARIABLE = "variable";
private final static String greekAlphabet = "αβγδεζηθικλμνξοπρστυφχψω"; private final static List<Character> ACCEPTABLE_CHARACTERS = Arrays.asList(Strings.toObjects(("1234567890abcdefghijklmnopqrstuvwxyzйцукенгшщзхъфывапролджэячсмитьбюё_" + GreekFloatingKeyboard.ALPHABET).toCharArray()));
private final static List<Character> acceptableChars = Arrays.asList(Strings.toObjects(("1234567890abcdefghijklmnopqrstuvwxyzйцукенгшщзхъфывапролджэячсмитьбюё_" + greekAlphabet).toCharArray()));
@NonNull @NonNull
private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(); private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
nameView.setShowSoftInputOnFocusCompat(true);
}
});
@NonNull @NonNull
private final KeyboardUser keyboardUser = new KeyboardUser(); private final KeyboardUser keyboardUser = new KeyboardUser();
@Bind(R.id.variable_name_label) @Bind(R.id.variable_name_label)
@ -79,6 +86,12 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat
EditText valueView; EditText valueView;
@Bind(R.id.variable_description) @Bind(R.id.variable_description)
EditText descriptionView; EditText descriptionView;
@Inject
Calculator calculator;
@Inject
FunctionsRegistry functionsRegistry;
@Inject
VariablesRegistry variablesRegistry;
@Nullable @Nullable
private CppVariable variable; 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 @Override
protected void onPrepareDialog(@NonNull AlertDialog.Builder builder) { protected void onPrepareDialog(@NonNull AlertDialog.Builder builder) {
builder.setNegativeButton(R.string.c_cancel, null); builder.setNegativeButton(R.string.c_cancel, null);
@ -189,13 +208,61 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat
} }
private boolean validateValue() { private boolean validateValue() {
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; return false;
} }
}
clearError(valueLabel);
return true;
}
private boolean validateName() { private boolean validateName() {
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; return false;
} }
clearError(nameLabel);
return true;
}
@Override @Override
public void onResume() { public void onResume() {
@ -295,28 +362,6 @@ public class EditVariableFragment extends BaseDialogFragment implements Calculat
keyboardWindow.show(new GreekFloatingKeyboard(keyboardUser), getDialog()); 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 { private class KeyboardUser implements FloatingKeyboard.User {
@NonNull @NonNull
@Override @Override

View File

@ -19,7 +19,7 @@ import java.util.Locale;
public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View.OnClickListener { public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View.OnClickListener {
private final static String GREEK_ALPHABET = "αβγδεζηθικλμνξοπρστυφχψω"; final static String ALPHABET = "αβγδεζηθικλμνξοπρστυφχψω";
public GreekFloatingKeyboard(@NonNull User user) { public GreekFloatingKeyboard(@NonNull User user) {
super(user); super(user);
@ -41,8 +41,8 @@ public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View.
} else { } else {
makeLastColumnLand(rowView, row); makeLastColumnLand(rowView, row);
} }
} else if (letter < GREEK_ALPHABET.length()) { } else if (letter < ALPHABET.length()) {
final Button button = addButton(rowView, View.NO_ID, String.valueOf(GREEK_ALPHABET.charAt(letter))); final Button button = addButton(rowView, View.NO_ID, String.valueOf(ALPHABET.charAt(letter)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
fixCapitalization(button); fixCapitalization(button);
} }
@ -146,7 +146,7 @@ public class GreekFloatingKeyboard extends BaseFloatingKeyboard implements View.
@Override @Override
public void process(@Nonnull Button key) { public void process(@Nonnull Button key) {
final String letter = key.getText().toString(); final String letter = key.getText().toString();
if (!GREEK_ALPHABET.contains(letter.toLowerCase(Locale.US))) { if (!ALPHABET.contains(letter.toLowerCase(Locale.US))) {
return; return;
} }
if (upperCase) { if (upperCase) {

View File

@ -66,7 +66,7 @@ public class VariablesFragment extends BaseEntitiesFragment<IConstant> implement
final List<IConstant> constants = expression.getUndefinedVars(); final List<IConstant> constants = expression.getUndefinedVars();
return constants.isEmpty(); return constants.isEmpty();
} catch (RuntimeException e) { } catch (RuntimeException e) {
return true; return false;
} }
} }

View File

@ -7,12 +7,10 @@ import android.text.InputType;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.widget.EditText; import android.widget.EditText;
import org.solovyev.android.Check; import org.solovyev.android.Check;
import java.lang.reflect.Method;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Method;
public class EditTextCompat extends EditText { public class EditTextCompat extends EditText {
@ -38,23 +36,27 @@ public class EditTextCompat extends EditText {
} }
public void dontShowSoftInputOnFocusCompat() { public void dontShowSoftInputOnFocusCompat() {
setShowSoftInputOnFocusCompat(false);
}
public void setShowSoftInputOnFocusCompat(boolean show) {
Check.isMainThread(); Check.isMainThread();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setShowSoftInputOnFocus(false); setShowSoftInputOnFocus(show);
} else { } else {
dontShowSoftInputOnFocusPreLollipop(); dontShowSoftInputOnFocusPreLollipop(show);
} }
} }
private void dontShowSoftInputOnFocusPreLollipop() { private void dontShowSoftInputOnFocusPreLollipop(boolean show) {
final Method method = getSetShowSoftInputOnFocusMethod(); final Method method = getSetShowSoftInputOnFocusMethod();
if (method == null) { if (method == null) {
disableSoftInputFromAppearing(); disableSoftInputFromAppearing();
return; return;
} }
try { try {
method.invoke(this, false); method.invoke(this, show);
} catch (Exception e) { } catch (Exception e) {
Log.w("EditTextCompat", e.getMessage(), e); Log.w("EditTextCompat", e.getMessage(), e);
} }

View File

@ -73,7 +73,8 @@
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:hint="@string/c_function_description" a:hint="@string/c_function_description"
a:inputType="text" /> a:inputType="textMultiLine"
a:maxLines="4" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
</LinearLayout> </LinearLayout>

View File

@ -55,12 +55,12 @@
a:id="@+id/variable_keyboard_button" a:id="@+id/variable_keyboard_button"
a:layout_width="wrap_content" a:layout_width="wrap_content"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_gravity="end|center_vertical" a:layout_gravity="end|top"
a:background="?attr/selectableItemBackgroundBorderless" a:background="?attr/selectableItemBackgroundBorderless"
a:minWidth="0dp"
a:padding="@dimen/cpp_image_button_padding"
a:text="@string/cpp_show_greek_keyboard" a:text="@string/cpp_show_greek_keyboard"
a:textAllCaps="false" a:textAllCaps="false"
a:padding="@dimen/cpp_image_button_padding"
a:minWidth="0dp"
a:textAppearance="?android:attr/textAppearanceSmall" a:textAppearance="?android:attr/textAppearanceSmall"
tools:ignore="UnusedAttribute" /> tools:ignore="UnusedAttribute" />
</FrameLayout> </FrameLayout>
@ -87,7 +87,8 @@
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:hint="@string/c_var_description" a:hint="@string/c_var_description"
a:inputType="text" /> a:inputType="textMultiLine"
a:maxLines="4" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
</LinearLayout> </LinearLayout>