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.animation.Animator;
20import android.app.Fragment;
21import android.os.Bundle;
22import android.support.v4.content.ContextCompat;
23import android.support.v7.widget.RecyclerView;
24import android.view.LayoutInflater;
25import android.view.MenuItem;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.Toolbar;
29
30import java.util.ArrayList;
31
32import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
33
34public class HistoryFragment extends Fragment implements DragLayout.DragCallback {
35
36    public static final String TAG = "HistoryFragment";
37    public static final String CLEAR_DIALOG_TAG = "clear";
38
39    private final DragController mDragController = new DragController();
40
41    private RecyclerView mRecyclerView;
42    private HistoryAdapter mAdapter;
43    private DragLayout mDragLayout;
44
45    private Evaluator mEvaluator;
46
47    private ArrayList<HistoryItem> mDataSet = new ArrayList<>();
48
49    private boolean mIsDisplayEmpty;
50
51    @Override
52    public void onCreate(Bundle savedInstanceState) {
53        super.onCreate(savedInstanceState);
54        mAdapter = new HistoryAdapter(mDataSet);
55    }
56
57    @Override
58    public View onCreateView(LayoutInflater inflater, ViewGroup container,
59            Bundle savedInstanceState) {
60        final View view = inflater.inflate(
61                R.layout.fragment_history, container, false /* attachToRoot */);
62
63        mDragLayout = (DragLayout) container.getRootView().findViewById(R.id.drag_layout);
64        mDragLayout.addDragCallback(this);
65
66        mRecyclerView = (RecyclerView) view.findViewById(R.id.history_recycler_view);
67        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
68            @Override
69            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
70                if (newState == SCROLL_STATE_DRAGGING) {
71                    stopActionModeOrContextMenu();
72                }
73                super.onScrollStateChanged(recyclerView, newState);
74            }
75        });
76
77        // The size of the RecyclerView is not affected by the adapter's contents.
78        mRecyclerView.setHasFixedSize(true);
79        mRecyclerView.setAdapter(mAdapter);
80
81        final Toolbar toolbar = (Toolbar) view.findViewById(R.id.history_toolbar);
82        toolbar.inflateMenu(R.menu.fragment_history);
83        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
84            @Override
85            public boolean onMenuItemClick(MenuItem item) {
86                if (item.getItemId() == R.id.menu_clear_history) {
87                    final Calculator calculator = (Calculator) getActivity();
88                    AlertDialogFragment.showMessageDialog(calculator, "" /* title */,
89                            getString(R.string.dialog_clear),
90                            getString(R.string.menu_clear_history),
91                            CLEAR_DIALOG_TAG);
92                    return true;
93                }
94                return onOptionsItemSelected(item);
95            }
96        });
97        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
98            @Override
99            public void onClick(View v) {
100                getActivity().onBackPressed();
101            }
102        });
103        return view;
104    }
105
106    @Override
107    public void onActivityCreated(Bundle savedInstanceState) {
108        super.onActivityCreated(savedInstanceState);
109
110        final Calculator activity = (Calculator) getActivity();
111        mEvaluator = Evaluator.getInstance(activity);
112        mAdapter.setEvaluator(mEvaluator);
113
114        final boolean isResultLayout = activity.isResultLayout();
115        final boolean isOneLine = activity.isOneLine();
116
117        // Snapshot display state here. For the rest of the lifecycle of this current
118        // HistoryFragment, this is what we will consider the display state.
119        // In rare cases, the display state can change after our adapter is initialized.
120        final CalculatorExpr mainExpr = mEvaluator.getExpr(Evaluator.MAIN_INDEX);
121        mIsDisplayEmpty = mainExpr == null || mainExpr.isEmpty();
122
123        initializeController(isResultLayout, isOneLine, mIsDisplayEmpty);
124
125        final long maxIndex = mEvaluator.getMaxIndex();
126
127        final ArrayList<HistoryItem> newDataSet = new ArrayList<>();
128
129        if (!mIsDisplayEmpty && !isResultLayout) {
130            // Add the current expression as the first element in the list (the layout is
131            // reversed and we want the current expression to be the last one in the
132            // RecyclerView).
133            // If we are in the result state, the result will animate to the last history
134            // element in the list and there will be no "Current Expression."
135            mEvaluator.copyMainToHistory();
136            newDataSet.add(new HistoryItem(Evaluator.HISTORY_MAIN_INDEX,
137                    System.currentTimeMillis(), mEvaluator.getExprAsSpannable(0)));
138        }
139        for (long i = 0; i < maxIndex; ++i) {
140            newDataSet.add(null);
141        }
142        final boolean isEmpty = newDataSet.isEmpty();
143        mRecyclerView.setBackgroundColor(ContextCompat.getColor(activity,
144                isEmpty ? R.color.empty_history_color : R.color.display_background_color));
145        if (isEmpty) {
146            newDataSet.add(new HistoryItem());
147        }
148        mDataSet = newDataSet;
149        mAdapter.setDataSet(mDataSet);
150        mAdapter.setIsResultLayout(isResultLayout);
151        mAdapter.setIsOneLine(activity.isOneLine());
152        mAdapter.setIsDisplayEmpty(mIsDisplayEmpty);
153        mAdapter.notifyDataSetChanged();
154    }
155
156    @Override
157    public void onStart() {
158        super.onStart();
159
160        final Calculator activity = (Calculator) getActivity();
161        mDragController.initializeAnimation(activity.isResultLayout(), activity.isOneLine(),
162                mIsDisplayEmpty);
163    }
164
165    @Override
166    public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
167        return mDragLayout.createAnimator(enter);
168    }
169
170    @Override
171    public void onDestroy() {
172        super.onDestroy();
173
174        if (mDragLayout != null) {
175            mDragLayout.removeDragCallback(this);
176        }
177
178        if (mEvaluator != null) {
179            // Note that the view is destroyed when the fragment backstack is popped, so
180            // these are essentially called when the DragLayout is closed.
181            mEvaluator.cancelNonMain();
182        }
183    }
184
185    private void initializeController(boolean isResult, boolean isOneLine, boolean isDisplayEmpty) {
186        mDragController.setDisplayFormula(
187                (CalculatorFormula) getActivity().findViewById(R.id.formula));
188        mDragController.setDisplayResult(
189                (CalculatorResult) getActivity().findViewById(R.id.result));
190        mDragController.setToolbar(getActivity().findViewById(R.id.toolbar));
191        mDragController.setEvaluator(mEvaluator);
192        mDragController.initializeController(isResult, isOneLine, isDisplayEmpty);
193    }
194
195    public boolean stopActionModeOrContextMenu() {
196        if (mRecyclerView == null) {
197            return false;
198        }
199        for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
200            final View view = mRecyclerView.getChildAt(i);
201            final HistoryAdapter.ViewHolder viewHolder =
202                    (HistoryAdapter.ViewHolder) mRecyclerView.getChildViewHolder(view);
203            if (viewHolder != null && viewHolder.getResult() != null
204                    && viewHolder.getResult().stopActionModeOrContextMenu()) {
205                return true;
206            }
207        }
208        return false;
209    }
210
211    /* Begin override DragCallback methods. */
212
213    @Override
214    public void onStartDraggingOpen() {
215        // no-op
216    }
217
218    @Override
219    public void onInstanceStateRestored(boolean isOpen) {
220        if (isOpen) {
221            mRecyclerView.setVisibility(View.VISIBLE);
222        }
223    }
224
225    @Override
226    public void whileDragging(float yFraction) {
227        if (isVisible() || isRemoving()) {
228            mDragController.animateViews(yFraction, mRecyclerView);
229        }
230    }
231
232    @Override
233    public boolean shouldCaptureView(View view, int x, int y) {
234        return !mRecyclerView.canScrollVertically(1 /* scrolling down */);
235    }
236
237    @Override
238    public int getDisplayHeight() {
239        return 0;
240    }
241
242    /* End override DragCallback methods. */
243}
244