Merge "Implement grouped headers." into ub-calculator-euler
diff --git a/res/layout/empty_history_view.xml b/res/layout/empty_history_view.xml
index cd5b759..7814b52 100644
--- a/res/layout/empty_history_view.xml
+++ b/res/layout/empty_history_view.xml
@@ -18,8 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/empty_history_view"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/empty_history_color">
+    android:layout_height="match_parent">
 
     <TextView
         android:layout_width="wrap_content"
diff --git a/res/layout/fragment_history.xml b/res/layout/fragment_history.xml
index c8c0bc8..0d8ed9b 100644
--- a/res/layout/fragment_history.xml
+++ b/res/layout/fragment_history.xml
@@ -40,6 +40,8 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:clipChildren="false"
+        android:clipToPadding="false"
+        android:paddingBottom="@dimen/history_divider_padding"
         app:layoutManager="LinearLayoutManager"
         app:reverseLayout="true" />
 </LinearLayout>
diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml
index 3baaf73..eaa95ad 100644
--- a/res/layout/history_item.xml
+++ b/res/layout/history_item.xml
@@ -19,16 +19,27 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:paddingTop="@dimen/history_divider_padding"
     android:clipChildren="false"
+    android:clipToPadding="false"
     android:orientation="vertical">
 
+    <View
+        android:id="@+id/history_divider"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/history_divider_padding"
+        android:layout_marginBottom="@dimen/history_divider_padding"
+        android:background="?android:attr/listDivider"
+        android:importantForAccessibility="no" />
+
     <TextView
         android:id="@+id/history_date"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:fontFamily="sans-serif-medium"
-        android:paddingStart="@dimen/history_item_text_padding_start"
-        android:paddingEnd="@dimen/history_item_text_padding_end"
+        android:paddingStart="@dimen/result_padding_start"
+        android:paddingEnd="@dimen/result_padding_end"
         android:text="@string/title_current_expression"
         android:textColor="?android:attr/colorAccent"
         android:textSize="14dp" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9de65d5..225cde3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,9 +20,9 @@
     <!-- The margin between the pad pages when displayed using a view pager. -->
     <dimen name="pad_page_margin">24dip</dimen>
 
+    <dimen name="history_divider_padding">14dip</dimen>
+
     <dimen name="history_item_text_padding_top">8dip</dimen>
     <dimen name="history_item_text_padding_bottom">16dip</dimen>
-    <dimen name="history_item_text_padding_start">16dip</dimen>
-    <dimen name="history_item_text_padding_end">24dip</dimen>
 
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 773c5a8..9e45a75 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -49,8 +49,9 @@
         <item name="android:layout_gravity">bottom|end</item>
         <item name="android:paddingTop">@dimen/history_item_text_padding_top</item>
         <item name="android:paddingBottom">@dimen/history_item_text_padding_bottom</item>
-        <item name="android:paddingStart">@dimen/history_item_text_padding_start</item>
-        <item name="android:paddingEnd">@dimen/history_item_text_padding_end</item>
+        <!-- Note: result_padding_start == formula_padding_start. -->
+        <item name="android:paddingStart">@dimen/result_padding_start</item>
+        <item name="android:paddingEnd">@dimen/result_padding_end</item>
         <item name="android:textSize">@dimen/result_textsize</item>
     </style>
 
diff --git a/src/com/android/calculator2/DragController.java b/src/com/android/calculator2/DragController.java
index 1c31767..ed983c5 100644
--- a/src/com/android/calculator2/DragController.java
+++ b/src/com/android/calculator2/DragController.java
@@ -51,6 +51,9 @@
     private int mResultStartColor;
     private int mResultEndColor;
 
+    // The padding at the bottom of the RecyclerView itself.
+    private int mBottomPaddingHeight;
+
     private boolean mAnimationInitialized;
 
     private AnimationController mAnimationController;
@@ -97,8 +100,11 @@
             final AlignedTextView formula = vh.getFormula();
             final CalculatorResult result = vh.getResult();
             final TextView date = vh.getDate();
+            final View divider = vh.getDivider();
 
             if (!mAnimationInitialized) {
+                mBottomPaddingHeight = recyclerView.getPaddingBottom();
+
                 mAnimationController.initializeScales(formula, result);
 
                 mAnimationController.initializeColorAnimators(formula, result);
@@ -140,6 +146,7 @@
                         mResultEndColor));
 
                 date.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
+                divider.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
             }
         } else if (EvaluatorStateUtils.isDisplayEmpty(mEvaluator)) {
             // There is no current expression but we still need to collect information
@@ -243,7 +250,8 @@
             // difference in result height.
             mFormulaTranslationY =
                     mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
-                            + mDisplayResult.getHeight() - result.getHeight();
+                            + mDisplayResult.getHeight() - result.getHeight()
+                            - mBottomPaddingHeight;
 
         }
 
