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 
17 package com.android.calculator2;
18 
19 import android.animation.Animator;
20 import android.app.Fragment;
21 import android.os.Bundle;
22 import android.support.v4.content.ContextCompat;
23 import android.support.v7.widget.RecyclerView;
24 import android.view.LayoutInflater;
25 import android.view.MenuItem;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.Toolbar;
29 
30 import java.util.ArrayList;
31 
32 import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
33 
34 public 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
onCreate(Bundle savedInstanceState)52     public void onCreate(Bundle savedInstanceState) {
53         super.onCreate(savedInstanceState);
54         mAdapter = new HistoryAdapter(mDataSet);
55     }
56 
57     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onActivityCreated(Bundle savedInstanceState)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
onStart()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
onCreateAnimator(int transit, boolean enter, int nextAnim)166     public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
167         return mDragLayout.createAnimator(enter);
168     }
169 
170     @Override
onDestroy()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 
initializeController(boolean isResult, boolean isOneLine, boolean isDisplayEmpty)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 
stopActionModeOrContextMenu()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
onStartDraggingOpen()214     public void onStartDraggingOpen() {
215         // no-op
216     }
217 
218     @Override
onInstanceStateRestored(boolean isOpen)219     public void onInstanceStateRestored(boolean isOpen) {
220         if (isOpen) {
221             mRecyclerView.setVisibility(View.VISIBLE);
222         }
223     }
224 
225     @Override
whileDragging(float yFraction)226     public void whileDragging(float yFraction) {
227         if (isVisible() || isRemoving()) {
228             mDragController.animateViews(yFraction, mRecyclerView);
229         }
230     }
231 
232     @Override
shouldCaptureView(View view, int x, int y)233     public boolean shouldCaptureView(View view, int x, int y) {
234         return !mRecyclerView.canScrollVertically(1 /* scrolling down */);
235     }
236 
237     @Override
getDisplayHeight()238     public int getDisplayHeight() {
239         return 0;
240     }
241 
242     /* End override DragCallback methods. */
243 }
244