aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/stats/StatsUIHelper.java
blob: 441ed3050b177f9fe19dad541eebb5f33c78e063 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package org.wordpress.android.ui.stats;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.text.Spannable;
import android.text.style.URLSpan;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.widget.ExpandableListAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;

import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.util.DisplayUtils;

class StatsUIHelper {
    // Max number of rows to show in a stats fragment
    private static final int STATS_GROUP_MAX_ITEMS = 10;
    private static final int STATS_CHILD_MAX_ITEMS = 50;
    private static final int ANIM_DURATION = 150;

    // Used for tablet UI
    private static final int TABLET_720DP = 720;
    private static final int TABLET_600DP = 600;

    private static boolean isInLandscape(Activity act) {
        Display display = act.getWindowManager().getDefaultDisplay();
        Point point = new Point();
        display.getSize(point);
        return (point.y < point.x);
    }

    // Load more bars for 720DP tablets
    private static boolean shouldLoadMoreBars() {
        return (StatsUtils.getSmallestWidthDP() >= TABLET_720DP);
    }

    public static void reloadLinearLayout(Context ctx, ListAdapter adapter, LinearLayout linearLayout, int maxNumberOfItemsToshow) {
        if (ctx == null || linearLayout == null || adapter == null) {
            return;
        }

        // limit number of items to show otherwise it would cause performance issues on the LinearLayout
        int count = Math.min(adapter.getCount(), maxNumberOfItemsToshow);

        if (count == 0) {
            linearLayout.removeAllViews();
            return;
        }

        int numExistingViews = linearLayout.getChildCount();
        // remove excess views
        if (count < numExistingViews) {
            int numToRemove = numExistingViews - count;
            linearLayout.removeViews(count, numToRemove);
            numExistingViews = count;
        }

        int bgColor = Color.TRANSPARENT;
        for (int i = 0; i < count; i++) {
            final View view;
            // reuse existing view when possible
            if (i < numExistingViews) {
                View convertView = linearLayout.getChildAt(i);
                view = adapter.getView(i, convertView, linearLayout);
                view.setBackgroundColor(bgColor);
                setViewBackgroundWithoutResettingPadding(view, i == 0 ? 0 : R.drawable.stats_list_item_background);
            } else {
                view = adapter.getView(i, null, linearLayout);
                view.setBackgroundColor(bgColor);
                setViewBackgroundWithoutResettingPadding(view, i == 0 ? 0 : R.drawable.stats_list_item_background);
                linearLayout.addView(view);
            }
        }
        linearLayout.invalidate();
    }

    /**
     *
     * Padding information are reset when changing the background Drawable on a View.
     * The reason why setting an image resets the padding is because 9-patch images can encode padding.
     *
     * See http://stackoverflow.com/a/10469121 and
     * http://www.mail-archive.com/android-developers@googlegroups.com/msg09595.html
     *
     * @param v The view to apply the background resource
     * @param backgroundResId The resource ID
     */
    private static void setViewBackgroundWithoutResettingPadding(final View v, final int backgroundResId) {
        final int paddingBottom = v.getPaddingBottom(), paddingLeft = v.getPaddingLeft();
        final int paddingRight = v.getPaddingRight(), paddingTop = v.getPaddingTop();
        v.setBackgroundResource(backgroundResId);
        v.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    }

    public static void reloadLinearLayout(Context ctx, ListAdapter adapter, LinearLayout linearLayout) {
        reloadLinearLayout(ctx, adapter, linearLayout, STATS_GROUP_MAX_ITEMS);
    }

    public static void reloadGroupViews(final Context ctx,
                                        final ExpandableListAdapter mAdapter,
                                        final SparseBooleanArray mGroupIdToExpandedMap,
                                        final LinearLayout mLinearLayout) {
        reloadGroupViews(ctx, mAdapter, mGroupIdToExpandedMap, mLinearLayout, STATS_GROUP_MAX_ITEMS);
    }

