1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calculator2;
18
19import android.support.v7.widget.RecyclerView;
20import android.view.LayoutInflater;
21import android.view.View;
22import android.view.ViewGroup;
23import android.widget.TextView;
24
25import java.util.ArrayList;
26import java.util.Calendar;
27import java.util.List;
28
29/**
30 * Adapter for RecyclerView of HistoryItems.
31 */
32public class HistoryAdapter extends RecyclerView.Adapter<HistoryAdapter.ViewHolder> {
33
34    private static final String TAG = "HistoryAdapter";
35
36    private static final int EMPTY_VIEW_TYPE = 0;
37    public static final int HISTORY_VIEW_TYPE = 1;
38
39    private Evaluator mEvaluator;
40
41    private final Calendar mCalendar = Calendar.getInstance();
42
43    private List<HistoryItem> mDataSet;
44
45    private boolean mIsResultLayout;
46    private boolean mIsOneLine;
47    private boolean mIsDisplayEmpty;
48
49    public HistoryAdapter(ArrayList<HistoryItem> dataSet) {
50        mDataSet = dataSet;
51        setHasStableIds(true);
52    }
53
54    @Override
55    public HistoryAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
56        final View v;
57        if (viewType == HISTORY_VIEW_TYPE) {
58            v = LayoutInflater.from(parent.getContext())
59                    .inflate(R.layout.history_item, parent, false);
60        } else {
61            v = LayoutInflater.from(parent.getContext())
62                    .inflate(R.layout.empty_history_view, parent, false);
63        }
64        return new ViewHolder(v, viewType);
65    }
66
67    @Override
68    public void onBindViewHolder(final HistoryAdapter.ViewHolder holder, int position) {
69        final HistoryItem item = getItem(position);
70
71        if (item.isEmptyView()) {
72            return;
73        }
74
75        holder.mFormula.setText(item.getFormula());
76        // Note: HistoryItems that are not the current expression will always have interesting ops.
77        holder.mResult.setEvaluator(mEvaluator, item.getEvaluatorIndex());
78        if (item.getEvaluatorIndex() == Evaluator.HISTORY_MAIN_INDEX) {
79            holder.mDate.setText(R.string.title_current_expression);
80            holder.mResult.setVisibility(mIsOneLine ? View.GONE : View.VISIBLE);
81        } else {
82            // If the previous item occurred on the same date, the current item does not need
83            // a date header.
84            if (shouldShowHeader(position, item)) {
85                holder.mDate.setText(item.getDateString());
86                // Special case -- very first item should not have a divider above it.
87                holder.mDivider.setVisibility(position == getItemCount() - 1
88                        ? View.GONE : View.VISIBLE);
89            } else {
90                holder.mDate.setVisibility(View.GONE);
91                holder.mDivider.setVisibility(View.INVISIBLE);
92            }
93        }
94    }
95
96    @Override
97    public void onViewRecycled(ViewHolder holder) {
98        if (holder.getItemViewType() == EMPTY_VIEW_TYPE) {
99            return;
100        }
101        mEvaluator.cancel(holder.getItemId(), true);
102
103        holder.mDate.setVisibility(View.VISIBLE);
104        holder.mDivider.setVisibility(View.VISIBLE);
105        holder.mDate.setText(null);
106        holder.mFormula.setText(null);
107        holder.mResult.setText(null);
108
109        super.onViewRecycled(holder);
110    }
111
112    @Override
113    public long getItemId(int position) {
114        return getItem(position).getEvaluatorIndex();
115    }
116
117    @Override
118    public int getItemViewType(int position) {
119        return getItem(position).isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
120    }
121
122    @Override
123    public int getItemCount() {
124        return mDataSet.size();
125    }
126
127    public void setDataSet(ArrayList<HistoryItem> dataSet) {
128        mDataSet = dataSet;
129    }
130
131    public void setIsResultLayout(boolean isResult) {
132        mIsResultLayout = isResult;
133    }
134
135    public void setIsOneLine(boolean isOneLine) {
136        mIsOneLine = isOneLine;
137    }
138
139    public void setIsDisplayEmpty(boolean isDisplayEmpty) {
140        mIsDisplayEmpty = isDisplayEmpty;
141    }
142
143    public void setEvaluator(Evaluator evaluator) {
144        mEvaluator = evaluator;
145    }
146
147    private int getEvaluatorIndex(int position) {
148        if (mIsDisplayEmpty || mIsResultLayout) {
149            return (int) (mEvaluator.getMaxIndex() - position);
150        } else {
151            // Account for the additional "Current Expression" with the +1.
152            return (int) (mEvaluator.getMaxIndex() - position + 1);
153        }
154    }
155
156    private boolean shouldShowHeader(int position, HistoryItem item) {
157        if (position == getItemCount() - 1) {
158            // First/oldest element should always show the header.
159            return true;
160        }
161        final HistoryItem prevItem = getItem(position + 1);
162        // We need to use Calendars to determine this because of Daylight Savings.
163        mCalendar.setTimeInMillis(item.getTimeInMillis());
164        final int year = mCalendar.get(Calendar.YEAR);
165        final int day = mCalendar.get(Calendar.DAY_OF_YEAR);
166        mCalendar.setTimeInMillis(prevItem.getTimeInMillis());
167        final int prevYear = mCalendar.get(Calendar.YEAR);
168        final int prevDay = mCalendar.get(Calendar.DAY_OF_YEAR);
169        return year != prevYear || day != prevDay;
170    }
171
172    /**
173     * Gets the HistoryItem from mDataSet, lazy-filling the dataSet if necessary.
174     */
175    private HistoryItem getItem(int position) {
176        HistoryItem item = mDataSet.get(position);
177        // Lazy-fill the data set.
178        if (item == null) {
179            final int evaluatorIndex = getEvaluatorIndex(position);
180            item = new HistoryItem(evaluatorIndex,
181                    mEvaluator.getTimeStamp(evaluatorIndex),
182                    mEvaluator.getExprAsSpannable(evaluatorIndex));
183            mDataSet.set(position, item);
184        }
185        return item;
186    }
187
188    public static class ViewHolder extends RecyclerView.ViewHolder {
189
190        private TextView mDate;
191        private AlignedTextView mFormula;
192        private CalculatorResult mResult;
193        private View mDivider;
194
195        public ViewHolder(View v, int viewType) {
196            super(v);
197            if (viewType == EMPTY_VIEW_TYPE) {
198                return;
199            }
200            mDate = (TextView) v.findViewById(R.id.history_date);
201            mFormula = (AlignedTextView) v.findViewById(R.id.history_formula);
202            mResult = (CalculatorResult) v.findViewById(R.id.history_result);
203            mDivider = v.findViewById(R.id.history_divider);
204        }
205
206        public AlignedTextView getFormula() {
207            return mFormula;
208        }
209
210        public CalculatorResult getResult() {
211            return mResult;
212        }
213
214        public TextView getDate() {
215            return mDate;
216        }
217
218        public View getDivider() {
219            return mDivider;
220        }
221    }
222}