Commit 1b64cd44 authored by Shakti Sahu's avatar Shakti Sahu Committed by Commit Bot

Ephemeral tab using bottom sheet : Added toolbar and favicon

This CL adds the detailed layout of toolbar for ephemeral tab, which
includes the title, caption, close button, and favicon.

Bug: 998826
Change-Id: I8103a12e010a1d6b43ef5cb5e0a8bc728255bd7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1775223
Commit-Queue: Shakti Sahu <shaktisahu@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarJinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: default avatarDonn Denman <donnd@chromium.org>
Cr-Commit-Position: refs/heads/master@{#696916}
parent bc8de300
......@@ -3,18 +3,93 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ephemeral_tab_caption_view"
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:paddingBottom="30dp"
android:layout_width="match_parent"
android:layout_height="@dimen/overlay_panel_bar_height"
android:background="@color/overlay_panel_bar_background_color"
android:orientation="vertical">
android:layout_height="86dp">
<TextView
android:id="@+id/ephemeral_tab_text"
<LinearLayout
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/overlay_panel_bar_height"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.BlackTitle1" />
</LinearLayout>
android:layout_height="@dimen/toolbar_height_no_shadow"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="4dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/favicon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerVertical="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="12dp"
android:src="@drawable/ic_chrome"
app:tint="@color/default_icon_color_blue"
android:scaleType="fitCenter"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/drag_handlebar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="4dp"
android:layout_centerHorizontal="true"
android:importantForAccessibility="no"
android:src="@drawable/drag_handlebar" />
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/open_in_new_tab"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="12dp"
android:src="@drawable/open_in_new_tab"
app:tint="@color/default_icon_color"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/ephemeral_tab_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_toStartOf="@id/open_in_new_tab"
android:layout_toEndOf="@id/favicon"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.BlackTitle1" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="2dp"
android:max="100"
android:progressBackgroundTint="@color/modern_blue_300"
android:progressTint="@color/modern_blue_600" />
</LinearLayout>
<org.chromium.chrome.browser.ui.widget.FadingShadowView
android:id="@+id/shadow"
android:layout_width="match_parent"
android:layout_height="@dimen/action_bar_shadow_height"
android:layout_marginTop="@dimen/toolbar_height_no_shadow"/>
</FrameLayout>
......@@ -136,6 +136,7 @@
<!-- Overlay panel dimensions -->
<dimen name="overlay_panel_bar_height_legacy">56dp</dimen>
<dimen name="overlay_panel_bar_height">60dp</dimen>
<dimen name="preview_tab_favicon_size">24dp</dimen>
<!-- Autofill keyboard accessory dimensions -->
<dimen name="keyboard_accessory_height_with_shadow">56dp</dimen>
......
......@@ -4,27 +4,49 @@
package org.chromium.chrome.browser.compositor.bottombar.ephemeraltab;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import org.chromium.base.Callback;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.bottombar.OverlayContentDelegate;
import org.chromium.chrome.browser.compositor.bottombar.OverlayContentProgressObserver;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContent;
import org.chromium.chrome.browser.favicon.FaviconHelper;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tabmodel.TabLaunchType;
import org.chromium.chrome.browser.util.ViewUtils;
import org.chromium.chrome.browser.widget.RoundedIconGenerator;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.PageTransition;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Central class for ephemeral tab, responsible for spinning off other classes necessary to display
* the preview tab UI.
*/
public class EphemeralTabCoordinator {
/** The delay (four video frames) after which the hide progress will be hidden. */
private static final long HIDE_PROGRESS_BAR_DELAY_MS = (1000 / 60) * 4;
// TODO(crbug/1001256): Use Context after removing dependency on OverlayPanelContent.
private final ChromeActivity mActivity;
private final BottomSheetController mBottomSheetController;
private final FaviconLoader mFaviconLoader;
private OverlayPanelContent mPanelContent;
private EphemeralTabSheetContent mSheetContent;
private boolean mIsIncognito;
private String mUrl;
/**
* Constructor.
......@@ -35,6 +57,7 @@ public class EphemeralTabCoordinator {
ChromeActivity activity, BottomSheetController bottomSheetController) {
mActivity = activity;
mBottomSheetController = bottomSheetController;
mFaviconLoader = new FaviconLoader(mActivity);
mBottomSheetController.getBottomSheet().addObserver(new EmptyBottomSheetObserver() {
@Override
public void onSheetStateChanged(int newState) {
......@@ -51,12 +74,15 @@ public class EphemeralTabCoordinator {
* @param isIncognito Whether we are currently in incognito mode.
*/
public void requestOpenSheet(String url, String title, boolean isIncognito) {
mUrl = url;
mIsIncognito = isIncognito;
if (mSheetContent == null) mSheetContent = new EphemeralTabSheetContent(mActivity);
// TODO(shaktisahu): If the sheet is already open, maybe close the sheet.
if (mSheetContent == null) {
mSheetContent = new EphemeralTabSheetContent(
mActivity, () -> openInNewTab(), () -> onToolbarClick());
}
getContent().loadUrl(url, true);
getContent().updateBrowserControlsState(true);
mSheetContent.attachWebContents(
getContent().getWebContents(), (ContentView) getContent().getContainerView());
mSheetContent.setTitleText(title);
......@@ -67,12 +93,10 @@ public class EphemeralTabCoordinator {
private OverlayPanelContent getContent() {
if (mPanelContent == null) {
// TODO(shaktisahu): Provide implementations for OverlayContentDelegate and
// OverlayContentProgressObserver, to drive progress bar and favicon.
mPanelContent = new OverlayPanelContent(new OverlayContentDelegate(),
new OverlayContentProgressObserver(), mActivity, mIsIncognito,
mPanelContent = new OverlayPanelContent(new EphemeralTabPanelContentDelegate(),
new PageLoadProgressObserver(), mActivity, mIsIncognito,
mActivity.getResources().getDimensionPixelSize(
R.dimen.overlay_panel_bar_height));
R.dimen.toolbar_height_no_shadow));
}
return mPanelContent;
}
......@@ -88,4 +112,136 @@ public class EphemeralTabCoordinator {
mPanelContent = null;
}
}
private void openInNewTab() {
if (canPromoteToNewTab() && mUrl != null) {
mBottomSheetController.hideContent(mSheetContent, /* animate= */ true);
mActivity.getCurrentTabCreator().createNewTab(
new LoadUrlParams(mUrl, PageTransition.LINK), TabLaunchType.FROM_LINK,
mActivity.getActivityTabProvider().get());
}
}
private void onToolbarClick() {
if (mBottomSheetController.getBottomSheet().getSheetState()
== BottomSheet.SheetState.PEEK) {
mBottomSheetController.expandSheet();
}
}
private boolean canPromoteToNewTab() {
return !mActivity.isCustomTab();
}
private void onFaviconAvailable(Drawable drawable) {
if (mSheetContent == null) return;
mSheetContent.startFaviconAnimation(drawable);
}
/**
* Observes the ephemeral tab web contents and loads the associated favicon.
*/
private class EphemeralTabPanelContentDelegate extends OverlayContentDelegate {
private String mCurrentUrl;
@Override
public void onMainFrameLoadStarted(String url, boolean isExternalUrl) {
try {
String newHost = new URL(url).getHost();
String curHost = mCurrentUrl == null ? null : new URL(mCurrentUrl).getHost();
if (!newHost.equals(curHost)) {
mCurrentUrl = url;
mFaviconLoader.loadFavicon(url, (drawable) -> onFaviconAvailable(drawable));
}
} catch (MalformedURLException e) {
assert false : "Malformed URL should not be passed.";
}
}
}
/** Observes the ephemeral tab page load progress and updates the progress bar. */
private class PageLoadProgressObserver extends OverlayContentProgressObserver {
@Override
public void onProgressBarStarted() {
if (mSheetContent == null) return;
mSheetContent.setProgressVisible(true);
mSheetContent.setProgress(0);
}
@Override
public void onProgressBarUpdated(int progress) {
if (mSheetContent == null) return;
mSheetContent.setProgress(progress);
}
@Override
public void onProgressBarFinished() {
// Hides the Progress Bar after a delay to make sure it is rendered for at least
// a few frames, otherwise its completion won't be visually noticeable.
new Handler().postDelayed(() -> {
if (mSheetContent == null) return;
mSheetContent.setProgressVisible(false);
}, HIDE_PROGRESS_BAR_DELAY_MS);
}
}
/**
* Helper class to generate a favicon for a given URL and resize it to the desired dimensions
* for displaying it on the image view.
*/
private static class FaviconLoader {
private final Context mContext;
private final FaviconHelper mFaviconHelper;
private final FaviconHelper.DefaultFaviconHelper mDefaultFaviconHelper;
private final RoundedIconGenerator mIconGenerator;
private final int mFaviconSize;
/** Constructor. */
public FaviconLoader(Context context) {
mContext = context;
mFaviconHelper = new FaviconHelper();
mDefaultFaviconHelper = new FaviconHelper.DefaultFaviconHelper();
mIconGenerator =
ViewUtils.createDefaultRoundedIconGenerator(mContext.getResources(), true);
mFaviconSize =
mContext.getResources().getDimensionPixelSize(R.dimen.preview_tab_favicon_size);
}
/**
* Generates a favicon for a given URL. If no favicon was could be found or generated from
* the URL, a default favicon will be shown.
* @param url The URL for which favicon is to be generated.
* @param callback The callback to be invoked to display the final image.
*/
public void loadFavicon(final String url, Callback<Drawable> callback) {
FaviconHelper.FaviconImageCallback imageCallback = (bitmap, iconUrl) -> {
Drawable drawable = faviconDrawable(bitmap, url);
if (drawable == null) {
drawable = mDefaultFaviconHelper.getDefaultFaviconDrawable(mContext, url, true);
}
callback.onResult(drawable);
};
mFaviconHelper.getLocalFaviconImageForURL(
Profile.getLastUsedProfile(), url, mFaviconSize, imageCallback);
}
/**
* Generates a rounded bitmap for the given favicon. If the given favicon is null, generates
* a favicon from the URL instead.
*/
private Drawable faviconDrawable(Bitmap image, String url) {
if (url == null) return null;
if (image == null) {
image = mIconGenerator.generateIconForUrl(url);
return new BitmapDrawable(mContext.getResources(),
Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, true));
}
return ViewUtils.createRoundedBitmapDrawable(
Bitmap.createScaledBitmap(image, mFaviconSize, mFaviconSize, true),
ViewUtils.DEFAULT_FAVICON_CORNER_RADIUS);
}
}
}
......@@ -5,16 +5,23 @@
package org.chromium.chrome.browser.compositor.bottombar.ephemeraltab;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.thinwebview.ThinWebView;
import org.chromium.chrome.browser.thinwebview.ThinWebViewFactory;
import org.chromium.chrome.browser.ui.widget.FadingShadow;
import org.chromium.chrome.browser.ui.widget.FadingShadowView;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
import org.chromium.components.embedder_support.view.ContentView;
import org.chromium.content_public.browser.RenderCoordinates;
......@@ -26,19 +33,30 @@ import org.chromium.ui.base.WindowAndroid;
*/
public class EphemeralTabSheetContent implements BottomSheet.BottomSheetContent {
private final Context mContext;
private final Runnable mOpenNewTabCallback;
private final Runnable mToolbarClickCallback;
private ViewGroup mToolbarView;
private ViewGroup mSheetContentView;
private WebContents mWebContents;
private ContentView mWebContentView;
private ThinWebView mThinWebView;
private FadingShadowView mShadow;
private Drawable mCurrentFavicon;
private ImageView mFaviconView;
/**
* Constructor.
* @param context An Android context.
* @param openNewTabCallback Callback invoked to open a new tab.
* @param toolbarClickCallback Callback invoked when user clicks on the toolbar.
*/
public EphemeralTabSheetContent(Context context) {
public EphemeralTabSheetContent(
Context context, Runnable openNewTabCallback, Runnable toolbarClickCallback) {
mContext = context;
mOpenNewTabCallback = openNewTabCallback;
mToolbarClickCallback = toolbarClickCallback;
createThinWebView();
createToolbarView();
......@@ -58,8 +76,10 @@ public class EphemeralTabSheetContent implements BottomSheet.BottomSheetContent
mThinWebView.attachWebContents(mWebContents, mWebContentView);
}
// Create a ThinWebView, add it to the view hierarchy, which represents the contents of the
// bottom sheet.
/**
* Create a ThinWebView, add it to the view hierarchy, which represents the contents of the
* bottom sheet.
*/
private void createThinWebView() {
mThinWebView = ThinWebViewFactory.create(mContext, new WindowAndroid(mContext));
......@@ -74,13 +94,56 @@ public class EphemeralTabSheetContent implements BottomSheet.BottomSheetContent
private void createToolbarView() {
mToolbarView = (ViewGroup) LayoutInflater.from(mContext).inflate(
R.layout.ephemeral_tab_toolbar, null);
mShadow = mToolbarView.findViewById(R.id.shadow);
mShadow.init(ApiCompatibilityUtils.getColor(
mContext.getResources(), R.color.toolbar_shadow_color),
FadingShadow.POSITION_TOP);
ImageView openInNewTabButton = mToolbarView.findViewById(R.id.open_in_new_tab);
openInNewTabButton.setOnClickListener(view -> mOpenNewTabCallback.run());
View toolbar = mToolbarView.findViewById(R.id.toolbar);
toolbar.setOnClickListener(view -> mToolbarClickCallback.run());
mFaviconView = mToolbarView.findViewById(R.id.favicon);
mCurrentFavicon = mFaviconView.getDrawable();
}
/** Method to be called to start the favicon anmiation. */
public void startFaviconAnimation(Drawable favicon) {
assert favicon != null;
// TODO(shaktisahu): Find out if there is a better way for this animation.
Drawable presentedDrawable = favicon;
if (mCurrentFavicon != null && !(mCurrentFavicon instanceof TransitionDrawable)) {
TransitionDrawable transitionDrawable = ApiCompatibilityUtils.createTransitionDrawable(
new Drawable[] {mCurrentFavicon, favicon});
transitionDrawable.setCrossFadeEnabled(true);
transitionDrawable.startTransition((int) BottomSheet.BASE_ANIMATION_DURATION_MS);
presentedDrawable = transitionDrawable;
}
mFaviconView.setImageDrawable(presentedDrawable);
mCurrentFavicon = favicon;
}
/** Sets the ephemeral tab title text. */
public void setTitleText(String text) {
TextView toolbarText = mToolbarView.findViewById(R.id.ephemeral_tab_text);
toolbarText.setText(text);
}
/** Sets the progress percentage on the progress bar. */
public void setProgress(int progress) {
ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
progressBar.setProgress(progress);
}
/** Called to show or hide the progress bar. */
public void setProgressVisible(boolean visible) {
ProgressBar progressBar = mToolbarView.findViewById(R.id.progress_bar);
progressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@Override
public View getContentView() {
return mSheetContentView;
......@@ -119,6 +182,11 @@ public class EphemeralTabSheetContent implements BottomSheet.BottomSheetContent
return true;
}
@Override
public boolean hideOnScroll() {
return false;
}
@Override
public boolean wrapContentEnabled() {
return true;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment