1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/TabsPanel.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,461 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import org.mozilla.gecko.Telemetry; 1.12 +import org.mozilla.gecko.TelemetryContract; 1.13 +import org.mozilla.gecko.animation.PropertyAnimator; 1.14 +import org.mozilla.gecko.animation.ViewHelper; 1.15 +import org.mozilla.gecko.widget.IconTabWidget; 1.16 + 1.17 +import android.content.Context; 1.18 +import android.content.res.Resources; 1.19 +import android.graphics.Rect; 1.20 +import android.os.Build; 1.21 +import android.util.AttributeSet; 1.22 +import android.view.LayoutInflater; 1.23 +import android.view.View; 1.24 +import android.view.ViewGroup; 1.25 +import android.widget.Button; 1.26 +import android.widget.FrameLayout; 1.27 +import android.widget.ImageButton; 1.28 +import android.widget.LinearLayout; 1.29 +import android.widget.RelativeLayout; 1.30 + 1.31 +public class TabsPanel extends LinearLayout 1.32 + implements LightweightTheme.OnChangeListener, 1.33 + IconTabWidget.OnTabChangedListener { 1.34 + private static final String LOGTAG = "GeckoTabsPanel"; 1.35 + 1.36 + public static enum Panel { 1.37 + NORMAL_TABS, 1.38 + PRIVATE_TABS, 1.39 + REMOTE_TABS 1.40 + } 1.41 + 1.42 + public static interface PanelView { 1.43 + public ViewGroup getLayout(); 1.44 + public void setTabsPanel(TabsPanel panel); 1.45 + public void show(); 1.46 + public void hide(); 1.47 + public boolean shouldExpand(); 1.48 + } 1.49 + 1.50 + public static interface TabsLayoutChangeListener { 1.51 + public void onTabsLayoutChange(int width, int height); 1.52 + } 1.53 + 1.54 + private Context mContext; 1.55 + private final GeckoApp mActivity; 1.56 + private final LightweightTheme mTheme; 1.57 + private RelativeLayout mHeader; 1.58 + private TabsListContainer mTabsContainer; 1.59 + private PanelView mPanel; 1.60 + private PanelView mPanelNormal; 1.61 + private PanelView mPanelPrivate; 1.62 + private PanelView mPanelRemote; 1.63 + private RelativeLayout mFooter; 1.64 + private TabsLayoutChangeListener mLayoutChangeListener; 1.65 + 1.66 + private IconTabWidget mTabWidget; 1.67 + private static ImageButton mAddTab; 1.68 + 1.69 + private Panel mCurrentPanel; 1.70 + private boolean mIsSideBar; 1.71 + private boolean mVisible; 1.72 + 1.73 + public TabsPanel(Context context, AttributeSet attrs) { 1.74 + super(context, attrs); 1.75 + mContext = context; 1.76 + mActivity = (GeckoApp) context; 1.77 + mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme(); 1.78 + 1.79 + setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, 1.80 + LinearLayout.LayoutParams.FILL_PARENT)); 1.81 + setOrientation(LinearLayout.VERTICAL); 1.82 + 1.83 + mCurrentPanel = Panel.NORMAL_TABS; 1.84 + mVisible = false; 1.85 + 1.86 + mIsSideBar = false; 1.87 + 1.88 + LayoutInflater.from(context).inflate(R.layout.tabs_panel, this); 1.89 + initialize(); 1.90 + } 1.91 + 1.92 + private void initialize() { 1.93 + mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header); 1.94 + mTabsContainer = (TabsListContainer) findViewById(R.id.tabs_container); 1.95 + 1.96 + mPanelNormal = (PanelView) findViewById(R.id.normal_tabs); 1.97 + mPanelNormal.setTabsPanel(this); 1.98 + 1.99 + mPanelPrivate = (PanelView) findViewById(R.id.private_tabs); 1.100 + mPanelPrivate.setTabsPanel(this); 1.101 + 1.102 + mPanelRemote = (PanelView) findViewById(R.id.synced_tabs); 1.103 + mPanelRemote.setTabsPanel(this); 1.104 + 1.105 + mFooter = (RelativeLayout) findViewById(R.id.tabs_panel_footer); 1.106 + 1.107 + mAddTab = (ImageButton) findViewById(R.id.add_tab); 1.108 + mAddTab.setOnClickListener(new Button.OnClickListener() { 1.109 + @Override 1.110 + public void onClick(View v) { 1.111 + TabsPanel.this.addTab(); 1.112 + } 1.113 + }); 1.114 + 1.115 + mTabWidget = (IconTabWidget) findViewById(R.id.tab_widget); 1.116 + 1.117 + mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal); 1.118 + mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private); 1.119 + 1.120 + if (!GeckoProfile.get(mContext).inGuestMode()) { 1.121 + mTabWidget.addTab(R.drawable.tabs_synced, R.string.tabs_synced); 1.122 + } 1.123 + 1.124 + mTabWidget.setTabSelectionListener(this); 1.125 + } 1.126 + 1.127 + private void addTab() { 1.128 + if (mCurrentPanel == Panel.NORMAL_TABS) { 1.129 + Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_tab"); 1.130 + mActivity.addTab(); 1.131 + } else { 1.132 + Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_private_tab"); 1.133 + mActivity.addPrivateTab(); 1.134 + } 1.135 + 1.136 + mActivity.autoHideTabs(); 1.137 + } 1.138 + 1.139 + @Override 1.140 + public void onTabChanged(int index) { 1.141 + if (index == 0) 1.142 + show(Panel.NORMAL_TABS, false); 1.143 + else if (index == 1) 1.144 + show(Panel.PRIVATE_TABS, false); 1.145 + else 1.146 + show(Panel.REMOTE_TABS, false); 1.147 + } 1.148 + 1.149 + private static int getTabContainerHeight(TabsListContainer listContainer) { 1.150 + Resources resources = listContainer.getContext().getResources(); 1.151 + 1.152 + PanelView panelView = listContainer.getCurrentPanelView(); 1.153 + if (panelView != null && !panelView.shouldExpand()) { 1.154 + return resources.getDimensionPixelSize(R.dimen.tabs_tray_horizontal_height); 1.155 + } 1.156 + 1.157 + int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); 1.158 + int screenHeight = resources.getDisplayMetrics().heightPixels; 1.159 + 1.160 + Rect windowRect = new Rect(); 1.161 + listContainer.getWindowVisibleDisplayFrame(windowRect); 1.162 + int windowHeight = windowRect.bottom - windowRect.top; 1.163 + 1.164 + // The web content area should have at least 1.5x the height of the action bar. 1.165 + // The tabs panel shouldn't take less than 50% of the screen height and can take 1.166 + // up to 80% of the window height. 1.167 + return (int) Math.max(screenHeight * 0.5f, 1.168 + Math.min(windowHeight - 2.5f * actionBarHeight, windowHeight * 0.8f) - actionBarHeight); 1.169 + } 1.170 + 1.171 + @Override 1.172 + public void onAttachedToWindow() { 1.173 + super.onAttachedToWindow(); 1.174 + mTheme.addListener(this); 1.175 + } 1.176 + 1.177 + @Override 1.178 + public void onDetachedFromWindow() { 1.179 + super.onDetachedFromWindow(); 1.180 + mTheme.removeListener(this); 1.181 + } 1.182 + 1.183 + @Override 1.184 + public void onLightweightThemeChanged() { 1.185 + final int background = getResources().getColor(R.color.background_tabs); 1.186 + final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background, true); 1.187 + if (drawable == null) 1.188 + return; 1.189 + 1.190 + drawable.setAlpha(34, 0); 1.191 + setBackgroundDrawable(drawable); 1.192 + } 1.193 + 1.194 + @Override 1.195 + public void onLightweightThemeReset() { 1.196 + setBackgroundColor(getContext().getResources().getColor(R.color.background_tabs)); 1.197 + } 1.198 + 1.199 + @Override 1.200 + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1.201 + super.onLayout(changed, left, top, right, bottom); 1.202 + onLightweightThemeChanged(); 1.203 + } 1.204 + 1.205 + // Tabs List Container holds the ListView 1.206 + public static class TabsListContainer extends FrameLayout { 1.207 + public TabsListContainer(Context context, AttributeSet attrs) { 1.208 + super(context, attrs); 1.209 + } 1.210 + 1.211 + public PanelView getCurrentPanelView() { 1.212 + final int childCount = getChildCount(); 1.213 + for (int i = 0; i < childCount; i++) { 1.214 + View child = getChildAt(i); 1.215 + if (!(child instanceof PanelView)) 1.216 + continue; 1.217 + 1.218 + if (child.getVisibility() == View.VISIBLE) 1.219 + return (PanelView) child; 1.220 + } 1.221 + 1.222 + return null; 1.223 + } 1.224 + 1.225 + @Override 1.226 + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1.227 + if (!GeckoAppShell.getGeckoInterface().hasTabsSideBar()) { 1.228 + int heightSpec = MeasureSpec.makeMeasureSpec(getTabContainerHeight(TabsListContainer.this), MeasureSpec.EXACTLY); 1.229 + super.onMeasure(widthMeasureSpec, heightSpec); 1.230 + } else { 1.231 + super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1.232 + } 1.233 + } 1.234 + } 1.235 + 1.236 + // Tabs Panel Toolbar contains the Buttons 1.237 + public static class TabsPanelToolbar extends LinearLayout 1.238 + implements LightweightTheme.OnChangeListener { 1.239 + private final LightweightTheme mTheme; 1.240 + 1.241 + public TabsPanelToolbar(Context context, AttributeSet attrs) { 1.242 + super(context, attrs); 1.243 + mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme(); 1.244 + 1.245 + setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, 1.246 + (int) context.getResources().getDimension(R.dimen.browser_toolbar_height))); 1.247 + 1.248 + setOrientation(LinearLayout.HORIZONTAL); 1.249 + } 1.250 + 1.251 + @Override 1.252 + public void onAttachedToWindow() { 1.253 + super.onAttachedToWindow(); 1.254 + mTheme.addListener(this); 1.255 + } 1.256 + 1.257 + @Override 1.258 + public void onDetachedFromWindow() { 1.259 + super.onDetachedFromWindow(); 1.260 + mTheme.removeListener(this); 1.261 + } 1.262 + 1.263 + @Override 1.264 + public void onLightweightThemeChanged() { 1.265 + final int background = getResources().getColor(R.color.background_tabs); 1.266 + final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background); 1.267 + if (drawable == null) 1.268 + return; 1.269 + 1.270 + drawable.setAlpha(34, 34); 1.271 + setBackgroundDrawable(drawable); 1.272 + } 1.273 + 1.274 + @Override 1.275 + public void onLightweightThemeReset() { 1.276 + setBackgroundColor(getContext().getResources().getColor(R.color.background_tabs)); 1.277 + } 1.278 + 1.279 + @Override 1.280 + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1.281 + super.onLayout(changed, left, top, right, bottom); 1.282 + onLightweightThemeChanged(); 1.283 + } 1.284 + } 1.285 + 1.286 + public void show(Panel panel) { 1.287 + show(panel, true); 1.288 + } 1.289 + 1.290 + public void show(Panel panel, boolean shouldResize) { 1.291 + if (!isShown()) 1.292 + setVisibility(View.VISIBLE); 1.293 + 1.294 + if (mPanel != null) { 1.295 + // Hide the old panel. 1.296 + mPanel.hide(); 1.297 + } 1.298 + 1.299 + final boolean showAnimation = !mVisible; 1.300 + mVisible = true; 1.301 + mCurrentPanel = panel; 1.302 + 1.303 + int index = panel.ordinal(); 1.304 + mTabWidget.setCurrentTab(index); 1.305 + 1.306 + if (index == 0) { 1.307 + mPanel = mPanelNormal; 1.308 + } else if (index == 1) { 1.309 + mPanel = mPanelPrivate; 1.310 + } else { 1.311 + mPanel = mPanelRemote; 1.312 + } 1.313 + 1.314 + mPanel.show(); 1.315 + 1.316 + if (mCurrentPanel == Panel.REMOTE_TABS) { 1.317 + if (mFooter != null) 1.318 + mFooter.setVisibility(View.GONE); 1.319 + 1.320 + mAddTab.setVisibility(View.INVISIBLE); 1.321 + } else { 1.322 + if (mFooter != null) 1.323 + mFooter.setVisibility(View.VISIBLE); 1.324 + 1.325 + mAddTab.setVisibility(View.VISIBLE); 1.326 + mAddTab.setImageLevel(index); 1.327 + } 1.328 + 1.329 + if (shouldResize) { 1.330 + if (isSideBar()) { 1.331 + if (showAnimation) 1.332 + dispatchLayoutChange(getWidth(), getHeight()); 1.333 + } else { 1.334 + int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height); 1.335 + int height = actionBarHeight + getTabContainerHeight(mTabsContainer); 1.336 + dispatchLayoutChange(getWidth(), height); 1.337 + } 1.338 + } 1.339 + } 1.340 + 1.341 + public void hide() { 1.342 + if (mVisible) { 1.343 + mVisible = false; 1.344 + dispatchLayoutChange(0, 0); 1.345 + } 1.346 + } 1.347 + 1.348 + public void refresh() { 1.349 + removeAllViews(); 1.350 + 1.351 + LayoutInflater.from(mContext).inflate(R.layout.tabs_panel, this); 1.352 + initialize(); 1.353 + 1.354 + if (mVisible) 1.355 + show(mCurrentPanel); 1.356 + } 1.357 + 1.358 + public void autoHidePanel() { 1.359 + mActivity.autoHideTabs(); 1.360 + } 1.361 + 1.362 + @Override 1.363 + public boolean isShown() { 1.364 + return mVisible; 1.365 + } 1.366 + 1.367 + public boolean isSideBar() { 1.368 + return mIsSideBar; 1.369 + } 1.370 + 1.371 + public void setIsSideBar(boolean isSideBar) { 1.372 + mIsSideBar = isSideBar; 1.373 + } 1.374 + 1.375 + public Panel getCurrentPanel() { 1.376 + return mCurrentPanel; 1.377 + } 1.378 + 1.379 + public void prepareTabsAnimation(PropertyAnimator animator) { 1.380 + // Not worth doing this on pre-Honeycomb without proper 1.381 + // hardware accelerated animations. 1.382 + if (Build.VERSION.SDK_INT < 11) { 1.383 + return; 1.384 + } 1.385 + 1.386 + final Resources resources = getContext().getResources(); 1.387 + final int toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); 1.388 + final int tabsPanelWidth = getWidth(); 1.389 + 1.390 + if (mVisible) { 1.391 + if (mIsSideBar) { 1.392 + ViewHelper.setTranslationX(mHeader, -tabsPanelWidth); 1.393 + } else { 1.394 + ViewHelper.setTranslationY(mHeader, -toolbarHeight); 1.395 + } 1.396 + 1.397 + if (mIsSideBar) { 1.398 + ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth); 1.399 + } else { 1.400 + ViewHelper.setTranslationY(mTabsContainer, -toolbarHeight); 1.401 + ViewHelper.setAlpha(mTabsContainer, 0); 1.402 + } 1.403 + 1.404 + // The footer view is only present on the sidebar 1.405 + if (mIsSideBar) { 1.406 + ViewHelper.setTranslationX(mFooter, -tabsPanelWidth); 1.407 + } 1.408 + } 1.409 + 1.410 + if (mIsSideBar) { 1.411 + final int translationX = (mVisible ? 0 : -tabsPanelWidth); 1.412 + 1.413 + animator.attach(mTabsContainer, 1.414 + PropertyAnimator.Property.TRANSLATION_X, 1.415 + translationX); 1.416 + animator.attach(mHeader, 1.417 + PropertyAnimator.Property.TRANSLATION_X, 1.418 + translationX); 1.419 + animator.attach(mFooter, 1.420 + PropertyAnimator.Property.TRANSLATION_X, 1.421 + translationX); 1.422 + } else { 1.423 + final int translationY = (mVisible ? 0 : -toolbarHeight); 1.424 + 1.425 + animator.attach(mTabsContainer, 1.426 + PropertyAnimator.Property.ALPHA, 1.427 + mVisible ? 1.0f : 0.0f); 1.428 + animator.attach(mTabsContainer, 1.429 + PropertyAnimator.Property.TRANSLATION_Y, 1.430 + translationY); 1.431 + animator.attach(mHeader, 1.432 + PropertyAnimator.Property.TRANSLATION_Y, 1.433 + translationY); 1.434 + } 1.435 + 1.436 + mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1.437 + mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); 1.438 + } 1.439 + 1.440 + public void finishTabsAnimation() { 1.441 + if (Build.VERSION.SDK_INT < 11) { 1.442 + return; 1.443 + } 1.444 + 1.445 + mHeader.setLayerType(View.LAYER_TYPE_NONE, null); 1.446 + mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null); 1.447 + 1.448 + // If the tray is now hidden, call hide() on current panel and unset it as the current panel 1.449 + // to avoid hide() being called again when the tray is opened next. 1.450 + if (!mVisible && mPanel != null) { 1.451 + mPanel.hide(); 1.452 + mPanel = null; 1.453 + } 1.454 + } 1.455 + 1.456 + public void setTabsLayoutChangeListener(TabsLayoutChangeListener listener) { 1.457 + mLayoutChangeListener = listener; 1.458 + } 1.459 + 1.460 + private void dispatchLayoutChange(int width, int height) { 1.461 + if (mLayoutChangeListener != null) 1.462 + mLayoutChangeListener.onTabsLayoutChange(width, height); 1.463 + } 1.464 +}