diff --git a/app/src/main/java/org/solovyev/android/calculator/Keyboard.java b/app/src/main/java/org/solovyev/android/calculator/Keyboard.java index f4889ab8..11d8713c 100644 --- a/app/src/main/java/org/solovyev/android/calculator/Keyboard.java +++ b/app/src/main/java/org/solovyev/android/calculator/Keyboard.java @@ -31,6 +31,7 @@ import org.solovyev.android.Check; import org.solovyev.android.calculator.buttons.CppSpecialButton; import org.solovyev.android.calculator.ga.Ga; import org.solovyev.android.calculator.math.MathType; +import org.solovyev.android.calculator.memory.Memory; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -48,6 +49,8 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe @Inject Display display; @Inject + Lazy memory; + @Inject Calculator calculator; @Inject Engine engine; @@ -148,6 +151,9 @@ public class Keyboard implements SharedPreferences.OnSharedPreferenceChangeListe case like: launcher.openFacebook(); break; + case memory: + editor.insert(memory.get().getValue()); + break; case erase: editor.erase(); break; diff --git a/app/src/main/java/org/solovyev/android/calculator/Runnables.java b/app/src/main/java/org/solovyev/android/calculator/Runnables.java new file mode 100644 index 00000000..7b4470bd --- /dev/null +++ b/app/src/main/java/org/solovyev/android/calculator/Runnables.java @@ -0,0 +1,25 @@ +package org.solovyev.android.calculator; + +import android.support.annotation.NonNull; +import org.solovyev.android.Check; + +import java.util.ArrayList; +import java.util.List; + +public class Runnables implements Runnable { + @NonNull + private final List list = new ArrayList<>(); + @Override + public void run() { + Check.isMainThread(); + for (Runnable runnable : list) { + runnable.run(); + } + list.clear(); + } + + public void add(@NonNull Runnable runnable) { + Check.isMainThread(); + list.add(runnable); + } +} diff --git a/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java b/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java index fd7f9d30..b999dc15 100644 --- a/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java +++ b/app/src/main/java/org/solovyev/android/calculator/buttons/CppSpecialButton.java @@ -24,11 +24,10 @@ package org.solovyev.android.calculator.buttons; import org.solovyev.android.Check; -import java.util.HashMap; -import java.util.Map; - import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; public enum CppSpecialButton { @@ -38,6 +37,7 @@ public enum CppSpecialButton { settings("settings"), settings_widget("settings_widget"), like("like"), + memory("memory"), erase("erase"), paste("paste"), copy("copy"), diff --git a/app/src/main/java/org/solovyev/android/calculator/history/History.java b/app/src/main/java/org/solovyev/android/calculator/history/History.java index 9af98adc..dbb92e89 100644 --- a/app/src/main/java/org/solovyev/android/calculator/history/History.java +++ b/app/src/main/java/org/solovyev/android/calculator/history/History.java @@ -69,7 +69,7 @@ public class History { @Nonnull private final List saved = new ArrayList<>(); @Nonnull - private final List whenLoadedRunnables = new ArrayList<>(); + private final Runnables whenLoadedRunnables = new Runnables(); private boolean loaded; @Inject Application application; @@ -231,10 +231,7 @@ public class History { postRecentWrite(); } loaded = true; - for (Runnable runnable : whenLoadedRunnables) { - runnable.run(); - } - whenLoadedRunnables.clear(); + whenLoadedRunnables.run(); } @Nonnull @@ -388,7 +385,6 @@ public class History { public void runWhenLoaded(@NonNull Runnable runnable) { Check.isTrue(!loaded); - Check.isMainThread(); whenLoadedRunnables.add(runnable); } diff --git a/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java b/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java index 5b5d63c7..5eb570b8 100644 --- a/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java +++ b/app/src/main/java/org/solovyev/android/calculator/keyboard/KeyboardUi.java @@ -12,13 +12,15 @@ import android.widget.Button; import android.widget.ImageButton; import butterknife.Bind; import butterknife.ButterKnife; +import dagger.Lazy; import jscl.AngleUnit; import jscl.NumeralBase; -import org.solovyev.android.calculator.ActivityLauncher; -import org.solovyev.android.calculator.Engine; -import org.solovyev.android.calculator.R; +import jscl.math.Expression; +import jscl.math.Generic; +import org.solovyev.android.calculator.*; import org.solovyev.android.calculator.buttons.CppSpecialButton; import org.solovyev.android.calculator.history.History; +import org.solovyev.android.calculator.memory.Memory; import org.solovyev.android.calculator.view.AngleUnitsButton; import org.solovyev.android.views.dragbutton.DirectionDragButton; import org.solovyev.android.views.dragbutton.DirectionDragImageButton; @@ -60,6 +62,10 @@ public class KeyboardUi extends BaseKeyboardUi { @Inject Engine engine; @Inject + Display display; + @Inject + Lazy memory; + @Inject PartialKeyboardUi partialUi; @Bind(R.id.cpp_button_vars) DirectionDragButton variablesButton; @@ -89,6 +95,9 @@ public class KeyboardUi extends BaseKeyboardUi { @Nullable @Bind(R.id.cpp_button_like) ImageButton likeButton; + @Nullable + @Bind(R.id.cpp_button_memory) + DirectionDragButton memoryButton; @Inject public KeyboardUi(@Nonnull Application application) { @@ -138,6 +147,7 @@ public class KeyboardUi extends BaseKeyboardUi { prepareButton(copyButton); prepareButton(pasteButton); prepareButton(likeButton); + prepareButton(memoryButton); if (isSimpleLayout()) { hideText(button1, up, down); @@ -212,6 +222,9 @@ public class KeyboardUi extends BaseKeyboardUi { case R.id.cpp_button_like: onClick(v, CppSpecialButton.like); break; + case R.id.cpp_button_memory: + onClick(v, CppSpecialButton.memory); + break; case R.id.cpp_button_operators: onClick(v, CppSpecialButton.operators); break; @@ -242,6 +255,8 @@ public class KeyboardUi extends BaseKeyboardUi { return true; } return false; + case R.id.cpp_button_memory: + return processMemoryButton(direction); case R.id.cpp_button_subtraction: if (direction == down) { launcher.showOperators(); @@ -261,6 +276,36 @@ public class KeyboardUi extends BaseKeyboardUi { } } + private boolean processMemoryButton(@NonNull DragDirection direction) { + final DisplayState state = display.getState(); + if (!state.valid) { + return false; + } + Generic value = state.getResult(); + if (value == null) { + try { + value = Expression.valueOf(state.text); + } catch (jscl.text.ParseException e) { + Log.w(App.TAG, e.getMessage(), e); + } + } + if (value == null) { + return false; + } + switch (direction) { + case up: + memory.get().add(value); + return true; + case down: + memory.get().subtract(value); + return true; + case left: + memory.get().clear(); + return true; + } + return false; + } + private boolean processAngleUnitsButton(@Nonnull DragDirection direction, @Nonnull DirectionDragButton button) { if (direction == DragDirection.left) { return processDefault(direction, button); diff --git a/app/src/main/java/org/solovyev/android/calculator/memory/Memory.java b/app/src/main/java/org/solovyev/android/calculator/memory/Memory.java new file mode 100644 index 00000000..14f0be53 --- /dev/null +++ b/app/src/main/java/org/solovyev/android/calculator/memory/Memory.java @@ -0,0 +1,201 @@ +package org.solovyev.android.calculator.memory; + +import android.os.Handler; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import jscl.math.Expression; +import jscl.math.Generic; +import jscl.math.JsclInteger; +import jscl.text.ParseException; +import org.solovyev.android.Check; +import org.solovyev.android.calculator.App; +import org.solovyev.android.calculator.AppModule; +import org.solovyev.android.calculator.Notifier; +import org.solovyev.android.calculator.Runnables; +import org.solovyev.android.io.FileSystem; + +import javax.annotation.Nonnull; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.util.concurrent.Executor; + +@Singleton +public class Memory { + + @NonNull + private static final Generic EMPTY = numeric(Expression.valueOf(JsclInteger.ZERO)); + @NonNull + private final FileSystem fileSystem; + @NonNull + private final File filesDir; + @NonNull + private final WriteTask writeTask = new WriteTask(); + @NonNull + private final Runnables whenLoadedRunnables = new Runnables(); + @Inject + Notifier notifier; + @Named(AppModule.THREAD_BACKGROUND) + @Inject + Executor backgroundThread; + @Inject + Handler handler; + @NonNull + private Generic value = EMPTY; + private boolean loaded; + + @Inject + public Memory(@NonNull @Named(AppModule.THREAD_INIT) Executor initThread, @NonNull FileSystem fileSystem, @NonNull @Named(AppModule.DIR_FILES) File filesDir) { + this.fileSystem = fileSystem; + this.filesDir = filesDir; + initThread.execute(new Runnable() { + @Override + public void run() { + initAsync(); + } + }); + } + + @NonNull + private static Generic numeric(@NonNull Generic generic) { + try { + return generic.numeric(); + } catch (RuntimeException e) { + return generic; + } + } + + private void initAsync() { + Check.isNotMainThread(); + final Generic value = loadValue(); + handler.post(new Runnable() { + @Override + public void run() { + onLoaded(value); + } + }); + } + + private void onLoaded(@NonNull Generic value) { + this.value = value; + this.loaded = true; + this.whenLoadedRunnables.run(); + } + + @NonNull + private Generic loadValue() { + Check.isNotMainThread(); + try { + final CharSequence value = fileSystem.read(getFile()); + return TextUtils.isEmpty(value) ? EMPTY : numeric(Expression.valueOf(value.toString())); + } catch (IOException | ParseException e) { + Log.e(App.TAG, e.getMessage(), e); + } + return EMPTY; + } + + public void add(@NonNull final Generic that) { + Check.isMainThread(); + if (!loaded) { + postAdd(that); + return; + } + try { + setValue(value.add(that)); + } catch (RuntimeException e) { + notifier.showMessage(e.getLocalizedMessage()); + } + } + + private void postAdd(@NonNull final Generic that) { + whenLoadedRunnables.add(new Runnable() { + @Override + public void run() { + add(that); + } + }); + } + + public void subtract(@NonNull final Generic that) { + Check.isMainThread(); + if (!loaded) { + postSubtract(that); + return; + } + try { + setValue(value.subtract(that)); + } catch (RuntimeException e) { + notifier.showMessage(e.getLocalizedMessage()); + } + } + + private void postSubtract(@NonNull final Generic that) { + whenLoadedRunnables.add(new Runnable() { + @Override + public void run() { + subtract(that); + } + }); + } + + @NonNull + public String getValue() { + try { + return value.toString(); + } catch (RuntimeException e) { + Log.w(App.TAG, e.getMessage(), e); + } + return ""; + } + + private void setValue(@NonNull Generic newValue) { + Check.isTrue(loaded); + value = numeric(newValue); + handler.removeCallbacks(writeTask); + handler.postDelayed(writeTask, 3000L); + notifier.showMessage(getValue()); + } + + public void clear() { + Check.isMainThread(); + if (!loaded) { + postClear(); + return; + } + setValue(EMPTY); + } + + private void postClear() { + whenLoadedRunnables.add(new Runnable() { + @Override + public void run() { + clear(); + } + }); + } + + @Nonnull + private File getFile() { + return new File(filesDir, "memory.txt"); + } + + private class WriteTask implements Runnable { + @Override + public void run() { + Check.isMainThread(); + if (!loaded) { + return; + } + final String value = getValue(); + backgroundThread.execute(new Runnable() { + @Override + public void run() { + fileSystem.writeSilently(getFile(), value); + } + }); + } + } +} diff --git a/app/src/main/res/layout/cpp_app_button_memory.xml b/app/src/main/res/layout/cpp_app_button_memory.xml new file mode 100644 index 00000000..edd46cb0 --- /dev/null +++ b/app/src/main/res/layout/cpp_app_button_memory.xml @@ -0,0 +1,31 @@ + + + + \ 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 296b6a43..d47f1d52 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -39,6 +39,7 @@ + diff --git a/app/src/main/res/values/text_non_translatable.xml b/app/src/main/res/values/text_non_translatable.xml index 616fca01..c835e01b 100644 --- a/app/src/main/res/values/text_non_translatable.xml +++ b/app/src/main/res/values/text_non_translatable.xml @@ -5,6 +5,10 @@ + M + M+ + M- + MC + + 0 diff --git a/jscl/src/main/java/jscl/math/Expression.java b/jscl/src/main/java/jscl/math/Expression.java index 6a9759c5..2dc0e7fe 100644 --- a/jscl/src/main/java/jscl/math/Expression.java +++ b/jscl/src/main/java/jscl/math/Expression.java @@ -128,12 +128,11 @@ public class Expression extends Generic { return new Expression().init(generic); } - public static Expression init(@Nonnull NumericWrapper numericWrapper) { - final Expression expression = new Expression(1); - Literal literal = new Literal(); + public Expression init(@Nonnull NumericWrapper numericWrapper) { + final Literal literal = new Literal(); literal.init(new ExpressionVariable(numericWrapper), 1); - expression.init(literal, JsclInteger.ONE); - return expression; + init(literal, JsclInteger.ONE); + return this; } public static void separateSign(MathML element, Generic generic) { @@ -561,21 +560,13 @@ public class Expression extends Generic { try { return integerValue().numeric(); } catch (NotIntegerException ex) { - final Literal literal = literalScm(); - - final Map content = literal.content(NUMERIC_CONVERTER); - - return substitute(content); + return substitute(literalScm().content(NUMERIC_CONVERTER)); } } @Nonnull public Generic valueOf(@Nonnull Generic generic) { - final Expression result = newInstance(0); - - result.init(generic); - - return result; + return newInstance(0).init(generic); } @Nonnull