@@ -254,7 +262,8 @@
 
         public void initializeResultTranslationY(CalculatorResult result) {
             // Baseline of result moves by the difference in result bottom padding.
-            mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+            mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom()
+            - mBottomPaddingHeight;
         }
 
         public void initializeResultTranslationX(CalculatorResult result) {
@@ -322,7 +331,8 @@
             // 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();
+                            + mDisplayResult.getHeight() - result.getHeight()
+                            - mBottomPaddingHeight;
         }
 
         @Override
@@ -334,8 +344,9 @@
         @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();
+            mResultTranslationY = mDisplayResult.getBottom() - result.getBottom()
+                   + mDisplayResult.getPaddingBottom() - result.getPaddingBottom()
+                   - mBottomPaddingHeight;
         }
 
         @Override
@@ -447,7 +458,7 @@
 
         @Override
         public float getHistoryElementTranslationY(float yFraction) {
-            return -mDisplayHeight * (1 - yFraction);
+            return -mDisplayHeight * (1 - yFraction) - mBottomPaddingHeight;
         }
 
         @Override
diff --git a/src/com/android/calculator2/HistoryAdapter.java b/src/com/android/calculator2/HistoryAdapter.java
index 429d0d7..68ad7bf 100644
--- a/src/com/android/calculator2/HistoryAdapter.java
+++ b/src/com/android/calculator2/HistoryAdapter.java
@@ -23,6 +23,7 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.List;
 
 /**
@@ -30,6 +31,8 @@
  */
 public class HistoryAdapter extends RecyclerView.Adapter<HistoryAdapter.ViewHolder> {
 
+    private static final String TAG = "HistoryAdapter";
+
     private static final int EMPTY_VIEW_TYPE = 0;
     private static final int HISTORY_VIEW_TYPE = 1;
 
@@ -37,6 +40,8 @@
     /* Text/accessibility descriptor for the current expression item. */
     private final String mCurrentExpressionDescription;
 
+    private final Calendar mCalendar = Calendar.getInstance();
+
     private List<HistoryItem> mDataSet;
 
     private boolean mIsResultLayout;
@@ -64,7 +69,7 @@
 
     @Override
     public void onBindViewHolder(final HistoryAdapter.ViewHolder holder, int position) {
-        final HistoryItem item = mDataSet.get(position);
+        final HistoryItem item = getItem(position);
 
         if (item.isEmptyView()) {
             return;
@@ -77,7 +82,17 @@
             holder.mDate.setText(mCurrentExpressionDescription);
             holder.mDate.setContentDescription(mCurrentExpressionDescription);
         } else {
-            holder.mDate.setText(item.getDateString());
+            // If the previous item occurred on the same date, the current item does not need
+            // a date header.
+            if (shouldShowHeader(position, item)) {
+                holder.mDate.setText(item.getDateString());
+                // Special case -- very first item should not have a divider above it.
+                holder.mDivider.setVisibility(position == getItemCount() - 1
+                        ? View.GONE : View.VISIBLE);
+            } else {
+                holder.mDate.setVisibility(View.GONE);
+                holder.mDivider.setVisibility(View.INVISIBLE);
+            }
         }
     }
 
@@ -88,6 +103,8 @@
         }
         mEvaluator.cancel(holder.getItemId(), true);
 
+        holder.mDate.setVisibility(View.VISIBLE);
+        holder.mDivider.setVisibility(View.VISIBLE);
         holder.mDate.setContentDescription(null);
         holder.mDate.setText(null);
         holder.mFormula.setText(null);
@@ -98,21 +115,12 @@
 
     @Override
     public long getItemId(int position) {
-        return mDataSet.get(position).getEvaluatorIndex();
+        return getItem(position).getEvaluatorIndex();
     }
 
     @Override
     public int getItemViewType(int position) {
-        HistoryItem item = mDataSet.get(position);
-
-        // Continue to lazy-fill the data set
-        if (item == null) {
-            final int evaluatorIndex = getEvaluatorIndex(position);
-            item = new HistoryItem(evaluatorIndex, mEvaluator.getTimeStamp(evaluatorIndex),
-                    mEvaluator.getExprAsSpannable(evaluatorIndex));
-            mDataSet.set(position, item);
-        }
-        return item.isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
+        return getItem(position).isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
     }
 
     @Override
@@ -124,17 +132,49 @@
         mDataSet = dataSet;
     }
 