    public static void reloadGroupViews(final Context ctx,
                                        final ExpandableListAdapter mAdapter,
                                        final SparseBooleanArray mGroupIdToExpandedMap,
                                        final LinearLayout mLinearLayout,
                                        final int maxNumberOfItemsToshow) {
        if (ctx == null || mLinearLayout == null || mAdapter == null || mGroupIdToExpandedMap == null) {
            return;
        }

        int groupCount = Math.min(mAdapter.getGroupCount(), maxNumberOfItemsToshow);
        if (groupCount == 0) {
            mLinearLayout.removeAllViews();
            return;
        }

        int numExistingGroupViews = mLinearLayout.getChildCount();

        // remove excess views
        if (groupCount < numExistingGroupViews) {
            int numToRemove = numExistingGroupViews - groupCount;
            mLinearLayout.removeViews(groupCount, numToRemove);
            numExistingGroupViews = groupCount;
        }

        int bgColor = Color.TRANSPARENT;

        // add each group
        for (int i = 0; i < groupCount; i++) {
            boolean isExpanded = mGroupIdToExpandedMap.get(i);

            // reuse existing view when possible
            final View groupView;
            if (i < numExistingGroupViews) {
                View convertView = mLinearLayout.getChildAt(i);
                groupView = mAdapter.getGroupView(i, isExpanded, convertView, mLinearLayout);
                groupView.setBackgroundColor(bgColor);
                setViewBackgroundWithoutResettingPadding(groupView, i == 0 ? 0 : R.drawable.stats_list_item_background);
            } else {
                groupView = mAdapter.getGroupView(i, isExpanded, null, mLinearLayout);
                groupView.setBackgroundColor(bgColor);
                setViewBackgroundWithoutResettingPadding(groupView, i == 0 ? 0 : R.drawable.stats_list_item_background);
                mLinearLayout.addView(groupView);
            }

            // groupView is recycled, we need to reset it to the original state.
            ViewGroup childContainer = (ViewGroup) groupView.findViewById(R.id.layout_child_container);
            if (childContainer != null) {
                childContainer.setVisibility(View.GONE);
            }
            // Remove any other prev animations set on the chevron
            final ImageView chevron = (ImageView) groupView.findViewById(R.id.stats_list_cell_chevron);
            if (chevron != null) {
                chevron.clearAnimation();
                chevron.setImageResource(R.drawable.stats_chevron_right);
            }

            // add children if this group is expanded
            if (isExpanded) {
                StatsUIHelper.showChildViews(mAdapter, mLinearLayout, i, groupView, false);
            }

            // toggle expand/collapse when group view is tapped
            final int groupPosition = i;
            groupView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mAdapter.getChildrenCount(groupPosition) == 0) {
                        return;
                    }
                    boolean shouldExpand = !mGroupIdToExpandedMap.get(groupPosition);
                    mGroupIdToExpandedMap.put(groupPosition, shouldExpand);
                    if (shouldExpand) {
                        StatsUIHelper.showChildViews(mAdapter, mLinearLayout, groupPosition, groupView, true);
                    } else {
                        StatsUIHelper.hideChildViews(groupView, groupPosition, true);
                    }
                }
            });
        }
    }

    /*
     * interpolator for all expand/collapse animations
    */
    private static Interpolator getInterpolator() {
        return new AccelerateInterpolator();
    }

    private static void hideChildViews(View groupView, int groupPosition, boolean animate) {
        final ViewGroup childContainer = (ViewGroup) groupView.findViewById(R.id.layout_child_container);
        if (childContainer == null) {
            return;
        }

        if (childContainer.getVisibility() != View.GONE) {
            if (animate) {
                Animation expand = new ScaleAnimation(1.0f, 1.0f, 1.0f, 0.0f);
                expand.setDuration(ANIM_DURATION);
                expand.setInterpolator(getInterpolator());
                expand.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) { }
                    @Override
                    public void onAnimationEnd(Animation animation) {
                        childContainer.setVisibility(View.GONE);
                    }
                    @Override
                    public void onAnimationRepeat(Animation animation) { }
                });
                childContainer.startAnimation(expand);
            } else {
                childContainer.setVisibility(View.GONE);
            }
        }
        StatsUIHelper.setGroupChevron(false, groupView, groupPosition, animate);
    }

    /*
     * shows the correct up/down chevron for the passed group
     */
    private static void setGroupChevron(final boolean isGroupExpanded, View groupView, int groupPosition, boolean animate) {
        final ImageView chevron = (ImageView) groupView.findViewById(R.id.stats_list_cell_chevron);
        if (chevron == null) {
            return;
        }
        if (isGroupExpanded) {
            // change the background of the parent
            setViewBackgroundWithoutResettingPadding(groupView, R.drawable.stats_list_item_expanded_background);
        } else {
            setViewBackgroundWithoutResettingPadding(groupView, groupPosition == 0 ? 0 : R.drawable.stats_list_item_background);
        }

        chevron.clearAnimation(); // Remove any other prev animations set on the chevron
        if (animate) {
            // make sure we start with the correct chevron for the prior state before animating it
            chevron.setImageResource(isGroupExpanded ? R.drawable.stats_chevron_right : R.drawable.stats_chevron_down);
            float start = (isGroupExpanded ? 0.0f : 0.0f);
            float end = (isGroupExpanded ? 90.0f : -90.0f);
            Animation rotate = new RotateAnimation(start, end, Animation.RELATIVE_TO_SELF, 0.5f,
                    Animation.RELATIVE_TO_SELF, 0.5f);
            rotate.setDuration(ANIM_DURATION);
            rotate.setInterpolator(getInterpolator());
            rotate.setFillAfter(true);
            chevron.startAnimation(rotate);
        } else {
            chevron.setImageResource(isGroupExpanded ? R.drawable.stats_chevron_down : R.drawable.stats_chevron_right);
        }
    }

    private static void showChildViews(ExpandableListAdapter mAdapter, LinearLayout mLinearLayout,
                                       int groupPosition, View groupView, boolean animate) {
        int childCount = Math.min(mAdapter.getChildrenCount(groupPosition), STATS_CHILD_MAX_ITEMS);
        if (childCount == 0) {
            return;
        }

        final ViewGroup childContainer = (ViewGroup) groupView.findViewById(R.id.layout_child_container);
        if (childContainer == null) {
            return;
        }

        int numExistingViews = childContainer.getChildCount();
        if (childCount < numExistingViews) {
            int numToRemove = numExistingViews - childCount;
            childContainer.removeViews(childCount, numToRemove);
            numExistingViews = childCount;
        }

        for (int i = 0; i < childCount; i++) {
            boolean isLastChild = (i == childCount - 1);
            if (i < numExistingViews) {
                View convertView = childContainer.getChildAt(i);
                mAdapter.getChildView(groupPosition, i, isLastChild, convertView, mLinearLayout);
            } else {
                View childView = mAdapter.getChildView(groupPosition, i, isLastChild, null, mLinearLayout);
                // remove the right/left padding so the child total aligns to left
                childView.setPadding(0,
                        childView.getPaddingTop(),
                        0,
                        isLastChild ? 0 : childView.getPaddingBottom()); // No padding bottom on last child
                setViewBackgroundWithoutResettingPadding(childView, R.drawable.stats_list_item_child_background);
                childContainer.addView(childView);
            }
        }

        if (childContainer.getVisibility() != View.VISIBLE) {
            if (animate) {
                Animation expand = new ScaleAnimation(1.0f, 1.0f, 0.0f, 1.0f);
                expand.setDuration(ANIM_DURATION);
                expand.setInterpolator(getInterpolator());
                childContainer.startAnimation(expand);
            }
            childContainer.setVisibility(View.VISIBLE);
        }

        StatsUIHelper.setGroupChevron(true, groupView, groupPosition, animate);
    }

    /**
     * Removes URL underlines in a string by replacing URLSpan occurrences by
     * URLSpanNoUnderline objects.
     *
     * @param pText A Spannable object. For example, a TextView casted as
     *               Spannable.
     */
    public static void removeUnderlines(Spannable pText) {
        URLSpan[] spans = pText.getSpans(0, pText.length(), URLSpan.class);

        for(URLSpan span:spans) {
            int start = pText.getSpanStart(span);
            int end = pText.getSpanEnd(span);
            pText.removeSpan(span);
            span = new URLSpanNoUnderline(span.getURL());
            pText.setSpan(span, start, end, 0);
        }
    }

    public static int getNumOfBarsToShow() {
        if (StatsUtils.getSmallestWidthDP() >= TABLET_720DP && DisplayUtils.isLandscape(WordPress.getContext())) {
            return 15;
        } else if (StatsUtils.getSmallestWidthDP() >= TABLET_600DP) {
            return 10;
        } else {
            return 7;
        }
    }
}