summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/toolbar/TabCounter.java
blob: 1e0ca516b430b7efd62073a1c860cee124457541 (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
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.gecko.toolbar;

import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.R;
import org.mozilla.gecko.animation.Rotate3DAnimation;
import org.mozilla.gecko.widget.themed.ThemedTextSwitcher;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AlphaAnimation;
import android.view.animation.AnimationSet;
import android.widget.ViewSwitcher;

public class TabCounter extends ThemedTextSwitcher
                        implements ViewSwitcher.ViewFactory {

    private static final float CENTER_X = 0.5f;
    private static final float CENTER_Y = 1.25f;
    private static final int DURATION = 500;
    private static final float Z_DISTANCE = 200;

    private final AnimationSet mFlipInForward;
    private final AnimationSet mFlipInBackward;
    private final AnimationSet mFlipOutForward;
    private final AnimationSet mFlipOutBackward;
    private final LayoutInflater mInflater;
    private final int mLayoutId;

    private int mCount;
    public static final int MAX_VISIBLE_TABS = 99;
    public static final String SO_MANY_TABS_OPEN = "∞";

    private enum FadeMode {
        FADE_IN,
        FADE_OUT
    }

    public TabCounter(Context context, AttributeSet attrs) {
        super(context, attrs);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabCounter);
        mLayoutId = a.getResourceId(R.styleable.TabCounter_android_layout, R.layout.tabs_counter);
        a.recycle();

        mInflater = LayoutInflater.from(context);

        mFlipInForward = createAnimation(-90, 0, FadeMode.FADE_IN, -1 * Z_DISTANCE, false);
        mFlipInBackward = createAnimation(90, 0, FadeMode.FADE_IN, Z_DISTANCE, false);
        mFlipOutForward = createAnimation(0, -90, FadeMode.FADE_OUT, -1 * Z_DISTANCE, true);
        mFlipOutBackward = createAnimation(0, 90, FadeMode.FADE_OUT, Z_DISTANCE, true);

        removeAllViews();
        setFactory(this);

        if (Versions.feature16Plus) {
            // This adds the TextSwitcher to the a11y node tree, where we in turn
            // could make it return an empty info node. If we don't do this the
            // TextSwitcher's child TextViews get picked up, and we don't want
            // that since the tabs ImageButton is already properly labeled for
            // accessibility.
            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
            setAccessibilityDelegate(new View.AccessibilityDelegate() {
                    @Override
                    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {}
                });
        }
    }

    void setCountWithAnimation(int count) {
        // Don't animate from initial state
        if (mCount == 0) {
            setCount(count);
            return;
        }

        if (mCount == count) {
            return;
        }

        // don't animate if there are still over MAX_VISIBLE_TABS tabs open
        if (mCount > MAX_VISIBLE_TABS && count > MAX_VISIBLE_TABS) {
            mCount = count;
            return;
        }

        if (count < mCount) {
            setInAnimation(mFlipInBackward);
            setOutAnimation(mFlipOutForward);
        } else {
            setInAnimation(mFlipInForward);
            setOutAnimation(mFlipOutBackward);
        }

        // Eliminate screen artifact. Set explicit In/Out animation pair order. This will always
        // animate pair in In->Out child order, prevent alternating use of the Out->In case.
        setDisplayedChild(0);

        // Set In value, trigger animation to Out value
        setCurrentText(formatForDisplay(mCount));
        setText(formatForDisplay(count));

        mCount = count;
    }

    private String formatForDisplay(int count) {
        if (count > MAX_VISIBLE_TABS) {
            return SO_MANY_TABS_OPEN;
        }
        return String.valueOf(count);
    }

    void setCount(int count) {
        setCurrentText(formatForDisplay(count));
        mCount = count;
    }

    // Alpha animations in editing mode cause action bar corruption on the
    // Nexus 7 (bug 961749). As a workaround, skip these animations in editing
    // mode.
    void onEnterEditingMode() {
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            getChildAt(i).clearAnimation();
        }
    }

    private AnimationSet createAnimation(float startAngle, float endAngle,
                                         FadeMode fadeMode,
                                         float zEnd, boolean reverse) {
        final Context context = getContext();
        AnimationSet set = new AnimationSet(context, null);
        set.addAnimation(new Rotate3DAnimation(startAngle, endAngle, CENTER_X, CENTER_Y, zEnd, reverse));
        set.addAnimation(fadeMode == FadeMode.FADE_IN ? new AlphaAnimation(0.0f, 1.0f) :
                                                        new AlphaAnimation(1.0f, 0.0f));
        set.setDuration(DURATION);
        set.setInterpolator(context, android.R.anim.accelerate_interpolator);
        return set;
    }

    @Override
    public View makeView() {
        return mInflater.inflate(mLayoutId, null);
    }

}