+    public void setIsResultLayout(boolean isResult) {
+        mIsResultLayout = isResult;
+    }
+
     private int getEvaluatorIndex(int position) {
         if (EvaluatorStateUtils.isDisplayEmpty(mEvaluator) || mIsResultLayout) {
-            return (int) mEvaluator.getMaxIndex() - position;
+            return (int) (mEvaluator.getMaxIndex() - position);
         } else {
             // Account for the additional "Current Expression" with the +1.
-            return (int) mEvaluator.getMaxIndex() - position + 1;
+            return (int) (mEvaluator.getMaxIndex() - position + 1);
         }
     }
 
-    public void setIsResultLayout(boolean isResult) {
-        mIsResultLayout = isResult;
+    private boolean shouldShowHeader(int position, HistoryItem item) {
+        if (position == getItemCount() - 1) {
+            // First/oldest element should always show the header.
+            return true;
+        }
+        final HistoryItem prevItem = getItem(position + 1);
+        // We need to use Calendars to determine this because of Daylight Savings.
+        mCalendar.setTimeInMillis(item.getTimeInMillis());
+        final int year = mCalendar.get(Calendar.YEAR);
+        final int day = mCalendar.get(Calendar.DAY_OF_YEAR);
+        mCalendar.setTimeInMillis(prevItem.getTimeInMillis());
+        final int prevYear = mCalendar.get(Calendar.YEAR);
+        final int prevDay = mCalendar.get(Calendar.DAY_OF_YEAR);
+        return year != prevYear || day != prevDay;
+    }
+
+    /**
+     * Gets the HistoryItem from mDataSet, lazy-filling the dataSet if necessary.
+     */
+    private HistoryItem getItem(int position) {
+        HistoryItem item = mDataSet.get(position);
+        // Lazy-fill the data set.
+        if (item == null) {
+            final int evaluatorIndex = getEvaluatorIndex(position);
+            item = new HistoryItem(evaluatorIndex,
+                    mEvaluator.getTimeStamp(evaluatorIndex),
+                    mEvaluator.getExprAsSpannable(evaluatorIndex));
+            mDataSet.set(position, item);
+        }
+        return item;
     }
 
     public static class ViewHolder extends RecyclerView.ViewHolder {
@@ -142,6 +182,7 @@
         private TextView mDate;
         private AlignedTextView mFormula;
         private CalculatorResult mResult;
+        private View mDivider;
 
         public ViewHolder(View v, int viewType) {
             super(v);
@@ -151,6 +192,7 @@
             mDate = (TextView) v.findViewById(R.id.history_date);
             mFormula = (AlignedTextView) v.findViewById(R.id.history_formula);
             mResult = (CalculatorResult) v.findViewById(R.id.history_result);
+            mDivider = v.findViewById(R.id.history_divider);
         }
 
         public AlignedTextView getFormula() {
@@ -164,5 +206,9 @@
         public TextView getDate() {
             return mDate;
         }
+
+        public View getDivider() {
+            return mDivider;
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/calculator2/HistoryFragment.java b/src/com/android/calculator2/HistoryFragment.java
index 22adcd3..f65f1e9 100644
--- a/src/com/android/calculator2/HistoryFragment.java
+++ b/src/com/android/calculator2/HistoryFragment.java
@@ -20,7 +20,9 @@
 import android.animation.ObjectAnimator;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
+import android.graphics.Color;
 import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
@@ -96,6 +98,7 @@
         mRecyclerView = (RecyclerView) view.findViewById(R.id.history_recycler_view);
 
         // The size of the RecyclerView is not affected by the adapter's contents.
+        mRecyclerView.setHasFixedSize(true);
         mRecyclerView.setAdapter(mAdapter);
 
         final Toolbar toolbar = (Toolbar) view.findViewById(R.id.history_toolbar);
@@ -153,7 +156,11 @@
             for (long i = 0; i < maxIndex; ++i) {
                 newDataSet.add(null);
             }
-            if (newDataSet.isEmpty()) {
+            final boolean isEmpty = newDataSet.isEmpty();
+            mRecyclerView.setBackgroundColor(isEmpty
+                    ? ContextCompat.getColor(activity, R.color.empty_history_color)
+                    : Color.TRANSPARENT);
+            if (isEmpty) {
                 newDataSet.add(new HistoryItem());
             }
             mDataSet = newDataSet;
diff --git a/src/com/android/calculator2/HistoryItem.java b/src/com/android/calculator2/HistoryItem.java
index 5694cc7..f20d1a7 100644
--- a/src/com/android/calculator2/HistoryItem.java
+++ b/src/com/android/calculator2/HistoryItem.java
@@ -57,6 +57,10 @@
                 DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
     }
 
+    public long getTimeInMillis() {
+        return mTimeInMillis;
+    }
+
     public Spannable getFormula() {
         return mFormula;
     }