diff --git a/app/src/main/java/org/solovyev/android/calculator/KeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/KeyboardUi.java new file mode 100644 index 00000000..3c387b00 --- /dev/null +++ b/app/src/main/java/org/solovyev/android/calculator/KeyboardUi.java @@ -0,0 +1,408 @@ +package org.solovyev.android.calculator; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.PointF; +import android.os.Build; +import android.support.annotation.DrawableRes; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import org.solovyev.android.views.dragbutton.DirectionDragButton; +import org.solovyev.android.views.dragbutton.DragButton; +import org.solovyev.android.views.dragbutton.DragDirection; +import org.solovyev.android.views.dragbutton.SimpleDragListener; + +import java.util.List; + +import static org.solovyev.android.views.dragbutton.DirectionDragButton.Direction.down; +import static org.solovyev.android.views.dragbutton.DirectionDragButton.Direction.up; + + +public class KeyboardUi { + @NonNull + private final ButtonHandler buttonHandler = new ButtonHandler(); + @NonNull + private final User user; + @NonNull + private final List parameterNames; + @NonNull + private final SimpleDragListener dragListener; + private final int textColor; + private final int textColorSecondary; + private final int sidePadding; + + @SuppressWarnings("deprecation") + public KeyboardUi(@NonNull User user, @NonNull List parameterNames) { + this.user = user; + this.parameterNames = parameterNames; + this.dragListener = new SimpleDragListener(buttonHandler, user.getContext()); + final Resources resources = user.getResources(); + textColor = resources.getColor(R.color.cpp_button_text); + textColorSecondary = resources.getColor(R.color.cpp_button_text); + sidePadding = resources.getDimensionPixelSize(R.dimen.cpp_button_padding); + } + + public void makeView() { + LinearLayout row = makeRow(); + addButton(row, 0, "7"); + addButton(row, 0, "8"); + addButton(row, 0, "9").setText("π", up).setText("e", down); + addOperationButton(row, R.id.cpp_kb_button_multiply, Locator.getInstance().getEngine().getMultiplicationSign()).setText("^n", up).setText("^2", down); + addButton(row, R.id.cpp_kb_button_clear, "C"); + + row = makeRow(); + addButton(row, 0, "4"); + addButton(row, 0, "5"); + addButton(row, 0, "6"); + addOperationButton(row, R.id.cpp_kb_button_divide, "/").setText("%", up).setText("sqrt", down); + final View backspace = addImageButton(row, R.id.cpp_kb_button_backspace, R.drawable.ic_backspace_white_24dp); + LongClickEraser.createAndAttach(backspace, user.getEditor()); + + row = makeRow(); + addButton(row, 0, "1"); + addButton(row, 0, "2"); + addButton(row, 0, "3"); + addOperationButton(row, R.id.cpp_kb_button_plus, "+"); + addImageButton(row, R.id.cpp_kb_button_space, R.drawable.ic_space_bar_white_24dp); + + row = makeRow(); + addButton(row, R.id.cpp_kb_button_brackets, "( )").setText("(", up).setText(")", down); + addButton(row, 0, "0").setText("00", up).setText("000", down); + addButton(row, 0, ".").setText(",", up); + addOperationButton(row, R.id.cpp_kb_button_minus, "−"); + addImageButton(row, R.id.cpp_kb_button_keyboard, R.drawable.ic_keyboard_white_24dp); + + row = makeRow(); + final int parametersCount = parameterNames.size(); + addButton(row, 0, parametersCount > 0 ? parameterNames.get(0) : "x"); + addButton(row, 0, parametersCount > 1 ? parameterNames.get(1) : "y"); + addButton(row, R.id.cpp_kb_button_functions, "f(x)"); + addButton(row, R.id.cpp_kb_button_constants, "π"); + addImageButton(row, R.id.cpp_kb_button_close, R.drawable.ic_done_white_24dp); + } + + @NonNull + private View addImageButton(@NonNull LinearLayout row, @IdRes int id, @DrawableRes int icon) { + final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT); + lp.weight = 1f; + final View view = makeImageButton(id, icon); + row.addView(view, lp); + return view; + } + + @NonNull + private DirectionDragButton addOperationButton(@NonNull LinearLayout row, @IdRes int id, @NonNull String text) { + final DirectionDragButton button = addButton(row, id, text); + button.setBackgroundResource(R.drawable.material_button_light_primary); + button.setTextColor(Color.WHITE); + button.setDirectionTextColor(Color.WHITE); + return button; + } + + @NonNull + private DirectionDragButton addButton(@NonNull LinearLayout row, @IdRes int id, @NonNull String text) { + final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT); + lp.weight = 1f; + final DirectionDragButton view = makeButton(id, text); + row.addView(view, lp); + return view; + } + + @NonNull + private LinearLayout makeRow() { + final LinearLayout row = new LinearLayout(user.getContext()); + row.setOrientation(LinearLayout.HORIZONTAL); + final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + lp.weight = 1f; + user.getKeyboard().addView(row, lp); + return row; + } + + @NonNull + private DirectionDragButton makeButton(@IdRes int id, @NonNull String text) { + final DirectionDragButton button = new DirectionDragButton(user.getContext()); + fillButton(button, id); + button.setText(text); + button.setTextColor(textColor); + button.setDirectionTextColor(textColorSecondary); + button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24); + button.setOnDragListener(dragListener); + return button; + } + + private void fillButton(@NonNull View button, @IdRes int id) { + button.setOnClickListener(buttonHandler); + button.setId(id); + button.setBackgroundResource(R.drawable.material_button_light); + button.setPadding(sidePadding, 1, sidePadding, 1); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + button.setStateListAnimator(null); + } + } + + @NonNull + private View makeImageButton(@IdRes int id, @DrawableRes int icon) { + final ImageButton button = new ImageButton(user.getContext()); + fillButton(button, id); + button.setImageResource(icon); + button.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + return button; + } + + public interface User { + @NonNull + Context getContext(); + + @NonNull + Resources getResources(); + + @NonNull + EditText getEditor(); + + @NonNull + ViewGroup getKeyboard(); + + void insertOperator(char operator); + + void insertOperator(@NonNull String operator); + + void showFunctions(@NonNull View v); + + void showConstants(@NonNull View v); + + void insertText(@NonNull CharSequence text, int offset); + + void done(); + + void showIme(); + } + + private class ButtonHandler implements View.OnClickListener, SimpleDragListener.DragProcessor { + @Override + public void onClick(@NonNull View v) { + switch (v.getId()) { + case R.id.cpp_kb_button_divide: + user.insertOperator('/'); + break; + case R.id.cpp_kb_button_plus: + user.insertOperator('+'); + break; + case R.id.cpp_kb_button_minus: + user.insertOperator('-'); + break; + case R.id.cpp_kb_button_multiply: + user.insertOperator('*'); + break; + case R.id.cpp_kb_button_functions: + user.showFunctions(v); + break; + case R.id.cpp_kb_button_constants: + user.showConstants(v); + break; + case R.id.cpp_kb_button_space: + user.insertText(" ", 0); + break; + case R.id.cpp_kb_button_keyboard: + user.showIme(); + break; + case R.id.cpp_kb_button_clear: + user.getEditor().setText(""); + user.getEditor().setSelection(0); + break; + case R.id.cpp_kb_button_brackets: + user.insertText("()", -1); + break; + case R.id.cpp_kb_button_close: + user.done(); + break; + default: + onDefaultClick(v); + break; + } + user.getEditor().requestFocus(); + v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + } + + private void onDefaultClick(@NonNull View v) { + user.insertText(((Button) v).getText(), 0); + } + + @Override + public boolean processDragEvent(@NonNull DragDirection direction, @NonNull DragButton button, @NonNull PointF startPoint, @NonNull MotionEvent e) { + switch (button.getId()) { + default: + return onDefaultDrag(button, direction); + } + } + + private boolean onDefaultDrag(@NonNull DragButton button, @NonNull DragDirection direction) { + final String text = ((DirectionDragButton) button).getText(direction); + if (TextUtils.isEmpty(text)) { + return false; + } + switch (text) { + case "sqrt": + user.insertText("sqrt()", -1); + break; + case ",": + user.insertText(", ", 0); + break; + case "^n": + user.insertOperator('^'); + break; + case "^2": + user.insertOperator("^ 2"); + break; + case "?": + case ">": + case "<": + case ">=": + case "<=": + case ":": + user.insertOperator(text); + break; + default: + user.insertText(text, 0); + break; + } + button.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + return true; + } + } + + public static final class LongClickEraser implements View.OnTouchListener, View.OnClickListener { + + @NonNull + private final View view; + + @NonNull + private final EditText editText; + + @NonNull + private final GestureDetector gestureDetector; + + @NonNull + private final Eraser eraser = new Eraser(); + + private LongClickEraser(@NonNull final View view, @NonNull EditText editText) { + this.view = view; + this.editText = editText; + this.gestureDetector = new GestureDetector(view.getContext(), new GestureDetector.SimpleOnGestureListener() { + public void onLongPress(MotionEvent e) { + if (eraser.isTracking()) { + eraser.start(); + } + } + }); + } + + public static void createAndAttach(@NonNull View view, @NonNull EditText editText) { + final LongClickEraser l = new LongClickEraser(view, editText); + view.setOnClickListener(l); + view.setOnTouchListener(l); + } + + private static void erase(@NonNull EditText editText) { + final int start = clampSelection(editText.getSelectionStart()); + final int end = clampSelection(editText.getSelectionEnd()); + if (start != end) { + editText.getText().delete(Math.min(start, end), Math.max(start, end)); + } else if (start > 0) { + editText.getText().delete(start - 1, start); + } + } + + public static int clampSelection(int selection) { + return selection < 0 ? 0 : selection; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + eraser.stopTracking(); + break; + default: + eraser.startTracking(); + gestureDetector.onTouchEvent(event); + break; + } + return false; + } + + @Override + public void onClick(View v) { + erase(editText); + v.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + } + + private class Eraser implements Runnable { + private static final int DELAY = 300; + private long delay; + private boolean erasing; + private boolean tracking = true; + + @Override + public void run() { + erase(editText); + if (editText.length() == 0 || clampSelection(editText.getSelectionStart()) == 0) { + stop(); + return; + } + delay = Math.max(50, 2 * delay / 3); + view.postDelayed(this, delay); + } + + void start() { + if (erasing) { + stop(); + } + erasing = true; + delay = DELAY; + view.removeCallbacks(this); + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + run(); + } + + void stop() { + view.removeCallbacks(this); + if (!erasing) { + return; + } + + erasing = false; + } + + public void stopTracking() { + stop(); + tracking = false; + } + + public boolean isTracking() { + return tracking; + } + + public void startTracking() { + tracking = true; + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/solovyev/android/calculator/KeyboardWindow.java b/app/src/main/java/org/solovyev/android/calculator/KeyboardWindow.java new file mode 100644 index 00000000..a17d5632 --- /dev/null +++ b/app/src/main/java/org/solovyev/android/calculator/KeyboardWindow.java @@ -0,0 +1,105 @@ +package org.solovyev.android.calculator; + +import android.app.Dialog; +import android.content.Context; +import android.os.IBinder; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.Gravity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.PopupWindow; + +import java.util.List; + +public class KeyboardWindow { + + @Nullable + private PopupWindow window; + @Nullable + private Dialog dialog; + + private static void hideIme(@NonNull View view) { + final IBinder token = view.getWindowToken(); + if (token != null) { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService( + Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(token, 0); + } + } + + public void hide() { + if (!isShown()) { + return; + } + moveDialog(Gravity.CENTER); + window.dismiss(); + window = null; + dialog = null; + } + + public void show(@NonNull KeyboardUi.User user, @Nullable Dialog dialog, @NonNull List parameterNames) { + if (isShown()) { + return; + } + this.dialog = dialog; + moveDialog(Gravity.TOP); + final EditText editor = user.getEditor(); + hideIme(editor); + final Context context = editor.getContext(); + final LinearLayout view = new LinearLayout(context); + view.setOrientation(LinearLayout.VERTICAL); + final int buttonSize = context.getResources().getDimensionPixelSize(R.dimen.cpp_kb_button_size); + final int keyboardSize = 5 * buttonSize; + window = new PopupWindow(view, keyboardSize, keyboardSize); + window.setClippingEnabled(false); + window.setOnDismissListener(new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + window = null; + } + }); + // see http://stackoverflow.com/a/4713487/720489 + editor.post(new Runnable() { + @Override + public void run() { + if (window == null) { + return; + } + if (editor.getWindowToken() != null) { + hideIme(editor); + final int inputWidth = editor.getWidth(); + final int xOff = (inputWidth - keyboardSize) / 2; + window.setWidth(keyboardSize); + window.showAsDropDown(editor, xOff, 0); + } else { + editor.postDelayed(this, 50); + } + } + }); + new KeyboardUi(user, parameterNames).makeView(); + } + + public boolean isShown() { + return window != null; + } + + @SuppressWarnings("unchecked") + public V getContentView() { + return (V) window.getContentView(); + } + + public void moveDialog(int gravity) { + if (dialog == null) { + return; + } + final Window window = dialog.getWindow(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = gravity; + window.setAttributes(lp); + } +} diff --git a/app/src/main/java/org/solovyev/android/calculator/function/EditFunctionFragment.java b/app/src/main/java/org/solovyev/android/calculator/function/EditFunctionFragment.java index 9d088bd0..ff4679f3 100644 --- a/app/src/main/java/org/solovyev/android/calculator/function/EditFunctionFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/function/EditFunctionFragment.java @@ -22,9 +22,11 @@ package org.solovyev.android.calculator.function; +import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.res.Resources; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -33,8 +35,15 @@ import android.support.design.widget.TextInputLayout; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.view.ContextMenu; +import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; @@ -45,6 +54,8 @@ import org.solovyev.android.calculator.CalculatorEventListener; import org.solovyev.android.calculator.CalculatorEventType; import org.solovyev.android.calculator.CalculatorUtils; import org.solovyev.android.calculator.DisplayState; +import org.solovyev.android.calculator.KeyboardUi; +import org.solovyev.android.calculator.KeyboardWindow; import org.solovyev.android.calculator.Locator; import org.solovyev.android.calculator.R; import org.solovyev.android.calculator.math.edit.CalculatorFunctionsActivity; @@ -52,8 +63,10 @@ import org.solovyev.android.calculator.math.edit.FunctionsFragment; import org.solovyev.android.calculator.math.edit.MathEntityRemover; import org.solovyev.android.calculator.math.edit.VarEditorSaver; import org.solovyev.android.calculator.model.AFunction; +import org.solovyev.common.math.MathRegistry; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -66,11 +79,23 @@ import jscl.math.Generic; import jscl.math.function.Constant; import jscl.math.function.CustomFunction; import jscl.math.function.Function; +import jscl.math.function.IConstant; import jscl.math.function.IFunction; -public class EditFunctionFragment extends BaseDialogFragment implements CalculatorEventListener { +public class EditFunctionFragment extends BaseDialogFragment implements CalculatorEventListener, View.OnClickListener, View.OnFocusChangeListener, View.OnKeyListener { private static final String ARG_INPUT = "input"; + private static final int MENU_FUNCTION = Menu.FIRST; + private static final int MENU_CONSTANT = Menu.FIRST + 1; + + @NonNull + private final MathRegistry functionsRegistry = Locator.getInstance().getEngine().getFunctionsRegistry(); + @NonNull + private final MathRegistry constantsRegistry = Locator.getInstance().getEngine().getVarsRegistry(); + @NonNull + private final KeyboardWindow keyboardWindow = new KeyboardWindow(); + @NonNull + private final KeyboardUser keyboardUser = new KeyboardUser(); @Bind(R.id.function_params) FunctionParamsView paramsView; @Bind(R.id.function_name_label) @@ -120,7 +145,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat builder.setPositiveButton(R.string.ok, null); final AFunction function = input.getFunction(); builder.setTitle(function == null ? R.string.function_create_function : R.string.function_edit_function); - if(function != null) { + if (function != null) { builder.setNeutralButton(R.string.c_remove, null); } } @@ -154,6 +179,34 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat return dialog; } + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (v.getId() == R.id.function_body) { + if (hasFocus) { + keyboardWindow.show(keyboardUser, getDialog(), paramsView.getParams()); + } else { + keyboardWindow.hide(); + } + } + } + @Override + public void onClick(View v) { + if (v.getId() == R.id.function_body) { + keyboardWindow.show(keyboardUser, getDialog(), paramsView.getParams()); + } + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (v.getId() == R.id.function_body) { + if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK && keyboardWindow.isShown()) { + keyboardWindow.hide(); + return true; + } + } + return false; + } + private void tryClose() { if (validate()) { applyData(); @@ -169,6 +222,9 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat if (!validateName()) { return false; } + if (!validateBody()) { + return false; + } return true; } @@ -182,6 +238,11 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat return true; } + private boolean validateBody() { + return true; + } + + @SuppressLint("InflateParams") @NonNull @Override protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState) { @@ -198,6 +259,9 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat descriptionView.setText(input.getDescription()); bodyView.setText(input.getContent()); } + bodyView.setOnClickListener(this); + bodyView.setOnFocusChangeListener(this); + bodyView.setOnKeyListener(this); return view; } @@ -275,7 +339,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat result.content = in.readString(); result.description = in.readString(); - final List parameterNames = new ArrayList(); + final List parameterNames = new ArrayList<>(); in.readTypedList(parameterNames, STRING_CREATOR); result.parameterNames = parameterNames; @@ -310,7 +374,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat result.name = name; result.content = value; result.description = description; - result.parameterNames = new ArrayList(parameterNames); + result.parameterNames = new ArrayList<>(parameterNames); return result; } @@ -323,7 +387,7 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat final Generic generic = viewState.getResult(); if (generic != null) { final Set constants = CalculatorUtils.getNotSystemConstants(generic); - final List parameterNames = new ArrayList(constants.size()); + final List parameterNames = new ArrayList<>(constants.size()); for (Constant constant : constants) { parameterNames.add(constant.getName()); } @@ -372,4 +436,161 @@ public class EditFunctionFragment extends BaseDialogFragment implements Calculat out.writeSerializable(function); } } + + private class KeyboardUser implements KeyboardUi.User, MenuItem.OnMenuItemClickListener { + @NonNull + @Override + public Context getContext() { + return getActivity(); + } + + @NonNull + @Override + public Resources getResources() { + return EditFunctionFragment.this.getResources(); + } + + @NonNull + @Override + public EditText getEditor() { + return bodyView; + } + + @NonNull + @Override + public ViewGroup getKeyboard() { + return keyboardWindow.getContentView(); + } + + @Override + public void insertOperator(char operator) { + insertOperator(String.valueOf(operator)); + } + + public int clampSelection(int selection) { + return selection < 0 ? 0 : selection; + } + + @Override + public void insertOperator(@NonNull String operator) { + final int start = clampSelection(bodyView.getSelectionStart()); + final int end = clampSelection(bodyView.getSelectionEnd()); + final Editable e = bodyView.getText(); + e.replace(start, end, getOperator(start, end, e, operator)); + } + + @NonNull + private String getOperator(int start, int end, @NonNull Editable e, @NonNull CharSequence operator) { + boolean spaceBefore = true; + boolean spaceAfter = true; + if (start > 0 && Character.isSpaceChar(e.charAt(start - 1))) { + spaceBefore = false; + } + if (end < e.length() && Character.isSpaceChar(e.charAt(end))) { + spaceAfter = false; + } + + if (spaceBefore && spaceAfter) { + return " " + operator + " "; + } + if (spaceBefore) { + return " " + operator; + } + if (spaceAfter) { + return operator + " "; + } + return String.valueOf(operator); + } + + @Override + public void showConstants(@NonNull View v) { + bodyView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + final int id = v.getId(); + if (id == R.id.function_body) { + menu.clear(); + for (String constant : getNamesSorted(constantsRegistry)) { + menu.add(MENU_CONSTANT, Menu.NONE, Menu.NONE, constant).setOnMenuItemClickListener(KeyboardUser.this); + } + unregisterForContextMenu(bodyView); + } + } + }); + bodyView.showContextMenu(); + } + + @Nonnull + private List getNamesSorted(@NonNull MathRegistry registry) { + final List names = registry.getNames(); + Collections.sort(names); + return names; + } + + @Override + public void showFunctions(@NonNull View v) { + bodyView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + final int id = v.getId(); + if (id == R.id.function_body) { + menu.clear(); + for (String function : getNamesSorted(functionsRegistry)) { + menu.add(MENU_FUNCTION, Menu.NONE, Menu.NONE, function).setOnMenuItemClickListener(KeyboardUser.this); + } + unregisterForContextMenu(bodyView); + } + } + }); + bodyView.showContextMenu(); + } + + @Override + public void insertText(@NonNull CharSequence text, int selectionOffset) { + final int start = clampSelection(bodyView.getSelectionStart()); + final int end = clampSelection(bodyView.getSelectionEnd()); + final Editable e = bodyView.getText(); + e.replace(start, end, text); + if (selectionOffset != 0) { + final int selection = clampSelection(bodyView.getSelectionEnd()); + final int newSelection = selection + selectionOffset; + if (newSelection >= 0 && newSelection < e.length()) { + bodyView.setSelection(newSelection); + } + } + } + + @Override + public void done() { + keyboardWindow.hide(); + validateBody(); + } + + @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 onMenuItemClick(MenuItem item) { + final int groupId = item.getGroupId(); + final CharSequence title = item.getTitle(); + if (groupId == MENU_FUNCTION) { + final int argsListIndex = title.toString().indexOf("("); + if (argsListIndex < 0) { + keyboardUser.insertText(title + "()", -1); + } else { + keyboardUser.insertText(title.subSequence(0, argsListIndex) + "()", -1); + } + } else if (groupId == MENU_CONSTANT) { + keyboardUser.insertText(title.toString(), 0); + } else { + return false; + } + return true; + } + } } diff --git a/app/src/main/java/org/solovyev/android/calculator/function/FunctionParamsView.java b/app/src/main/java/org/solovyev/android/calculator/function/FunctionParamsView.java index 16392d4b..f7aee1bb 100644 --- a/app/src/main/java/org/solovyev/android/calculator/function/FunctionParamsView.java +++ b/app/src/main/java/org/solovyev/android/calculator/function/FunctionParamsView.java @@ -27,6 +27,8 @@ import android.content.Context; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.text.Editable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -38,6 +40,7 @@ import android.widget.TextView; import org.solovyev.android.Check; import org.solovyev.android.calculator.App; +import org.solovyev.android.calculator.R; import java.util.ArrayList; import java.util.List; @@ -146,6 +149,7 @@ public class FunctionParamsView extends LinearLayout { } paramView.setInputType(EditorInfo.TYPE_CLASS_TEXT); paramView.setId(id); + paramView.setHint(R.string.c_function_parameter); rowView.addView(paramView, new LayoutParams(0, WRAP_CONTENT, 3)); addView(rowView, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); @@ -195,7 +199,10 @@ public class FunctionParamsView extends LinearLayout { for (int i = 1; i < getChildCount(); i++) { final ViewGroup row = getRowByIndex(i); final EditText paramView = (EditText) row.getChildAt(PARAM_VIEW_INDEX); - params.add(paramView.getText().toString()); + final Editable param = paramView.getText(); + if (!TextUtils.isEmpty(param)) { + params.add(param.toString()); + } } return params; diff --git a/app/src/main/java/org/solovyev/android/calculator/history/EditHistoryFragment.java b/app/src/main/java/org/solovyev/android/calculator/history/EditHistoryFragment.java index 49bc7050..fc46de4f 100644 --- a/app/src/main/java/org/solovyev/android/calculator/history/EditHistoryFragment.java +++ b/app/src/main/java/org/solovyev/android/calculator/history/EditHistoryFragment.java @@ -1,5 +1,6 @@ package org.solovyev.android.calculator.history; +import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; @@ -11,14 +12,16 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.TextView; -import butterknife.Bind; -import butterknife.ButterKnife; + import org.solovyev.android.calculator.AppComponent; import org.solovyev.android.calculator.BaseDialogFragment; import org.solovyev.android.calculator.R; import javax.inject.Inject; +import butterknife.Bind; +import butterknife.ButterKnife; + public class EditHistoryFragment extends BaseDialogFragment { public static final String ARG_STATE = "state"; @@ -81,6 +84,7 @@ public class EditHistoryFragment extends BaseDialogFragment { }); } + @SuppressLint("InflateParams") @NonNull @Override protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater, @Nullable Bundle savedInstanceState) { diff --git a/app/src/main/res/drawable-hdpi/app_icon.png b/app/src/main/res/drawable-hdpi/app_icon.png new file mode 100644 index 00000000..39c8550b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/app_icon.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png new file mode 100644 index 00000000..ef76616b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_backspace_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_done_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_done_white_24dp.png new file mode 100644 index 00000000..ef1b2ca6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_done_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_keyboard_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_keyboard_white_24dp.png new file mode 100644 index 00000000..d2b2ed4d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_keyboard_white_24dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_space_bar_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_space_bar_white_24dp.png new file mode 100644 index 00000000..864f7d9b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_space_bar_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/app_icon.png b/app/src/main/res/drawable-mdpi/app_icon.png new file mode 100644 index 00000000..257ab389 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/app_icon.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png new file mode 100644 index 00000000..99871fb2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_backspace_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_done_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_done_white_24dp.png new file mode 100644 index 00000000..1ddeef87 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_done_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_keyboard_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_keyboard_white_24dp.png new file mode 100644 index 00000000..b863d02e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_keyboard_white_24dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_space_bar_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_space_bar_white_24dp.png new file mode 100644 index 00000000..efe2f491 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_space_bar_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/app_icon.png b/app/src/main/res/drawable-xhdpi/app_icon.png new file mode 100644 index 00000000..1658a3f8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_icon.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_backspace_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_backspace_white_24dp.png new file mode 100644 index 00000000..ad229f0b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_backspace_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png new file mode 100644 index 00000000..e27c938b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_done_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_keyboard_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_keyboard_white_24dp.png new file mode 100644 index 00000000..8041bc62 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_keyboard_white_24dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_space_bar_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_space_bar_white_24dp.png new file mode 100644 index 00000000..0c555e9c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_space_bar_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/app_icon.png b/app/src/main/res/drawable-xxhdpi/app_icon.png new file mode 100644 index 00000000..de6a6055 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/app_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png new file mode 100644 index 00000000..05ef4b12 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_backspace_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png new file mode 100644 index 00000000..92bf47c2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_keyboard_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_keyboard_white_24dp.png new file mode 100644 index 00000000..2a5dcefa Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_keyboard_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_space_bar_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_space_bar_white_24dp.png new file mode 100644 index 00000000..b01f441d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_space_bar_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_backspace_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_backspace_white_24dp.png new file mode 100644 index 00000000..9843dad2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_backspace_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png new file mode 100644 index 00000000..40a1c8dc Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_keyboard_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_keyboard_white_24dp.png new file mode 100644 index 00000000..25bcfe6e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_keyboard_white_24dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_space_bar_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_space_bar_white_24dp.png new file mode 100644 index 00000000..33832219 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_space_bar_white_24dp.png differ diff --git a/app/src/main/res/values-small/dimens.xml b/app/src/main/res/values-small/dimens.xml index 36d2e3b6..c5d03b3e 100644 --- a/app/src/main/res/values-small/dimens.xml +++ b/app/src/main/res/values-small/dimens.xml @@ -29,4 +29,5 @@ 20sp 20sp + 40dp \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 48c42fcb..56d56c2e 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -39,4 +39,5 @@ 1dp 20dp + 50dp \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 96c8994c..00356f42 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -60,5 +60,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/text_strings.xml b/app/src/main/res/values/text_strings.xml index b0a78fc9..d5bf268c 100644 --- a/app/src/main/res/values/text_strings.xml +++ b/app/src/main/res/values/text_strings.xml @@ -149,6 +149,7 @@ Value Description Parameters + Parameter Create function Edit function Name of function is not valid: name must start with a letter, can contain letters, digits and underscore.