summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
blob: d5362f1f10c601129b26466005e7de38a5a95e42 (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
/* -*- 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.tabs;

import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;

public abstract class TabsLayout extends RecyclerView
        implements TabsPanel.TabsLayout,
        Tabs.OnTabsChangedListener,
        RecyclerViewClickSupport.OnItemClickListener,
        TabsTouchHelperCallback.DismissListener {

    private static final String LOGTAG = "Gecko" + TabsLayout.class.getSimpleName();

    private final boolean isPrivate;
    private TabsPanel tabsPanel;
    private final TabsLayoutRecyclerAdapter tabsAdapter;

    public TabsLayout(Context context, AttributeSet attrs, int itemViewLayoutResId) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
        isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
        a.recycle();

        tabsAdapter = new TabsLayoutRecyclerAdapter(context, itemViewLayoutResId, isPrivate,
                /* close on click listener */
                new Button.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // The view here is the close button, which has a reference
                        // to the parent TabsLayoutItemView in its tag, hence the getTag() call.
                        TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
                        closeTab(itemView);
                    }
                });
        setAdapter(tabsAdapter);

        RecyclerViewClickSupport.addTo(this).setOnItemClickListener(this);

        setRecyclerListener(new RecyclerListener() {
            @Override
            public void onViewRecycled(RecyclerView.ViewHolder holder) {
                final TabsLayoutItemView itemView = (TabsLayoutItemView) holder.itemView;
                itemView.setThumbnail(null);
                itemView.setCloseVisible(true);
            }
        });
    }

    @Override
    public void setTabsPanel(TabsPanel panel) {
        tabsPanel = panel;
    }

    @Override
    public void show() {
        setVisibility(View.VISIBLE);
        Tabs.getInstance().refreshThumbnails();
        Tabs.registerOnTabsChangedListener(this);
        refreshTabsData();
    }

    @Override
    public void hide() {
        setVisibility(View.GONE);
        Tabs.unregisterOnTabsChangedListener(this);
        GeckoAppShell.notifyObservers("Tab:Screenshot:Cancel", "");
        tabsAdapter.clear();
    }

    @Override
    public boolean shouldExpand() {
        return true;
    }

    protected void autoHidePanel() {
        tabsPanel.autoHidePanel();
    }

    @Override
    public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
        switch (msg) {
            case ADDED:
                final int tabIndex = Integer.parseInt(data);
                tabsAdapter.notifyTabInserted(tab, tabIndex);
                if (addAtIndexRequiresScroll(tabIndex)) {
                    // (The current Tabs implementation updates the SELECTED tab *after* this
                    // call to ADDED, so don't just call updateSelectedPosition().)
                    scrollToPosition(tabIndex);
                }
                break;

            case CLOSED:
                if (tab.isPrivate() == isPrivate && tabsAdapter.getItemCount() > 0) {
                    tabsAdapter.removeTab(tab);
                }
                break;

            case SELECTED:
            case UNSELECTED:
            case THUMBNAIL:
            case TITLE:
            case RECORDING_CHANGE:
            case AUDIO_PLAYING_CHANGE:
                tabsAdapter.notifyTabChanged(tab);
                break;
        }
    }

    // Addition of a tab at selected positions (dependent on LayoutManager) will result in a tab
    // being added out of view - return true if index is such a position.
    abstract protected boolean addAtIndexRequiresScroll(int index);

    @Override
    public void onItemClicked(RecyclerView recyclerView, int position, View v) {
        final TabsLayoutItemView item = (TabsLayoutItemView) v;
        final int tabId = item.getTabId();
        final Tab tab = Tabs.getInstance().selectTab(tabId);
        if (tab == null) {
            // The tab that was clicked no longer exists in the tabs list (which can happen if you
            // tap on a tab while its remove animation is running), so ignore the click.
            return;
        }

        autoHidePanel();
        Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
    }

    // Updates the selected position in the list so that it will be scrolled to the right place.
    private void updateSelectedPosition() {
        final int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
        if (selected != NO_POSITION) {
            scrollToPosition(selected);
        }
    }

    private void refreshTabsData() {
        // Store a different copy of the tabs, so that we don't have to worry about
        // accidentally updating it on the wrong thread.
        final ArrayList<Tab> tabData = new ArrayList<>();
        final Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();

        for (final Tab tab : allTabs) {
            if (tab.isPrivate() == isPrivate) {
                tabData.add(tab);
            }
        }

        tabsAdapter.setTabs(tabData);
        updateSelectedPosition();
    }

    private void closeTab(View view) {
        final TabsLayoutItemView itemView = (TabsLayoutItemView) view;
        final Tab tab = getTabForView(itemView);
        if (tab == null) {
            // We can be null here if this is the second closeTab call resulting from a sufficiently
            // fast double tap on the close tab button.
            return;
        }

        final boolean closingLastTab = tabsAdapter.getItemCount() == 1;
        Tabs.getInstance().closeTab(tab, true);
        if (closingLastTab) {
            autoHidePanel();
        }
    }

    protected void closeAllTabs() {
        final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
        for (final Tab tab : tabs) {
            // In the normal panel we want to close all tabs (both private and normal),
            // but in the private panel we only want to close private tabs.
            if (!isPrivate || tab.isPrivate()) {
                Tabs.getInstance().closeTab(tab, false);
            }
        }
    }

    @Override
    public void onItemDismiss(View view) {
        closeTab(view);
    }

    private Tab getTabForView(View view) {
        if (view == null) {
            return null;
        }
        return Tabs.getInstance().getTab(((TabsLayoutItemView) view).getTabId());
    }

    @Override
    public void setEmptyView(View emptyView) {
        // We never display an empty view.
    }

    @Override
    abstract public void closeAll();
}