Merge changes I72de7f22,I70992067 into ub-calculator-euler
* changes:
Handle text animation in different display situations.
Integrate database with history.
diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml
index 1d45a14..87ef83b 100644
--- a/res/layout/history_item.xml
+++ b/res/layout/history_item.xml
@@ -54,6 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:bufferType="spannable"
+ android:maxLines="1"
android:textColor="@color/display_result_text_color" />
</LinearLayout>
diff --git a/src/com/android/calculator2/AlertDialogFragment.java b/src/com/android/calculator2/AlertDialogFragment.java
index e098022..12840ae 100644
--- a/src/com/android/calculator2/AlertDialogFragment.java
+++ b/src/com/android/calculator2/AlertDialogFragment.java
@@ -20,6 +20,7 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
+import android.app.FragmentManager;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -78,6 +79,10 @@
*/
public static void showMessageDialog(Activity activity, @Nullable CharSequence title,
CharSequence message, @Nullable CharSequence positiveButtonLabel) {
+ final FragmentManager manager = activity.getFragmentManager();
+ if (manager == null || manager.isDestroyed()) {
+ return;
+ }
final AlertDialogFragment dialogFragment = new AlertDialogFragment();
final Bundle args = new Bundle();
args.putCharSequence(KEY_MESSAGE, message);
@@ -87,7 +92,7 @@
}
args.putCharSequence(KEY_TITLE, title);
dialogFragment.setArguments(args);
- dialogFragment.show(activity.getFragmentManager(), null /* tag */);
+ dialogFragment.show(manager, null /* tag */);
}
public AlertDialogFragment() {
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 8aac061..41a041c 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -33,6 +33,7 @@
import android.animation.PropertyValuesHolder;
import android.app.ActionBar;
import android.app.Activity;
+import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.ClipData;
import android.content.DialogInterface;
@@ -189,7 +190,7 @@
@Override
public void onClosed() {
- getFragmentManager().popBackStack();
+ popFragmentBackstack();
}
@Override
@@ -308,8 +309,10 @@
findViewById(R.id.op_sqr)
};
- mEvaluator = new Evaluator(this);
+ mEvaluator = Evaluator.getInstance(this);
mResultText.setEvaluator(mEvaluator, Evaluator.MAIN_INDEX);
+ // This resultText should always use evaluateAndNotify, not requireResult().
+ mResultText.setShouldRequireResult(false);
KeyMaps.setActivity(this);
mDragLayout = (DragLayout) findViewById(R.id.drag_layout);
@@ -319,9 +322,10 @@
mHistoryFrame = (FrameLayout) findViewById(R.id.history_frame);
if (savedInstanceState != null) {
- setState(CalculatorState.values()[
- savedInstanceState.getInt(KEY_DISPLAY_STATE,
- CalculatorState.INPUT.ordinal())]);
+ final CalculatorState savedState = CalculatorState.values()[
+ savedInstanceState.getInt(KEY_DISPLAY_STATE,
+ CalculatorState.INPUT.ordinal())];
+ setState(savedState);
CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
if (unprocessed != null) {
mUnprocessedChars = unprocessed.toString();
@@ -500,7 +504,7 @@
if (mDragLayout.isOpen()) {
// Close the layout and remove the fragment.
mDragLayout.setClosed();
- getFragmentManager().popBackStack();
+ popFragmentBackstack();
return;
}
if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
@@ -623,6 +627,13 @@
}
}
+ private void popFragmentBackstack() {
+ final FragmentManager manager = getFragmentManager();
+ if (manager == null || manager.isDestroyed()) {
+ return;
+ }
+ manager.popBackStack();
+ }
/**
* Switch to INPUT from RESULT state in response to input of the specified button_id.
* View.NO_ID is treated as an incomplete function id.
@@ -1176,6 +1187,10 @@
}
private void showHistoryFragment(int transit) {
+ final FragmentManager manager = getFragmentManager();
+ if (manager == null || manager.isDestroyed()) {
+ return;
+ }
if (!mDragLayout.isOpen()) {
getFragmentManager().beginTransaction()
.replace(R.id.history_frame, mHistoryFragment, HistoryFragment.TAG)
@@ -1183,6 +1198,7 @@
.addToBackStack(HistoryFragment.TAG)
.commit();
}
+ // TODO: pass current scroll position of result
}
private void displayMessage(String title, String message) {
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index f20d2bf..c6eb9af 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -21,15 +21,12 @@
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.TtsSpan;
-import android.text.style.TtsSpan.TextBuilder;
-import java.math.BigInteger;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
+import java.math.BigInteger;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
/**
* A mathematical expression represented as a sequence of "tokens".
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index caf72fe..bfabfce 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -113,6 +113,7 @@
private float mNoEllipsisCredit;
// Fraction of digit width saved by both replacing ellipsis with digit
// and avoiding scientific notation.
+ private boolean mShouldRequireResult = true;
private static final int MAX_WIDTH = 100;
// Maximum number of digits displayed.
public static final int MAX_LEADING_ZEROES = 6;
@@ -303,6 +304,22 @@
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (mEvaluator != null && mShouldRequireResult) {
+ final CalculatorExpr expr = mEvaluator.getExpr(mIndex);
+ if (expr != null && expr.hasInterestingOps()) {
+ mEvaluator.requireResult(mIndex, this, this);
+ }
+ }
+ }
+
+ public void setShouldRequireResult(boolean should) {
+ mShouldRequireResult = should;
+ }
+
// From Evaluator.CharMetricsInfo.
@Override
public float separatorChars(String s, int len) {
diff --git a/src/com/android/calculator2/DragController.java b/src/com/android/calculator2/DragController.java
index 737cbb6..b0ff122 100644
--- a/src/com/android/calculator2/DragController.java
+++ b/src/com/android/calculator2/DragController.java
@@ -25,6 +25,8 @@
*/
public final class DragController {
+ private static final String TAG = "DragController";
+
// References to views from the Calculator Display.
private CalculatorFormula mDisplayFormula;
private CalculatorResult mDisplayResult;
@@ -33,12 +35,48 @@
private int mFormulaTranslationY;
private int mFormulaTranslationX;
private float mFormulaScale;
+ private float mResultScale;
private int mResultTranslationY;
private int mResultTranslationX;
+ private int mDisplayHeight;
+
private boolean mAnimationInitialized;
+ private AnimationController mAnimationController;
+
+ private Evaluator mEvaluator;
+
+ public void setEvaluator(Evaluator evaluator) {
+ mEvaluator = evaluator;
+
+ if (evaluator != null) {
+ // Initialize controller
+ if (isDisplayEmpty()) {
+ // Empty display
+ mAnimationController = new EmptyAnimationController();
+ } else if (mEvaluator.hasResult(Evaluator.MAIN_INDEX)) {
+ // Result
+ mAnimationController = new ResultAnimationController();
+ } else {
+ // There is something in the formula field. There may or may not be
+ // a quick result.
+ mAnimationController = new AnimationController();
+ }
+ }
+ }
+
+ // There is no formula or result in the CalculatorDisplay.
+ private boolean isDisplayEmpty() {
+ if (mEvaluator != null) {
+ final CalculatorExpr mainExpr = mEvaluator.getExpr(Evaluator.MAIN_INDEX);
+ return mainExpr == null || mainExpr.isEmpty();
+ } else {
+ return false;
+ }
+ }
+
public void setDisplayFormula(CalculatorFormula formula) {
mDisplayFormula = formula;
}
@@ -54,82 +92,346 @@
public void animateViews(float yFraction, RecyclerView recyclerView, int itemCount) {
final HistoryAdapter.ViewHolder vh = (HistoryAdapter.ViewHolder)
recyclerView.findViewHolderForAdapterPosition(0);
- if (vh != null) {
+ if (vh != null && !isDisplayEmpty()) {
final CalculatorFormula formula = vh.getFormula();
final CalculatorResult result = vh.getResult();
final TextView date = vh.getDate();
if (!mAnimationInitialized) {
- // Calculate the scale for the text
- mFormulaScale = (mDisplayFormula.getTextSize() * 1.0f) / formula.getTextSize();
+ mAnimationController.initializeScales(formula, result);
- // Baseline of formula moves by the difference in formula bottom padding.
- mFormulaTranslationY =
- mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
- + mDisplayResult.getHeight() - result.getHeight();
+ mAnimationController.initializeFormulaTranslationX(formula);
- // Right border of formula moves by the difference in formula end padding.
- mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+ mAnimationController.initializeFormulaTranslationY(formula, result);
- // Baseline of result moves by the difference in result bottom padding.
- mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+ mAnimationController.initializeResultTranslationX(result);
- mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+ mAnimationController.initializeResultTranslationY(result);
mAnimationInitialized = true;
}
if (mAnimationInitialized) {
+ result.setScaleX(mAnimationController.getResultScale(yFraction));
+ result.setScaleY(mAnimationController.getResultScale(yFraction));
+
+ formula.setScaleX(mAnimationController.getFormulaScale(yFraction));
+ formula.setScaleY(mAnimationController.getFormulaScale(yFraction));
+
formula.setPivotX(formula.getWidth() - formula.getPaddingEnd());
formula.setPivotY(formula.getHeight() - formula.getPaddingBottom());
result.setPivotX(result.getWidth() - result.getPaddingEnd());
result.setPivotY(result.getHeight() - result.getPaddingBottom());
- final float resultTranslationX = (mResultTranslationX * yFraction)
- - mResultTranslationX;
- result.setTranslationX(resultTranslationX);
+ formula.setTranslationX(mAnimationController.getFormulaTranslationX(yFraction));
+ formula.setTranslationY(mAnimationController.getFormulaTranslationY(yFraction));
- // Scale linearly between -mResultTranslationY and 0.
- final float resultTranslationY =
- (mResultTranslationY * yFraction) - mResultTranslationY;
- result.setTranslationY(resultTranslationY);
+ result.setTranslationX(mAnimationController.getResultTranslationX(yFraction));
+ result.setTranslationY(mAnimationController.getResultTranslationY(yFraction));
- final float scale = mFormulaScale - (mFormulaScale * yFraction) + yFraction;
- formula.setScaleY(scale);
- formula.setScaleX(scale);
+ date.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
+ }
+ } else if (isDisplayEmpty()) {
+ // There is no current expression but we still need to collect information
+ // to translate the other viewholders.
+ if (!mAnimationInitialized) {
+ mAnimationController.initializeDisplayHeight();
- final float formulaTranslationX = (mFormulaTranslationX * yFraction)
- - mFormulaTranslationX;
- formula.setTranslationX(formulaTranslationX);
+ mAnimationInitialized = true;
+ }
+ }
- // Scale linearly between -FormulaTranslationY and 0.
- final float formulaTranslationY =
- (mFormulaTranslationY * yFraction) - mFormulaTranslationY;
- formula.setTranslationY(formulaTranslationY);
-
- // We want the date to start out above the visible screen with
- // this distance decreasing as it's pulled down.
- final float dateTranslationY =
- - mToolbar.getHeight() * (1 - yFraction)
- + formulaTranslationY
- - mDisplayFormula.getPaddingTop()
- + (mDisplayFormula.getPaddingTop() * yFraction);
- date.setTranslationY(dateTranslationY);
-
- // Translate items above the Current Expression to accommodate the size change.
- // Move up all ViewHolders above the current expression.
- for (int i = recyclerView.getChildCount() - 1; i > 0; --i) {
- final RecyclerView.ViewHolder vh2 =
- recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
- if (vh2 != null) {
- final View view = vh2.itemView;
- if (view != null) {
- view.setTranslationY(dateTranslationY);
- }
- }
+ // Move up all ViewHolders above the current expression; if there is no current expression,
+ // we're translating all the viewholders.
+ for (int i = recyclerView.getChildCount() - 1;
+ i >= mAnimationController.getFirstTranslatedViewHolderIndex();
+ --i) {
+ final RecyclerView.ViewHolder vh2 =
+ recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
+ if (vh2 != null) {
+ final View view = vh2.itemView;
+ if (view != null) {
+ view.setTranslationY(
+ mAnimationController.getHistoryElementTranslationY(yFraction));
}
}
}
}
+
+ /**
+ * Reset all initialized values whenever the History fragment is closed because the
+ * DisplayState may change.
+ */
+ public void resetAnimationInitialized() {
+ mAnimationInitialized = false;
+ }
+
+ public interface AnimateTextInterface {
+
+ void initializeDisplayHeight();
+
+ void initializeScales(CalculatorFormula formula, CalculatorResult result);
+
+ void initializeFormulaTranslationX(CalculatorFormula formula);
+
+ void initializeFormulaTranslationY(CalculatorFormula formula, CalculatorResult result);
+
+ void initializeResultTranslationX(CalculatorResult result);
+
+ void initializeResultTranslationY(CalculatorResult result);
+
+ float getResultTranslationX(float yFraction);
+
+ float getResultTranslationY(float yFraction);
+
+ float getResultScale(float yFraction);
+
+ float getFormulaScale(float yFraction);
+
+ float getFormulaTranslationX(float yFraction);
+
+ float getFormulaTranslationY(float yFraction);
+
+ float getDateTranslationY(float yFraction);
+
+ float getHistoryElementTranslationY(float yFraction);
+
+ // Return the lowest index of the first Viewholder to be translated upwards.
+ // If there is no current expression, we translate all the viewholders; otherwise,
+ // we start at index 1.
+ int getFirstTranslatedViewHolderIndex();
+ }
+
+ // The default AnimationController when Display is in INPUT state and DisplayFormula is not
+ // empty. There may or may not be a quick result.
+ public class AnimationController implements DragController.AnimateTextInterface {
+
+ public void initializeDisplayHeight() {
+ // no-op
+ }
+
+ public void initializeScales(CalculatorFormula formula, CalculatorResult result) {
+ // Calculate the scale for the text
+ mFormulaScale = (mDisplayFormula.getTextSize() * 1.0f) / formula.getTextSize();
+ }
+
+ public void initializeFormulaTranslationY(CalculatorFormula formula,
+ CalculatorResult result) {
+ // Baseline of formula moves by the difference in formula bottom padding and the
+ // difference in result height.
+ mFormulaTranslationY =
+ mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
+ + mDisplayResult.getHeight() - result.getHeight();
+
+ }
+
+ public void initializeFormulaTranslationX(CalculatorFormula formula) {
+ // Right border of formula moves by the difference in formula end padding.
+ mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+ }
+
+ public void initializeResultTranslationY(CalculatorResult result) {
+ // Baseline of result moves by the difference in result bottom padding.
+ mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+ }
+
+ public void initializeResultTranslationX(CalculatorResult result) {
+ mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+ }
+
+ public float getResultTranslationX(float yFraction) {
+ return (mResultTranslationX * yFraction) - mResultTranslationX;
+ }
+
+ public float getResultTranslationY(float yFraction) {
+ return (mResultTranslationY * yFraction) - mResultTranslationY;
+ }
+
+ public float getResultScale(float yFraction) {
+ return 1;
+ }
+
+ public float getFormulaScale(float yFraction) {
+ return mFormulaScale - (mFormulaScale * yFraction) + yFraction;
+ }
+
+ public float getFormulaTranslationX(float yFraction) {
+ return (mFormulaTranslationX * yFraction) -
+ mFormulaTranslationX;
+ }
+
+ public float getFormulaTranslationY(float yFraction) {
+ // Scale linearly between -FormulaTranslationY and 0.
+ return (mFormulaTranslationY * yFraction) - mFormulaTranslationY;
+ }
+
+ public float getDateTranslationY(float yFraction) {
+ // We also want the date to start out above the visible screen with
+ // this distance decreasing as it's pulled down.
+ return -mToolbar.getHeight() * (1 - yFraction)
+ + getResultTranslationY(yFraction)
+ - mDisplayFormula.getPaddingTop() +
+ (mDisplayFormula.getPaddingTop() * yFraction);
+ }
+
+ public float getHistoryElementTranslationY(float yFraction) {
+ return getDateTranslationY(yFraction);
+ }
+
+ public int getFirstTranslatedViewHolderIndex() {
+ return 1;
+ }
+ }
+
+ // The default AnimationController when Display is in RESULT state.
+ public class ResultAnimationController extends AnimationController
+ implements DragController.AnimateTextInterface {
+ @Override
+ public void initializeScales(CalculatorFormula formula, CalculatorResult result) {
+ final float textSize = mDisplayResult.getTextSize() * mDisplayResult.getScaleX();
+ mResultScale = textSize / result.getTextSize();
+
+ mFormulaScale = 1;
+ }
+
+ @Override
+ public void initializeFormulaTranslationY(CalculatorFormula formula,
+ CalculatorResult result) {
+ // Baseline of formula moves by the difference in formula bottom padding and the
+ // difference in the result height.
+ mFormulaTranslationY = mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
+ + mDisplayResult.getHeight() - result.getHeight();
+ }
+
+ @Override
+ public void initializeFormulaTranslationX(CalculatorFormula formula) {
+ // Right border of formula moves by the difference in formula end padding.
+ mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+ }
+
+ @Override
+ public void initializeResultTranslationY(CalculatorResult result) {
+ // Baseline of result moves by the difference in result bottom padding.
+ mResultTranslationY = mDisplayResult.getBottom() - result.getBottom() +
+ mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+ }
+
+ @Override
+ public void initializeResultTranslationX(CalculatorResult result) {
+ mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+ }
+
+ @Override
+ public float getResultTranslationX(float yFraction) {
+ return (mResultTranslationX * yFraction) - mResultTranslationX;
+ }
+
+ @Override
+ public float getResultTranslationY(float yFraction) {
+ return (mResultTranslationY * yFraction) - mResultTranslationY;
+ }
+
+ @Override
+ public float getFormulaTranslationX(float yFraction) {
+ return (mFormulaTranslationX * yFraction) -
+ mFormulaTranslationX;
+ }
+
+ @Override
+ public float getFormulaTranslationY(float yFraction) {
+ return getDateTranslationY(yFraction);
+ }
+
+ @Override
+ public float getResultScale(float yFraction) {
+ return mResultScale - (mResultScale * yFraction) + yFraction;
+ }
+
+ @Override
+ public float getFormulaScale(float yFraction) {
+ return 1;
+ }
+
+ @Override
+ public float getDateTranslationY(float yFraction) {
+ // We also want the date to start out above the visible screen with
+ // this distance decreasing as it's pulled down.
+ return -mToolbar.getHeight() * (1 - yFraction)
+ + (mResultTranslationY * yFraction) - mResultTranslationY
+ - mDisplayFormula.getPaddingTop() +
+ (mDisplayFormula.getPaddingTop() * yFraction);
+ }
+
+ @Override
+ public int getFirstTranslatedViewHolderIndex() {
+ return 1;
+ }
+ }
+
+ // The default AnimationController when Display is completely empty.
+ public class EmptyAnimationController extends AnimationController
+ implements DragController.AnimateTextInterface {
+ @Override
+ public void initializeDisplayHeight() {
+ mDisplayHeight = mToolbar.getHeight() + mDisplayResult.getHeight()
+ + mDisplayFormula.getHeight();
+ }
+
+ @Override
+ public void initializeScales(CalculatorFormula formula, CalculatorResult result) {
+ // no-op
+ }
+
+ @Override
+ public void initializeFormulaTranslationY(CalculatorFormula formula,
+ CalculatorResult result) {
+ // no-op
+ }
+
+ @Override
+ public void initializeFormulaTranslationX(CalculatorFormula formula) {
+ // no-op
+ }
+
+ @Override
+ public void initializeResultTranslationY(CalculatorResult result) {
+ // no-op
+ }
+
+ @Override
+ public void initializeResultTranslationX(CalculatorResult result) {
+ // no-op
+ }
+
+ @Override
+ public float getResultTranslationX(float yFraction) {
+ return 0;
+ }
+
+ @Override
+ public float getResultTranslationY(float yFraction) {
+ return 0;
+ }
+
+ @Override
+ public float getFormulaScale(float yFraction) {
+ return 1;
+ }
+
+ @Override
+ public float getDateTranslationY(float yFraction) {
+ return 0;
+ }
+
+ @Override
+ public float getHistoryElementTranslationY(float yFraction) {
+ return -mDisplayHeight * (1 - yFraction);
+ }
+
+ @Override
+ public int getFirstTranslatedViewHolderIndex() {
+ return 0;
+ }
+ }
}
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index d73534f..2e2fe21 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -17,14 +17,13 @@
package com.android.calculator2;
import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.VisibleForTesting;
+import android.text.Spannable;
import android.util.Log;
import com.hp.creals.CR;
@@ -38,11 +37,11 @@
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.Date;
import java.util.Random;
import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
/**
* This implements the calculator evaluation logic.
@@ -98,6 +97,15 @@
*/
public class Evaluator implements CalculatorExpr.ExprResolver {
+ private static Evaluator evaluator;
+
+ public static Evaluator getInstance(Calculator calculator) {
+ if (evaluator == null) {
+ evaluator = new Evaluator(calculator);
+ }
+ return evaluator;
+ }
+
public interface EvaluationListener {
/**
* Called if evaluation was explicitly cancelled or evaluation timed out.
@@ -1146,6 +1154,14 @@
}
/**
+ * Whether this expression has explicitly been evaluated (User pressed "=")
+ */
+ public boolean hasResult(long index) {
+ final ExprInfo ei = ensureExprIsCached(index);
+ return ei.mResultString != null;
+ }
+
+ /**
* Is a reevaluation still in progress?
*/
public boolean evaluationInProgress(long index) {
@@ -1731,6 +1747,10 @@
return getExpr(index).toSpannableStringBuilder(mActivity).toString();
}
+ public Spannable getExprAsSpannable(long index) {
+ return getExpr(index).toSpannableStringBuilder(mActivity);
+ }
+
/**
* Generate a String representation of all expressions in the database.
* Debugging only.
diff --git a/src/com/android/calculator2/HistoryAdapter.java b/src/com/android/calculator2/HistoryAdapter.java
index 25c7044..169e34f 100644
--- a/src/com/android/calculator2/HistoryAdapter.java
+++ b/src/com/android/calculator2/HistoryAdapter.java
@@ -23,7 +23,6 @@
import android.widget.TextView;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.List;
/**
@@ -34,20 +33,19 @@
private static final int EMPTY_VIEW_TYPE = 0;
private static final int HISTORY_VIEW_TYPE = 1;
- private final List<HistoryItem> mDataSet = new ArrayList<>();
- private String mCurrentExpression;
+ private final Evaluator mEvaluator;
+ /* Text/accessibility descriptor for the current expression item. */
+ private final String mCurrentExpressionDescription;
- public HistoryAdapter(int[] dataset, String currentExpression) {
- mCurrentExpression = currentExpression;
- // Temporary dataset
- final Calendar calendar = Calendar.getInstance();
- for (int i: dataset) {
- calendar.set(2016, 10, i);
- mDataSet.add(new HistoryItem(calendar.getTimeInMillis(), Integer.toString(i) + "+1",
- Integer.toString(i+1)));
- }
- // Temporary: just testing the empty view placeholder
- mDataSet.add(new HistoryItem());
+ private List<HistoryItem> mDataSet;
+
+ private boolean mHasCurrentExpression = true;
+
+ public HistoryAdapter(Calculator calculator, ArrayList<HistoryItem> dataSet,
+ String currentExpressionDescription) {
+ mEvaluator = Evaluator.getInstance(calculator);
+ mDataSet = dataSet;
+ mCurrentExpressionDescription = currentExpressionDescription;
}
@Override
@@ -64,21 +62,27 @@
}
@Override
- public void onBindViewHolder(HistoryAdapter.ViewHolder holder, int position) {
+ public void onBindViewHolder(final HistoryAdapter.ViewHolder holder, int position) {
final HistoryItem item = mDataSet.get(position);
if (item.isEmptyView()) {
return;
}
- if (!isCurrentExpressionItem(position)) {
+
+ holder.mFormula.setText(item.getFormula());
+ // Note: HistoryItems that are not the current expression will always have interesting ops.
+ holder.mResult.setEvaluator(mEvaluator, item.getId());
+ if (mHasCurrentExpression && position == 0) {
+ holder.mDate.setText(mCurrentExpressionDescription);
+ holder.mDate.setContentDescription(mCurrentExpressionDescription);
+ } else {
holder.mDate.setText(item.getDateString());
holder.mDate.setContentDescription(item.getDateDescription());
- } else {
- holder.mDate.setText(mCurrentExpression);
- holder.mDate.setContentDescription(mCurrentExpression);
}
- holder.mFormula.setText(item.getFormula());
- holder.mResult.setText(item.getResult());
+ }
+
+ public void setHasCurrentExpression(boolean has) {
+ mHasCurrentExpression = has;
}
@Override
@@ -88,12 +92,23 @@
holder.mFormula.setText(null);
holder.mResult.setText(null);
+ // TODO: Only cancel the calculation for the recycled view
+ mEvaluator.cancelAll(true);
+
super.onViewRecycled(holder);
}
@Override
public int getItemViewType(int position) {
- return mDataSet.get(position).isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
+ HistoryItem item = mDataSet.get(position);
+
+ // Continue to lazy-fill the data set
+ if (item == null) {
+ item = new HistoryItem(position, mEvaluator.getTimeStamp(position),
+ mEvaluator.getExprAsSpannable(position));
+ mDataSet.set(position, item);
+ }
+ return item.isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
}
@Override
@@ -101,8 +116,8 @@
return mDataSet.size();
}
- private boolean isCurrentExpressionItem(int position) {
- return position == mDataSet.size() - 1;
+ public void setDataSet(ArrayList<HistoryItem> dataSet) {
+ mDataSet = dataSet;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
diff --git a/src/com/android/calculator2/HistoryFragment.java b/src/com/android/calculator2/HistoryFragment.java
index 984ee82..a8f984d 100644
--- a/src/com/android/calculator2/HistoryFragment.java
+++ b/src/com/android/calculator2/HistoryFragment.java
@@ -30,6 +30,8 @@
import android.view.ViewGroup;
import android.widget.Toolbar;
+import java.util.ArrayList;
+
public class HistoryFragment extends Fragment {
public static final String TAG = "HistoryFragment";
@@ -48,7 +50,10 @@
@Override
public void onClosed() {
- mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
+ // TODO: only cancel historical evaluations
+ mEvaluator.cancelAll(true);
+
+ mDragController.resetAnimationInitialized();
}
@Override
@@ -78,13 +83,15 @@
private RecyclerView mRecyclerView;
private HistoryAdapter mAdapter;
+ private Evaluator mEvaluator;
+
+ private ArrayList<HistoryItem> mDataSet = new ArrayList<>();
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Temporary data
- final int[] testArray = {7};
- mAdapter = new HistoryAdapter(testArray,
+ mAdapter = new HistoryAdapter((Calculator) getActivity(), mDataSet,
getContext().getResources().getString(R.string.title_current_expression));
}
@@ -125,10 +132,47 @@
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- initializeController();
final DragLayout dragLayout = (DragLayout) getActivity().findViewById(R.id.drag_layout);
dragLayout.removeDragCallback(mDragCallback);
dragLayout.addDragCallback(mDragCallback);
+
+ mEvaluator = Evaluator.getInstance((Calculator) getActivity());
+
+ if (mEvaluator != null) {
+ initializeController();
+
+ final long maxIndex = mEvaluator.getMaxIndex();
+
+ final ArrayList<HistoryItem> newDataSet = new ArrayList<>();
+
+ if (!mEvaluator.getExpr(Evaluator.MAIN_INDEX).isEmpty()) {
+ // Add the current expression as the first element in the list (the layout is reversed
+ // and we want the current expression to be the last one in the recyclerview).
+ newDataSet.add(new HistoryItem(Evaluator.MAIN_INDEX, 0 /* millis*/,
+ mEvaluator.getExprAsSpannable(0)));
+ }
+ // We retrieve the current expression separately, so it's excluded from this loop.
+ // We lazy-fill, so just retrieve the first 25 expressions for now.
+ for (long i = Math.min(maxIndex, 25); i > 0; --i) {
+ final HistoryItem item = new HistoryItem(i, mEvaluator.getTimeStamp(i),
+ mEvaluator.getExprAsSpannable(i));
+ newDataSet.add(item);
+ }
+ for (long i = Math.max(maxIndex - 25, 0); i > 0; --i) {
+ newDataSet.add(null);
+ }
+ if (maxIndex == 0) {
+ newDataSet.add(new HistoryItem());
+ }
+ mDataSet = newDataSet;
+ mAdapter.setDataSet(mDataSet);
+ }
+
+ mAdapter.notifyDataSetChanged();
+
+ // Initialize the current expression element to dimensions that match the display to
+ // avoid flickering and scrolling when elements expand on drag start.
+ mDragController.animateViews(1.0f, mRecyclerView, mAdapter.getItemCount());
}
@Override
@@ -149,6 +193,8 @@
if (dragLayout != null) {
dragLayout.removeDragCallback(mDragCallback);
}
+
+ mEvaluator.cancelAll(true);
super.onDestroy();
}
@@ -161,9 +207,7 @@
mDragController.setToolbar(getActivity().findViewById(R.id.toolbar));
- // Initialize the current expression element to dimensions that match the display to avoid
- // flickering and scrolling when elements expand on drag start.
- mDragController.animateViews(1.0f, mRecyclerView, mAdapter.getItemCount());
+ mDragController.setEvaluator(mEvaluator);
}
private void clearHistory() {
diff --git a/src/com/android/calculator2/HistoryItem.java b/src/com/android/calculator2/HistoryItem.java
index 17f009f..63f46e9 100644
--- a/src/com/android/calculator2/HistoryItem.java
+++ b/src/com/android/calculator2/HistoryItem.java
@@ -16,6 +16,7 @@
package com.android.calculator2;
+import android.text.Spannable;
import android.text.format.DateFormat;
import java.text.SimpleDateFormat;
@@ -27,20 +28,24 @@
private static final String dateFormat = "EEEMMMd";
private static final String descriptionFormat = "EEEEMMMMd";
+ private long mId;
private Date mDate;
- private String mFormula;
- private String mResult;
+ private Spannable mFormula;
// This is true only for the "empty history" view.
private final boolean mIsEmpty;
- public HistoryItem(long millis, String formula, String result) {
+ public HistoryItem(long id, long millis, Spannable formula) {
+ mId = id;
mDate = new Date(millis);
mFormula = formula;
- mResult = result;
mIsEmpty = false;
}
+ public long getId() {
+ return mId;
+ }
+
public HistoryItem() {
mIsEmpty = true;
}
@@ -62,11 +67,7 @@
return new SimpleDateFormat(descriptionPattern, l).format(mDate);
}
- public String getFormula() {
+ public Spannable getFormula() {
return mFormula;
}
-
- public String getResult() {
- return mResult;
- }
}
\ No newline at end of file