Commit 770dda4c authored by Sinan Sahin's avatar Sinan Sahin Committed by Commit Bot

Reland "[Offline indicator v2] Add Java animations"

This is a reland of b5018edf

Original change's description:
> [Offline indicator v2] Add Java animations
> 
> This CL implements the color and text transition animations that run when
> showing or hiding the status indicator.
> 
> Show:
> 1- Transition the status bar color.
> 2- Wait for the browser controls animation in cc to finish.
> 3- Fade in text.
> 
> Hide:
> 1- Transition the status bar and the indicator color.
> 2- Wait 2 seconds.
> 3- Fade out text, and transition the status bar and indicator color back
> to the original status bar color.
> 4- Let the browser controls hide animation run in cc.
> 
> Bug: 989148
> Change-Id: If19a51e6c5091436d78e34ef50f11ffa0118ee0d
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2067095
> Commit-Queue: Sinan Sahin <sinansahin@google.com>
> Reviewed-by: Matthew Jones <mdjones@chromium.org>
> Reviewed-by: Theresa  <twellington@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#747080}

Bug: 989148
Change-Id: I0bf7813c4ef7833d66e449863d16cc1068bc9572
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2090015Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Commit-Queue: Sinan Sahin <sinansahin@google.com>
Cr-Commit-Position: refs/heads/master@{#747439}
parent 51d643e5
...@@ -8,14 +8,15 @@ ...@@ -8,14 +8,15 @@
android:id="@+id/status_indicator" android:id="@+id/status_indicator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/black"> android:background="@color/modern_primary_color">
<org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables <org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables
android:id="@+id/status_text" android:id="@+id/status_text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:minHeight="20dp" android:minHeight="20dp"
android:textAlignment="center" android:textAlignment="center"
android:drawablePadding="8dp" /> android:drawablePadding="8dp"
android:textAppearance="@style/TextAppearance.TextSmall.Primary"/>
</org.chromium.components.browser_ui.widget.ViewResourceFrameLayout> </org.chromium.components.browser_ui.widget.ViewResourceFrameLayout>
...@@ -133,6 +133,10 @@ ...@@ -133,6 +133,10 @@
<!-- Prefetch article colors --> <!-- Prefetch article colors -->
<color name="prefetch_offline_icon_tint_color">@color/modern_grey_500</color> <color name="prefetch_offline_icon_tint_color">@color/modern_grey_500</color>
<!-- Offline indicator v2 colors -->
<color name="offline_indicator_offline_color">@android:color/black</color>
<color name="offline_indicator_back_online_color">@color/default_bg_color_blue</color>
<!-- Other colors --> <!-- Other colors -->
<color name="media_viewer_bg">#000000</color> <color name="media_viewer_bg">#000000</color>
<color name="image_viewer_bg">#0E0E0E</color> <color name="image_viewer_bg">#0E0E0E</color>
......
...@@ -9,6 +9,10 @@ import android.graphics.drawable.Drawable; ...@@ -9,6 +9,10 @@ import android.graphics.drawable.Drawable;
import android.view.View; import android.view.View;
import android.view.ViewStub; import android.view.ViewStub;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout; import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout;
...@@ -16,8 +20,6 @@ import org.chromium.ui.modelutil.PropertyModel; ...@@ -16,8 +20,6 @@ import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor; import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.resources.ResourceManager; import org.chromium.ui.resources.ResourceManager;
import java.util.HashSet;
/** /**
* The coordinator for a status indicator that is positioned below the status bar and is persistent. * The coordinator for a status indicator that is positioned below the status bar and is persistent.
* Typically used to relay status, e.g. indicate user is offline. * Typically used to relay status, e.g. indicate user is offline.
...@@ -29,95 +31,119 @@ public class StatusIndicatorCoordinator { ...@@ -29,95 +31,119 @@ public class StatusIndicatorCoordinator {
* Called when the height of the status indicator changes. * Called when the height of the status indicator changes.
* @param newHeight The new height in pixels. * @param newHeight The new height in pixels.
*/ */
void onStatusIndicatorHeightChanged(int newHeight); default void onStatusIndicatorHeightChanged(int newHeight) {}
/**
* Called when the background color of the status indicator changes.
* @param newColor The new color as {@link ColorInt}.
*/
default void onStatusIndicatorColorChanged(@ColorInt int newColor) {}
} }
private StatusIndicatorMediator mMediator; private StatusIndicatorMediator mMediator;
private PropertyModel mModel;
private View mView;
private StatusIndicatorSceneLayer mSceneLayer; private StatusIndicatorSceneLayer mSceneLayer;
private HashSet<StatusIndicatorObserver> mObservers = new HashSet<>(); private boolean mIsShowing;
private Runnable mRemoveOnLayoutChangeListener;
/**
* Constructs the status indicator.
* @param activity The {@link Activity} to find and inflate the status indicator view.
* @param resourceManager The {@link ResourceManager} for the status indicator's cc layer.
* @param fullscreenManager The {@link ChromeFullscreenManager} to listen to for the changes in
* controls offsets.
* @param statusBarColorWithoutStatusIndicatorSupplier A supplier that will get the status bar
* color without taking the status indicator
* into account.
*/
public StatusIndicatorCoordinator(Activity activity, ResourceManager resourceManager, public StatusIndicatorCoordinator(Activity activity, ResourceManager resourceManager,
ChromeFullscreenManager fullscreenManager) { ChromeFullscreenManager fullscreenManager,
Supplier<Integer> statusBarColorWithoutStatusIndicatorSupplier) {
// TODO(crbug.com/1005843): Create this view lazily if/when we need it. This is a task for // TODO(crbug.com/1005843): Create this view lazily if/when we need it. This is a task for
// when we have the public API figured out. // when we have the public API figured out. First, we should avoid inflating the view here
// in case it's never used.
final ViewStub stub = activity.findViewById(R.id.status_indicator_stub); final ViewStub stub = activity.findViewById(R.id.status_indicator_stub);
ViewResourceFrameLayout root = (ViewResourceFrameLayout) stub.inflate(); ViewResourceFrameLayout root = (ViewResourceFrameLayout) stub.inflate();
mView = root;
mSceneLayer = new StatusIndicatorSceneLayer(root, () -> fullscreenManager); mSceneLayer = new StatusIndicatorSceneLayer(root, () -> fullscreenManager);
mModel = new PropertyModel.Builder(StatusIndicatorProperties.ALL_KEYS) PropertyModel model =
.with(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE) new PropertyModel.Builder(StatusIndicatorProperties.ALL_KEYS)
.with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false) .with(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE)
.build(); .with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false)
PropertyModelChangeProcessor.create(mModel, .build();
PropertyModelChangeProcessor.create(model,
new StatusIndicatorViewBinder.ViewHolder(root, mSceneLayer), new StatusIndicatorViewBinder.ViewHolder(root, mSceneLayer),
StatusIndicatorViewBinder::bind); StatusIndicatorViewBinder::bind);
mMediator = new StatusIndicatorMediator(mModel, fullscreenManager); mMediator = new StatusIndicatorMediator(
mObservers.add(mMediator); model, fullscreenManager, statusBarColorWithoutStatusIndicatorSupplier);
resourceManager.getDynamicResourceLoader().registerResource( resourceManager.getDynamicResourceLoader().registerResource(
root.getId(), root.getResourceAdapter()); root.getId(), root.getResourceAdapter());
root.addOnLayoutChangeListener(mMediator);
mRemoveOnLayoutChangeListener = () -> root.removeOnLayoutChangeListener(mMediator);
}
public void destroy() {
mRemoveOnLayoutChangeListener.run();
} }
// TODO(sinansahin): Destroy the view when not needed.
/** /**
* Set the {@link String} the status indicator should display. * Show the status indicator with the initial properties with animations.
* @param statusText The string. *
* @param statusText The status string that will be visible on the status indicator.
* @param statusIcon The icon {@link Drawable} that will appear next to the status text.
* @param backgroundColor The background color for the status indicator and the status bar.
* @param textColor Status text color.
* @param iconTint Status icon tint.
*/ */
public void setStatusText(String statusText) { public void show(@NonNull String statusText, Drawable statusIcon, @ColorInt int backgroundColor,
mModel.set(StatusIndicatorProperties.STATUS_TEXT, statusText); @ColorInt int textColor, @ColorInt int iconTint) {
// TODO(sinansahin): Once we've moved the connectivity detection code to a separate class,
// we should make sure #show(), #updateContent(), and #hide() are called correctly there,
// e.g. show shouldn't be called if we're already showing. Then, we can turn these if checks
// into asserts.
if (mIsShowing) return;
mIsShowing = true;
mMediator.animateShow(statusText, statusIcon, backgroundColor, textColor, iconTint);
} }
/** /**
* Set the {@link Drawable} the status indicator should display next to the status text. * Update the status indicator text, icon and colors with animations. All of the properties will
* @param statusIcon The icon drawable. * be animated even if only one property changes. Support to animate a single property may be
* added in the future if needed.
*
* @param statusText The string that will replace the current text.
* @param statusIcon The icon that will replace the current icon.
* @param backgroundColor The color that will replace the status indicator background color.
* @param textColor The new text color to fit the new background.
* @param iconTint The new icon tint to fit the background.
* @param animationCompleteCallback The callback that will be run once the animations end.
*/ */
public void setStatusIcon(Drawable statusIcon) { public void updateContent(@NonNull String statusText, Drawable statusIcon,
mModel.set(StatusIndicatorProperties.STATUS_ICON, statusIcon); @ColorInt int backgroundColor, @ColorInt int textColor, @ColorInt int iconTint,
} Runnable animationCompleteCallback) {
if (!mIsShowing) return;
// TODO(sinansahin): With animation. mMediator.animateUpdate(statusText, statusIcon, backgroundColor, textColor, iconTint,
// TODO(sinansahin): Destroy the view when not needed. animationCompleteCallback);
/** Show the status indicator. */
public void show() {
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.INVISIBLE);
// TODO(crbug.com/1005843): We will need a measure pass before we can get the real height of
// this view. We should keep this in mind when inflating the view lazily.
mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
final int height = v.getHeight();
for (StatusIndicatorObserver observer : mObservers) {
observer.onStatusIndicatorHeightChanged(height);
}
mView.removeOnLayoutChangeListener(this);
}
});
} }
// TODO(sinansahin): With animation as well. /**
/** Hide the status indicator. */ * Hide the status indicator with animations.
*/
public void hide() { public void hide() {
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE); if (!mIsShowing) return;
for (StatusIndicatorObserver observer : mObservers) { mIsShowing = false;
observer.onStatusIndicatorHeightChanged(0);
} mMediator.animateHide();
} }
public void addObserver(StatusIndicatorObserver observer) { public void addObserver(StatusIndicatorObserver observer) {
mObservers.add(observer); mMediator.addObserver(observer);
} }
public void removeObserver(StatusIndicatorObserver observer) { public void removeObserver(StatusIndicatorObserver observer) {
mObservers.remove(observer); mMediator.removeObserver(observer);
}
/**
* Is the status indicator currently visible.
* @return True if visible.
*/
public boolean isVisible() {
return mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE);
} }
/** /**
......
...@@ -26,6 +26,19 @@ class StatusIndicatorProperties { ...@@ -26,6 +26,19 @@ class StatusIndicatorProperties {
static final PropertyModel.WritableBooleanPropertyKey COMPOSITED_VIEW_VISIBLE = static final PropertyModel.WritableBooleanPropertyKey COMPOSITED_VIEW_VISIBLE =
new PropertyModel.WritableBooleanPropertyKey(); new PropertyModel.WritableBooleanPropertyKey();
static final PropertyKey[] ALL_KEYS = new PropertyKey[] { static final PropertyModel.WritableIntPropertyKey BACKGROUND_COLOR =
STATUS_TEXT, STATUS_ICON, ANDROID_VIEW_VISIBILITY, COMPOSITED_VIEW_VISIBLE}; new PropertyModel.WritableIntPropertyKey();
static final PropertyModel.WritableFloatPropertyKey TEXT_ALPHA =
new PropertyModel.WritableFloatPropertyKey();
static final PropertyModel.WritableIntPropertyKey TEXT_COLOR =
new PropertyModel.WritableIntPropertyKey();
static final PropertyModel.WritableIntPropertyKey ICON_TINT =
new PropertyModel.WritableIntPropertyKey();
static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {STATUS_TEXT, STATUS_ICON, ANDROID_VIEW_VISIBILITY,
COMPOSITED_VIEW_VISIBLE, BACKGROUND_COLOR, TEXT_ALPHA, TEXT_COLOR, ICON_TINT};
} }
...@@ -4,10 +4,13 @@ ...@@ -4,10 +4,13 @@
package org.chromium.chrome.browser.status_indicator; package org.chromium.chrome.browser.status_indicator;
import android.content.res.ColorStateList;
import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout; import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout;
import org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
...@@ -47,6 +50,21 @@ class StatusIndicatorViewBinder { ...@@ -47,6 +50,21 @@ class StatusIndicatorViewBinder {
} else if (StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY == propertyKey) { } else if (StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY == propertyKey) {
view.javaViewRoot.setVisibility( view.javaViewRoot.setVisibility(
model.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY)); model.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
} else if (StatusIndicatorProperties.BACKGROUND_COLOR == propertyKey) {
view.javaViewRoot.setBackgroundColor(
model.get(StatusIndicatorProperties.BACKGROUND_COLOR));
} else if (StatusIndicatorProperties.TEXT_ALPHA == propertyKey) {
final View text = view.javaViewRoot.findViewById(R.id.status_text);
text.setAlpha(model.get(StatusIndicatorProperties.TEXT_ALPHA));
} else if (StatusIndicatorProperties.TEXT_COLOR == propertyKey) {
final TextView text = view.javaViewRoot.findViewById(R.id.status_text);
text.setTextColor(model.get(StatusIndicatorProperties.TEXT_COLOR));
} else if (StatusIndicatorProperties.ICON_TINT == propertyKey) {
final TextViewWithCompoundDrawables text =
view.javaViewRoot.findViewById(R.id.status_text);
final ColorStateList tint =
ColorStateList.valueOf(model.get(StatusIndicatorProperties.ICON_TINT));
text.setDrawableTintColor(tint);
} else { } else {
assert false : "Unhandled property detected in StatusIndicatorViewBinder!"; assert false : "Unhandled property detected in StatusIndicatorViewBinder!";
} }
......
...@@ -4,13 +4,17 @@ ...@@ -4,13 +4,17 @@
package org.chromium.chrome.browser.tabbed_mode; package org.chromium.chrome.browser.tabbed_mode;
import android.os.Handler;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.TraceEvent; import org.chromium.base.TraceEvent;
import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.task.PostTask; import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.AppHooks; import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager; import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
...@@ -48,6 +52,8 @@ import org.chromium.ui.base.WindowAndroid; ...@@ -48,6 +52,8 @@ import org.chromium.ui.base.WindowAndroid;
public class TabbedRootUiCoordinator extends RootUiCoordinator implements NativeInitObserver { public class TabbedRootUiCoordinator extends RootUiCoordinator implements NativeInitObserver {
private static boolean sEnableStatusIndicatorForTests; private static boolean sEnableStatusIndicatorForTests;
private static final int STATUS_INDICATOR_WAIT_BEFORE_HIDE_DURATION_MS = 2000;
private @Nullable ImmersiveModeManager mImmersiveModeManager; private @Nullable ImmersiveModeManager mImmersiveModeManager;
private TabbedSystemUiCoordinator mSystemUiCoordinator; private TabbedSystemUiCoordinator mSystemUiCoordinator;
private @Nullable EmptyBackgroundViewWrapper mEmptyBackgroundViewWrapper; private @Nullable EmptyBackgroundViewWrapper mEmptyBackgroundViewWrapper;
...@@ -82,6 +88,8 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native ...@@ -82,6 +88,8 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
if (mStatusIndicatorCoordinator != null) { if (mStatusIndicatorCoordinator != null) {
mStatusIndicatorCoordinator.removeObserver(mStatusIndicatorObserver); mStatusIndicatorCoordinator.removeObserver(mStatusIndicatorObserver);
mStatusIndicatorCoordinator.removeObserver(mActivity.getStatusBarColorController());
mStatusIndicatorCoordinator.destroy();
} }
if (mToolbarButtonInProductHelpController != null) { if (mToolbarButtonInProductHelpController != null) {
...@@ -174,17 +182,23 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native ...@@ -174,17 +182,23 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
final ChromeFullscreenManager fullscreenManager = mActivity.getFullscreenManager(); final ChromeFullscreenManager fullscreenManager = mActivity.getFullscreenManager();
mStatusIndicatorCoordinator = new StatusIndicatorCoordinator(mActivity, mStatusIndicatorCoordinator = new StatusIndicatorCoordinator(mActivity,
mActivity.getCompositorViewHolder().getResourceManager(), fullscreenManager); mActivity.getCompositorViewHolder().getResourceManager(), fullscreenManager,
mActivity.getStatusBarColorController()::getStatusBarColorWithoutStatusIndicator);
layoutManager.setStatusIndicatorSceneOverlay(mStatusIndicatorCoordinator.getSceneLayer()); layoutManager.setStatusIndicatorSceneOverlay(mStatusIndicatorCoordinator.getSceneLayer());
mStatusIndicatorObserver = (indicatorHeight -> { mStatusIndicatorObserver = new StatusIndicatorCoordinator.StatusIndicatorObserver() {
final int resourceId = mActivity.getControlContainerHeightResource(); @Override
final int topControlsNewHeight = public void onStatusIndicatorHeightChanged(int indicatorHeight) {
mActivity.getResources().getDimensionPixelSize(resourceId) + indicatorHeight; final int resourceId = mActivity.getControlContainerHeightResource();
fullscreenManager.setAnimateBrowserControlsHeightChanges(true); final int topControlsNewHeight =
fullscreenManager.setTopControlsHeight(topControlsNewHeight, indicatorHeight); mActivity.getResources().getDimensionPixelSize(resourceId)
fullscreenManager.setAnimateBrowserControlsHeightChanges(false); + indicatorHeight;
}); fullscreenManager.setAnimateBrowserControlsHeightChanges(true);
fullscreenManager.setTopControlsHeight(topControlsNewHeight, indicatorHeight);
fullscreenManager.setAnimateBrowserControlsHeightChanges(false);
}
};
mStatusIndicatorCoordinator.addObserver(mStatusIndicatorObserver); mStatusIndicatorCoordinator.addObserver(mStatusIndicatorObserver);
mStatusIndicatorCoordinator.addObserver(mActivity.getStatusBarColorController());
// Don't listen to the ConnectivityDetector if the feature is disabled. // Don't listen to the ConnectivityDetector if the feature is disabled.
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.OFFLINE_INDICATOR_V2)) { if (!ChromeFeatureList.isEnabled(ChromeFeatureList.OFFLINE_INDICATOR_V2)) {
...@@ -194,9 +208,30 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native ...@@ -194,9 +208,30 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
mConnectivityDetector = new ConnectivityDetector((state) -> { mConnectivityDetector = new ConnectivityDetector((state) -> {
final boolean offline = state != ConnectivityDetector.ConnectionState.VALIDATED; final boolean offline = state != ConnectivityDetector.ConnectionState.VALIDATED;
if (offline) { if (offline) {
mStatusIndicatorCoordinator.show(); final int backgroundColor = ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.offline_indicator_offline_color);
final int textColor = ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.default_text_color_light);
final int iconTint = ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.default_icon_color_light);
mStatusIndicatorCoordinator.show(
mActivity.getString(R.string.offline_indicator_v2_offline_text), null,
backgroundColor, textColor, iconTint);
} else { } else {
mStatusIndicatorCoordinator.hide(); final int backgroundColor = ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.offline_indicator_back_online_color);
final int textColor = ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.default_text_color_inverse);
final int iconTint = ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.default_icon_color_inverse);
Runnable hide = () -> {
final Handler handler = new Handler();
handler.postDelayed(() -> mStatusIndicatorCoordinator.hide(),
STATUS_INDICATOR_WAIT_BEFORE_HIDE_DURATION_MS);
};
mStatusIndicatorCoordinator.updateContent(
mActivity.getString(R.string.offline_indicator_v2_back_online_text), null,
backgroundColor, textColor, iconTint, hide);
} }
}); });
} }
......
...@@ -21,6 +21,7 @@ import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver; ...@@ -21,6 +21,7 @@ import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.lifecycle.Destroyable; import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.ntp.NewTabPage; import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.status_indicator.StatusIndicatorCoordinator;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabSelectionType; import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab.TabThemeColorHelper; import org.chromium.chrome.browser.tab.TabThemeColorHelper;
...@@ -39,9 +40,10 @@ import org.chromium.ui.util.ColorUtils; ...@@ -39,9 +40,10 @@ import org.chromium.ui.util.ColorUtils;
* Maintains the status bar color for a {@link ChromeActivity}. * Maintains the status bar color for a {@link ChromeActivity}.
*/ */
public class StatusBarColorController public class StatusBarColorController
implements Destroyable, TopToolbarCoordinator.UrlExpansionObserver { implements Destroyable, TopToolbarCoordinator.UrlExpansionObserver,
public static @ColorInt int UNDEFINED_STATUS_BAR_COLOR = Color.TRANSPARENT; StatusIndicatorCoordinator.StatusIndicatorObserver {
public static @ColorInt int DEFAULT_STATUS_BAR_COLOR = Color.argb(0x01, 0, 0, 0); public static final @ColorInt int UNDEFINED_STATUS_BAR_COLOR = Color.TRANSPARENT;
public static final @ColorInt int DEFAULT_STATUS_BAR_COLOR = Color.argb(0x01, 0, 0, 0);
/** /**
* Provides the base status bar color. * Provides the base status bar color.
...@@ -86,6 +88,8 @@ public class StatusBarColorController ...@@ -86,6 +88,8 @@ public class StatusBarColorController
private float mToolbarUrlExpansionPercentage; private float mToolbarUrlExpansionPercentage;
private boolean mShouldUpdateStatusBarColorForNTP; private boolean mShouldUpdateStatusBarColorForNTP;
private @ColorInt int mStatusIndicatorColor;
private @ColorInt int mStatusBarColorWithoutStatusIndicator;
/** /**
* @param chromeActivity The {@link ChromeActivity} that this class is attached to. * @param chromeActivity The {@link ChromeActivity} that this class is attached to.
...@@ -106,6 +110,8 @@ public class StatusBarColorController ...@@ -106,6 +110,8 @@ public class StatusBarColorController
mStandardDefaultThemeColor = ChromeColors.getDefaultThemeColor(resources, false); mStandardDefaultThemeColor = ChromeColors.getDefaultThemeColor(resources, false);
mIncognitoDefaultThemeColor = ChromeColors.getDefaultThemeColor(resources, true); mIncognitoDefaultThemeColor = ChromeColors.getDefaultThemeColor(resources, true);
mStatusIndicatorColor = UNDEFINED_STATUS_BAR_COLOR;
mStatusBarColorTabObserver = new ActivityTabProvider.ActivityTabTabObserver( mStatusBarColorTabObserver = new ActivityTabProvider.ActivityTabTabObserver(
chromeActivity.getActivityTabProvider()) { chromeActivity.getActivityTabProvider()) {
@Override @Override
...@@ -203,6 +209,14 @@ public class StatusBarColorController ...@@ -203,6 +209,14 @@ public class StatusBarColorController
if (mShouldUpdateStatusBarColorForNTP) updateStatusBarColor(); if (mShouldUpdateStatusBarColorForNTP) updateStatusBarColor();
} }
// StatusIndicatorCoordinator.StatusIndicatorObserver implementation.
@Override
public void onStatusIndicatorColorChanged(@ColorInt int newColor) {
mStatusIndicatorColor = newColor;
updateStatusBarColor();
}
/** /**
* @param tabModelSelector The {@link TabModelSelector} to check whether incognito model is * @param tabModelSelector The {@link TabModelSelector} to check whether incognito model is
* selected. * selected.
...@@ -229,7 +243,49 @@ public class StatusBarColorController ...@@ -229,7 +243,49 @@ public class StatusBarColorController
statusBarColor = statusBarColor =
mIsIncognito ? mIncognitoDefaultThemeColor : mStandardDefaultThemeColor; mIsIncognito ? mIncognitoDefaultThemeColor : mStandardDefaultThemeColor;
} }
setStatusBarColor(statusBarColor, isDefaultThemeColor);
mStatusBarColorWithoutStatusIndicator = statusBarColor;
final boolean statusIndicatorColorSet = mStatusIndicatorColor != UNDEFINED_STATUS_BAR_COLOR;
if (statusIndicatorColorSet) {
statusBarColor = mStatusIndicatorColor;
}
// If the API level is not at least M, the status bar icons will be always light. So, we
// should darken the status bar color.
boolean shouldDarkenStatusBar = Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
// Calculate the color without the status indicator.
if (shouldDarkenStatusBar && isDefaultThemeColor) {
mStatusBarColorWithoutStatusIndicator = Color.BLACK;
} else if (shouldDarkenStatusBar) {
mStatusBarColorWithoutStatusIndicator =
ColorUtils.getDarkenedColorForStatusBar(mStatusBarColorWithoutStatusIndicator);
}
// If we need to darken the color and the theme color is default, the status bar color
// should be black. However, we should use the status indicator color if it's set.
if (shouldDarkenStatusBar && isDefaultThemeColor && !statusIndicatorColorSet) {
statusBarColor = Color.BLACK;
} else if (shouldDarkenStatusBar) {
statusBarColor = ColorUtils.getDarkenedColorForStatusBar(statusBarColor);
} else {
// If we aren't darkening the color, we should apply scrim if it's showing.
statusBarColor = applyCurrentScrimToColor(statusBarColor);
}
setStatusBarColor(statusBarColor);
}
// TODO(sinansahin): Confirm pre-M expectations with UX and update as needed.
/**
* @return The status bar color without the status indicator's color taken into consideration.
* Color returned from this method includes darkening if the OS version doesn't support
* light status bar icons (pre-M). However, scrimming isn't included since it's managed
* completely by this class.
*/
public @ColorInt int getStatusBarColorWithoutStatusIndicator() {
return mStatusBarColorWithoutStatusIndicator;
} }
private @ColorInt int calculateBaseStatusBarColor() { private @ColorInt int calculateBaseStatusBarColor() {
...@@ -273,36 +329,35 @@ public class StatusBarColorController ...@@ -273,36 +329,35 @@ public class StatusBarColorController
} }
/** /**
* Set device status bar to a given color. * Set device status bar to a given color. Also, set the status bar icons to a dark color if
* needed.
* @param color The color that the status bar should be set to. * @param color The color that the status bar should be set to.
* @param isDefaultThemeColor Whether {@code color} is the default theme color.
*/ */
private void setStatusBarColor(int color, boolean isDefaultThemeColor) { private void setStatusBarColor(@ColorInt int color) {
if (UiUtils.isSystemUiThemingDisabled()) return; if (UiUtils.isSystemUiThemingDisabled()) return;
int statusBarColor = color; final View root = mWindow.getDecorView().getRootView();
boolean supportsDarkStatusIcons = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; boolean needsDarkStatusBarIcons = !ColorUtils.shouldUseLightForegroundOnBackground(color);
View root = mWindow.getDecorView().getRootView(); ApiCompatibilityUtils.setStatusBarIconColor(root, needsDarkStatusBarIcons);
Resources resources = root.getResources(); ApiCompatibilityUtils.setStatusBarColor(mWindow, color);
if (supportsDarkStatusIcons) { }
if (mScrimColor == 0) {
mScrimColor = ApiCompatibilityUtils.getColor(resources, R.color.black_alpha_65);
}
// Apply a color overlay if the scrim is showing.
float scrimColorAlpha = (mScrimColor >>> 24) / 255f;
int scrimColorOpaque = mScrimColor & 0xFF000000;
statusBarColor = ColorUtils.getColorWithOverlay(
statusBarColor, scrimColorOpaque, mStatusBarScrimFraction * scrimColorAlpha);
boolean needsDarkStatusBarIcons =
!ColorUtils.shouldUseLightForegroundOnBackground(statusBarColor);
ApiCompatibilityUtils.setStatusBarIconColor(root, needsDarkStatusBarIcons);
} else {
statusBarColor = isDefaultThemeColor ? Color.BLACK
: ColorUtils.getDarkenedColorForStatusBar(color);
}
ApiCompatibilityUtils.setStatusBarColor(mWindow, statusBarColor); /**
* Get the scrim applied color if the scrim is showing. Otherwise, return the original color.
* @param color Color to maybe apply scrim to.
* @return The resulting color.
*/
private @ColorInt int applyCurrentScrimToColor(@ColorInt int color) {
if (mScrimColor == 0) {
final View root = mWindow.getDecorView().getRootView();
final Resources resources = root.getResources();
mScrimColor = ApiCompatibilityUtils.getColor(resources, R.color.black_alpha_65);
}
// Apply a color overlay if the scrim is showing.
float scrimColorAlpha = (mScrimColor >>> 24) / 255f;
int scrimColorOpaque = mScrimColor & 0xFF000000;
return ColorUtils.getColorWithOverlay(
color, scrimColorOpaque, mStatusBarScrimFraction * scrimColorAlpha);
} }
/** /**
......
...@@ -4,9 +4,8 @@ ...@@ -4,9 +4,8 @@
package org.chromium.chrome.browser.status_indicator; package org.chromium.chrome.browser.status_indicator;
import static org.hamcrest.CoreMatchers.equalTo; import android.graphics.Color;
import static org.hamcrest.MatcherAssert.assertThat; import android.graphics.drawable.ColorDrawable;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import android.view.View; import android.view.View;
...@@ -15,6 +14,7 @@ import android.view.ViewGroup; ...@@ -15,6 +14,7 @@ import android.view.ViewGroup;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -32,6 +32,7 @@ import org.chromium.chrome.test.util.browser.Features; ...@@ -32,6 +32,7 @@ import org.chromium.chrome.test.util.browser.Features;
import org.chromium.content_public.browser.test.util.Criteria; import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper; import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.test.util.DisableAnimationsTestRule;
import org.chromium.ui.test.util.UiRestriction; import org.chromium.ui.test.util.UiRestriction;
/** /**
...@@ -46,6 +47,11 @@ import org.chromium.ui.test.util.UiRestriction; ...@@ -46,6 +47,11 @@ import org.chromium.ui.test.util.UiRestriction;
@Restriction({UiRestriction.RESTRICTION_TYPE_PHONE}) @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
public class StatusIndicatorTest { public class StatusIndicatorTest {
// clang-format on // clang-format on
@ClassRule
public static DisableAnimationsTestRule mDisableAnimationsTestRule =
new DisableAnimationsTestRule();
@Rule @Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
...@@ -82,15 +88,15 @@ public class StatusIndicatorTest { ...@@ -82,15 +88,15 @@ public class StatusIndicatorTest {
mActivityTestRule.getActivity().getFullscreenManager(); mActivityTestRule.getActivity().getFullscreenManager();
InstrumentationRegistry.getInstrumentation().waitForIdleSync(); InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertThat("Wrong initial Android view visibility.", Assert.assertEquals("Wrong initial Android view visibility.", View.GONE,
mStatusIndicatorContainer.getVisibility(), equalTo(View.GONE)); mStatusIndicatorContainer.getVisibility());
Assert.assertFalse("Wrong initial composited view visibility.", Assert.assertFalse("Wrong initial composited view visibility.",
mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing()); mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing());
assertThat("Wrong initial control container top margin.", Assert.assertEquals("Wrong initial control container top margin.", 0,
mControlContainerLayoutParams.topMargin, equalTo(0)); mControlContainerLayoutParams.topMargin);
TestThreadUtils.runOnUiThreadBlocking(mStatusIndicatorCoordinator::show);
TestThreadUtils.runOnUiThreadBlocking(() -> mStatusIndicatorCoordinator.show(
"Status", null, Color.BLACK, Color.WHITE, Color.WHITE));
InstrumentationRegistry.getInstrumentation().waitForIdleSync(); InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// TODO(sinansahin): Investigate setting the duration for the browser controls animations to // TODO(sinansahin): Investigate setting the duration for the browser controls animations to
...@@ -101,19 +107,32 @@ public class StatusIndicatorTest { ...@@ -101,19 +107,32 @@ public class StatusIndicatorTest {
fullscreenManager::getTopControlsMinHeightOffset)); fullscreenManager::getTopControlsMinHeightOffset));
// Now, the Android view should be visible. // Now, the Android view should be visible.
assertThat("Wrong Android view visibility.", mStatusIndicatorContainer.getVisibility(), Assert.assertEquals("Wrong Android view visibility.", View.VISIBLE,
equalTo(View.VISIBLE)); mStatusIndicatorContainer.getVisibility());
Assert.assertEquals("Wrong background color.", Color.BLACK,
((ColorDrawable) mStatusIndicatorContainer.getBackground()).getColor());
TestThreadUtils.runOnUiThreadBlocking(mStatusIndicatorCoordinator::hide); TestThreadUtils.runOnUiThreadBlocking(() -> mStatusIndicatorCoordinator.updateContent(
"Exit status", null, Color.WHITE, Color.BLACK, Color.BLACK, () -> {}));
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// The Android view visibility should be {@link View.GONE} after #hide(). // The Android view should be visible.
assertThat("Wrong Android view visibility.", mStatusIndicatorContainer.getVisibility(), Assert.assertEquals("Wrong Android view visibility.", View.VISIBLE,
equalTo(View.GONE)); mStatusIndicatorContainer.getVisibility());
Assert.assertEquals("Wrong background color.", Color.WHITE,
((ColorDrawable) mStatusIndicatorContainer.getBackground()).getColor());
TestThreadUtils.runOnUiThreadBlocking(mStatusIndicatorCoordinator::hide);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// Wait until the status indicator finishes animating, or becomes fully hidden. // Wait until the status indicator finishes animating, or becomes fully hidden.
CriteriaHelper.pollUiThread( CriteriaHelper.pollUiThread(
Criteria.equals(0, fullscreenManager::getTopControlsMinHeightOffset)); Criteria.equals(0, fullscreenManager::getTopControlsMinHeightOffset));
// The Android view visibility should be {@link View.GONE} after #hide().
Assert.assertEquals("Wrong Android view visibility.", View.GONE,
mStatusIndicatorContainer.getVisibility());
Assert.assertFalse("Composited view shouldn't be visible.", Assert.assertFalse("Composited view shouldn't be visible.",
mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing()); mStatusIndicatorSceneLayer.isSceneOverlayTreeShowing());
} }
......
...@@ -4,18 +4,25 @@ ...@@ -4,18 +4,25 @@
package org.chromium.chrome.browser.status_indicator; package org.chromium.chrome.browser.status_indicator;
import static org.hamcrest.CoreMatchers.equalTo; import static android.graphics.PorterDuff.Mode.SRC_IN;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.graphics.Color;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.test.annotation.UiThreadTest; import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.v4.content.res.ResourcesCompat; import android.support.v4.content.res.ResourcesCompat;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.TextView;
import org.junit.Assert; import org.chromium.base.MathUtils;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -23,6 +30,7 @@ import org.junit.runner.RunWith; ...@@ -23,6 +30,7 @@ import org.junit.runner.RunWith;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout; import org.chromium.components.browser_ui.widget.ViewResourceFrameLayout;
import org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor; import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
...@@ -37,7 +45,7 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase { ...@@ -37,7 +45,7 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase {
private static final String STATUS_TEXT = "Offline"; private static final String STATUS_TEXT = "Offline";
private ViewResourceFrameLayout mContainer; private ViewResourceFrameLayout mContainer;
private TextView mStatusTextView; private TextViewWithCompoundDrawables mStatusTextView;
private MockStatusIndicatorSceneLayer mSceneLayer; private MockStatusIndicatorSceneLayer mSceneLayer;
private PropertyModel mModel; private PropertyModel mModel;
...@@ -78,11 +86,9 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase { ...@@ -78,11 +86,9 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase {
@SmallTest @SmallTest
@UiThreadTest @UiThreadTest
public void testTextView() { public void testTextView() {
Assert.assertTrue( assertTrue("Wrong initial status text.", TextUtils.isEmpty(mStatusTextView.getText()));
"Wrong initial status text.", TextUtils.isEmpty(mStatusTextView.getText())); assertNull("Wrong initial status icon.", mStatusTextView.getCompoundDrawablesRelative()[0]);
Assert.assertNull( assertTrue(
"Wrong initial status icon.", mStatusTextView.getCompoundDrawablesRelative()[0]);
Assert.assertTrue(
"Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull()); "Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull());
Drawable drawable = ResourcesCompat.getDrawable(getActivity().getResources(), Drawable drawable = ResourcesCompat.getDrawable(getActivity().getResources(),
...@@ -93,10 +99,10 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase { ...@@ -93,10 +99,10 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase {
mModel.set(StatusIndicatorProperties.STATUS_ICON, drawable); mModel.set(StatusIndicatorProperties.STATUS_ICON, drawable);
}); });
assertThat("Wrong status text.", mStatusTextView.getText(), equalTo(STATUS_TEXT)); assertEquals("Wrong status text.", STATUS_TEXT, mStatusTextView.getText());
assertThat("Wrong status icon.", mStatusTextView.getCompoundDrawablesRelative()[0], assertEquals(
equalTo(drawable)); "Wrong status icon.", drawable, mStatusTextView.getCompoundDrawablesRelative()[0]);
Assert.assertTrue( assertTrue(
"Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull()); "Rest of the compound drawables are not null.", areRestOfCompoundDrawablesNull());
} }
...@@ -104,9 +110,9 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase { ...@@ -104,9 +110,9 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase {
@SmallTest @SmallTest
@UiThreadTest @UiThreadTest
public void testVisibility() { public void testVisibility() {
assertThat("Wrong initial Android view visibility.", mContainer.getVisibility(), assertEquals(
equalTo(View.GONE)); "Wrong initial Android view visibility.", View.GONE, mContainer.getVisibility());
Assert.assertFalse("Wrong initial composited view visibility.", assertFalse("Wrong initial composited view visibility.",
mSceneLayer.isSceneOverlayTreeShowing()); mSceneLayer.isSceneOverlayTreeShowing());
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
...@@ -114,18 +120,73 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase { ...@@ -114,18 +120,73 @@ public class StatusIndicatorViewBinderTest extends DummyUiActivityTestCase {
mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, true); mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, true);
}); });
assertThat( assertEquals("Android view is not visible.", View.VISIBLE, mContainer.getVisibility());
"Android view is not visible.", mContainer.getVisibility(), equalTo(View.VISIBLE)); assertTrue("Composited view is not visible.", mSceneLayer.isSceneOverlayTreeShowing());
Assert.assertTrue(
"Composited view is not visible.", mSceneLayer.isSceneOverlayTreeShowing());
TestThreadUtils.runOnUiThreadBlocking(() -> { TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE); mModel.set(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE);
mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false); mModel.set(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false);
}); });
assertThat("Android view is not gone.", mContainer.getVisibility(), equalTo(View.GONE)); assertEquals("Android view is not gone.", View.GONE, mContainer.getVisibility());
Assert.assertFalse("Composited view is visible.", mSceneLayer.isSceneOverlayTreeShowing()); assertFalse("Composited view is visible.", mSceneLayer.isSceneOverlayTreeShowing());
}
@Test
@SmallTest
@UiThreadTest
public void testColorAndTint() {
int bgColor = getActivity().getResources().getColor(R.color.modern_primary_color);
int textColor = getActivity().getResources().getColor(R.color.default_text_color);
assertEquals("Wrong initial background color.", bgColor,
((ColorDrawable) mContainer.getBackground()).getColor());
assertEquals("Wrong initial text color", textColor, mStatusTextView.getCurrentTextColor());
Drawable drawable = ResourcesCompat.getDrawable(getActivity().getResources(),
R.drawable.ic_error_white_24dp_filled, getActivity().getTheme());
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(StatusIndicatorProperties.STATUS_ICON, drawable);
mModel.set(StatusIndicatorProperties.BACKGROUND_COLOR, Color.BLUE);
mModel.set(StatusIndicatorProperties.TEXT_COLOR, Color.RED);
mModel.set(StatusIndicatorProperties.ICON_TINT, Color.GREEN);
});
assertEquals("Wrong background color.", Color.BLUE,
((ColorDrawable) mContainer.getBackground()).getColor());
assertEquals("Wrong text color.", Color.RED, mStatusTextView.getCurrentTextColor());
// There is no way to get the color filter below L. We could technically modify
// TextViewWithCompoundDrawables to cache it, but it's not worth the effort. Once the min
// apk is L, we won't be using color filters anyway.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
assertEquals("Wrong compound drawables tint",
new PorterDuffColorFilter(Color.GREEN, SRC_IN),
mStatusTextView.getCompoundDrawablesRelative()[0].getColorFilter());
}
}
@Test
@SmallTest
@UiThreadTest
public void testTextAlpha() {
assertEquals(
"Wrong initial text alpha.", 1.f, mStatusTextView.getAlpha(), MathUtils.EPSILON);
TestThreadUtils.runOnUiThreadBlocking(
() -> mModel.set(StatusIndicatorProperties.TEXT_ALPHA, .5f));
assertEquals("Wrong text alpha.", .5f, mStatusTextView.getAlpha(), MathUtils.EPSILON);
TestThreadUtils.runOnUiThreadBlocking(
() -> mModel.set(StatusIndicatorProperties.TEXT_ALPHA, .0f));
assertEquals("Wrong text alpha.", 0.f, mStatusTextView.getAlpha(), MathUtils.EPSILON);
TestThreadUtils.runOnUiThreadBlocking(
() -> mModel.set(StatusIndicatorProperties.TEXT_ALPHA, 1.f));
assertEquals("Wrong text alpha.", 1.f, mStatusTextView.getAlpha(), MathUtils.EPSILON);
} }
private boolean areRestOfCompoundDrawablesNull() { private boolean areRestOfCompoundDrawablesNull() {
......
...@@ -9,16 +9,22 @@ import android.graphics.Color; ...@@ -9,16 +9,22 @@ import android.graphics.Color;
import android.os.Build; import android.os.Build;
import android.support.test.filters.LargeTest; import android.support.test.filters.LargeTest;
import androidx.annotation.ColorInt;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MinAndroidSdkLevel; import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.Restriction; import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.flags.CachedFeatureFlags; import org.chromium.chrome.browser.flags.CachedFeatureFlags;
...@@ -31,6 +37,7 @@ import org.chromium.chrome.test.util.browser.ThemeTestUtils; ...@@ -31,6 +37,7 @@ import org.chromium.chrome.test.util.browser.ThemeTestUtils;
import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.test.util.UiRestriction; import org.chromium.ui.test.util.UiRestriction;
import org.chromium.ui.util.ColorUtils;
/** /**
* {@link StatusBarColorController} tests. * {@link StatusBarColorController} tests.
...@@ -42,10 +49,14 @@ public class StatusBarColorControllerTest { ...@@ -42,10 +49,14 @@ public class StatusBarColorControllerTest {
@Rule @Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule(); public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
private @ColorInt int mScrimColor;
@Before @Before
public void setUp() { public void setUp() {
CachedFeatureFlags.setForTesting(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, true); CachedFeatureFlags.setForTesting(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID, true);
mActivityTestRule.startMainActivityOnBlankPage(); mActivityTestRule.startMainActivityOnBlankPage();
mScrimColor = ApiCompatibilityUtils.getColor(mActivityTestRule.getActivity().getResources(),
org.chromium.chrome.R.color.black_alpha_65);
} }
@After @After
...@@ -109,7 +120,81 @@ public class StatusBarColorControllerTest { ...@@ -109,7 +120,81 @@ public class StatusBarColorControllerTest {
ThemeTestUtils.assertStatusBarColor(activity, expectedDefaultStandardColor); ThemeTestUtils.assertStatusBarColor(activity, expectedDefaultStandardColor);
} }
/**
* Test that the status indicator color is included in the color calculation correctly.
*/
@Test
@LargeTest
@Feature({"StatusBar"})
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
@Restriction({UiRestriction.RESTRICTION_TYPE_PHONE}) // Status bar is always black on tablets
public void testColorWithStatusIndicator() {
final ChromeActivity activity = mActivityTestRule.getActivity();
final StatusBarColorController statusBarColorController =
mActivityTestRule.getActivity().getStatusBarColorController();
final Supplier<Integer> statusBarColor = () -> activity.getWindow().getStatusBarColor();
final int initialColor = statusBarColor.get();
// Initially, StatusBarColorController#getStatusBarColorWithoutStatusIndicator should return
// the same color as the current status bar color.
Assert.assertEquals(
"Wrong initial value returned by #getStatusBarColorWithoutStatusIndicator().",
initialColor, statusBarColorController.getStatusBarColorWithoutStatusIndicator());
// Set a status indicator color.
TestThreadUtils.runOnUiThreadBlocking(
() -> statusBarColorController.onStatusIndicatorColorChanged(Color.BLUE));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Assert.assertEquals("Wrong status bar color for Android L.",
ColorUtils.getDarkenedColorForStatusBar(Color.BLUE),
statusBarColor.get().intValue());
} else {
Assert.assertEquals("Wrong status bar color for Android M+.", Color.BLUE,
statusBarColor.get().intValue());
}
// StatusBarColorController#getStatusBarColorWithoutStatusIndicator should still return the
// initial color.
Assert.assertEquals("Wrong value returned by #getStatusBarColorWithoutStatusIndicator().",
initialColor, statusBarColorController.getStatusBarColorWithoutStatusIndicator());
// Set scrim.
TestThreadUtils.runOnUiThreadBlocking(
() -> statusBarColorController.getStatusBarScrimDelegate()
.setStatusBarScrimFraction(.5f));
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// If we're already darkening the color for Android L, scrim shouldn't be applied.
Assert.assertEquals("Wrong status bar color w/ scrim for Android L.",
ColorUtils.getDarkenedColorForStatusBar(Color.BLUE),
statusBarColor.get().intValue());
} else {
// Otherwise, the resulting color should be a scrimmed version of the status bar color.
Assert.assertEquals("Wrong status bar color w/ scrim for Android M+.",
getScrimmedColor(Color.BLUE, .5f), statusBarColor.get().intValue());
}
TestThreadUtils.runOnUiThreadBlocking(() -> {
// Remove scrim.
statusBarColorController.getStatusBarScrimDelegate().setStatusBarScrimFraction(.0f);
// Set the status indicator color to the default, i.e. transparent.
statusBarColorController.onStatusIndicatorColorChanged(Color.TRANSPARENT);
});
// Now, the status bar color should be back to the initial color.
Assert.assertEquals(
"Wrong status bar color after the status indicator color is set to default.",
initialColor, statusBarColor.get().intValue());
}
private int defaultColorFallbackToBlack(int color) { private int defaultColorFallbackToBlack(int color) {
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? Color.BLACK : color; return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? Color.BLACK : color;
} }
private int getScrimmedColor(@ColorInt int color, float fraction) {
final float scrimColorAlpha = (mScrimColor >>> 24) / 255f;
final int scrimColorOpaque = mScrimColor & 0xFF000000;
return ColorUtils.getColorWithOverlay(color, scrimColorOpaque, fraction * scrimColorAlpha);
}
} }
...@@ -4,13 +4,14 @@ ...@@ -4,13 +4,14 @@
package org.chromium.chrome.browser.status_indicator; package org.chromium.chrome.browser.status_indicator;
import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Color;
import android.view.View; import android.view.View;
import org.junit.Before; import org.junit.Before;
...@@ -37,6 +38,12 @@ public class StatusIndicatorMediatorTest { ...@@ -37,6 +38,12 @@ public class StatusIndicatorMediatorTest {
@Mock @Mock
ChromeFullscreenManager mFullscreenManager; ChromeFullscreenManager mFullscreenManager;
@Mock
View mStatusIndicatorView;
@Mock
StatusIndicatorCoordinator.StatusIndicatorObserver mObserver;
private PropertyModel mModel; private PropertyModel mModel;
private StatusIndicatorMediator mMediator; private StatusIndicatorMediator mMediator;
...@@ -47,24 +54,38 @@ public class StatusIndicatorMediatorTest { ...@@ -47,24 +54,38 @@ public class StatusIndicatorMediatorTest {
.with(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE) .with(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY, View.GONE)
.with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false) .with(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE, false)
.build(); .build();
mMediator = new StatusIndicatorMediator(mModel, mFullscreenManager); mMediator = new StatusIndicatorMediator(mModel, mFullscreenManager, () -> Color.WHITE);
} }
@Test @Test
public void testHeightChangeAddsListener() { public void testHeightChangeAddsListener() {
// // After layout
mMediator.onStatusIndicatorHeightChanged(70); setViewHeight(70);
mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
verify(mFullscreenManager).addListener(mMediator); verify(mFullscreenManager).addListener(mMediator);
} }
@Test
public void testHeightChangeNotifiesObservers() {
// Add an observer.
mMediator.addObserver(mObserver);
// After layout
setViewHeight(70);
mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
verify(mObserver).onStatusIndicatorHeightChanged(70);
mMediator.removeObserver(mObserver);
}
@Test @Test
public void testHeightChangeDoesNotRemoveListenerImmediately() { public void testHeightChangeDoesNotRemoveListenerImmediately() {
// Show the status indicator. // Show the status indicator.
mMediator.onStatusIndicatorHeightChanged(70); setViewHeight(70);
mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
mMediator.onControlsOffsetChanged(0, 70, 0, 0, false); mMediator.onControlsOffsetChanged(0, 70, 0, 0, false);
// Now, hide it. Listener shouldn't be removed. // Now, hide it. Listener shouldn't be removed.
mMediator.onStatusIndicatorHeightChanged(0); setViewHeight(0);
mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
verify(mFullscreenManager, never()).removeListener(mMediator); verify(mFullscreenManager, never()).removeListener(mMediator);
// Once the hiding animation is done... // Once the hiding animation is done...
...@@ -76,52 +97,51 @@ public class StatusIndicatorMediatorTest { ...@@ -76,52 +97,51 @@ public class StatusIndicatorMediatorTest {
@Test @Test
public void testHeightChangeToZeroMakesAndroidViewGone() { public void testHeightChangeToZeroMakesAndroidViewGone() {
// Show the status indicator. // Show the status indicator.
mMediator.onStatusIndicatorHeightChanged(70); setViewHeight(70);
mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
mMediator.onControlsOffsetChanged(0, 70, 0, 0, false); mMediator.onControlsOffsetChanged(0, 70, 0, 0, false);
// The Android view should be visible at this point. // The Android view should be visible at this point.
assertThat(mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), assertEquals(View.VISIBLE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
equalTo(View.VISIBLE));
// Now hide it. // Now hide it.
mMediator.onStatusIndicatorHeightChanged(0); setViewHeight(0);
mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
// The hiding animation... // The hiding animation...
mMediator.onControlsOffsetChanged(0, 30, 0, 0, false); mMediator.onControlsOffsetChanged(0, 30, 0, 0, false);
// Android view will be gone once the animation starts. // Android view will be gone once the animation starts.
assertThat( assertEquals(View.GONE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), equalTo(View.GONE));
mMediator.onControlsOffsetChanged(0, 0, 0, 0, false); mMediator.onControlsOffsetChanged(0, 0, 0, 0, false);
// Shouldn't make the Android view invisible. It should stay gone. // Shouldn't make the Android view invisible. It should stay gone.
assertThat( assertEquals(View.GONE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), equalTo(View.GONE));
} }
@Test @Test
public void testOffsetChangeUpdatesVisibility() { public void testOffsetChangeUpdatesVisibility() {
// Initially, the Android view should be GONE. // Initially, the Android view should be GONE.
mMediator.onStatusIndicatorHeightChanged(20); setViewHeight(20);
assertThat( mMediator.onLayoutChange(mStatusIndicatorView, 0, 0, 0, 0, 0, 0, 0, 0);
mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), equalTo(View.GONE)); assertEquals(View.GONE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
// Assume the status indicator is completely hidden. // Assume the status indicator is completely hidden.
mMediator.onControlsOffsetChanged(0, 0, 0, 0, false); mMediator.onControlsOffsetChanged(0, 0, 0, 0, false);
assertThat(mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), assertEquals(View.INVISIBLE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
equalTo(View.INVISIBLE));
assertFalse(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE)); assertFalse(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
// Status indicator is partially showing. // Status indicator is partially showing.
mMediator.onControlsOffsetChanged(0, 10, 0, 0, false); mMediator.onControlsOffsetChanged(0, 10, 0, 0, false);
assertThat(mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), assertEquals(View.INVISIBLE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
equalTo(View.INVISIBLE));
assertTrue(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE)); assertTrue(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
// Status indicator is fully showing, 20px. // Status indicator is fully showing, 20px.
mMediator.onControlsOffsetChanged(0, 20, 0, 0, false); mMediator.onControlsOffsetChanged(0, 20, 0, 0, false);
assertThat(mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), assertEquals(View.VISIBLE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
equalTo(View.VISIBLE));
assertTrue(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE)); assertTrue(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
// Hide again. // Hide again.
mMediator.onControlsOffsetChanged(0, 0, 0, 0, false); mMediator.onControlsOffsetChanged(0, 0, 0, 0, false);
assertThat(mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY), assertEquals(View.INVISIBLE, mModel.get(StatusIndicatorProperties.ANDROID_VIEW_VISIBILITY));
equalTo(View.INVISIBLE));
assertFalse(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE)); assertFalse(mModel.get(StatusIndicatorProperties.COMPOSITED_VIEW_VISIBLE));
} }
private void setViewHeight(int height) {
when(mStatusIndicatorView.getHeight()).thenReturn(height);
}
} }
...@@ -2986,6 +2986,14 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p ...@@ -2986,6 +2986,14 @@ To change this setting, <ph name="BEGIN_LINK">&lt;resetlink&gt;</ph>reset sync<p
See downloads See downloads
</message> </message>
<!-- Offline indicator v2 -->
<message name="IDS_OFFLINE_INDICATOR_V2_OFFLINE_TEXT" desc="Text to be displayed in the status indicator above the toolbar that lets the users know they are offline.">
No internet connection
</message>
<message name="IDS_OFFLINE_INDICATOR_V2_BACK_ONLINE_TEXT" desc="Text to be displayed in the status indicator above the toolbar when the device goes back online.">
Back online
</message>
<!-- Find in page --> <!-- Find in page -->
<message name="IDS_HINT_FIND_IN_PAGE" desc="Hint text to show for the find in page search field when the search field is empty."> <message name="IDS_HINT_FIND_IN_PAGE" desc="Hint text to show for the find in page search field when the search field is empty.">
Find in page Find in page
......
...@@ -9,6 +9,7 @@ import android.content.res.ColorStateList; ...@@ -9,6 +9,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources; import android.content.res.Resources;
import android.support.v7.content.res.AppCompatResources; import android.support.v7.content.res.AppCompatResources;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes; import androidx.annotation.ColorRes;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
...@@ -25,7 +26,7 @@ public class ChromeColors { ...@@ -25,7 +26,7 @@ public class ChromeColors {
* adaptive default color. * adaptive default color.
* @return The default theme color. * @return The default theme color.
*/ */
public static @ColorRes int getDefaultThemeColor(Resources res, boolean forceDarkBgColor) { public static @ColorInt int getDefaultThemeColor(Resources res, boolean forceDarkBgColor) {
return forceDarkBgColor return forceDarkBgColor
? ApiCompatibilityUtils.getColor(res, R.color.toolbar_background_primary_dark) ? ApiCompatibilityUtils.getColor(res, R.color.toolbar_background_primary_dark)
: ApiCompatibilityUtils.getColor(res, R.color.toolbar_background_primary); : ApiCompatibilityUtils.getColor(res, R.color.toolbar_background_primary);
...@@ -40,7 +41,7 @@ public class ChromeColors { ...@@ -40,7 +41,7 @@ public class ChromeColors {
* returns adaptive primary background color. * returns adaptive primary background color.
* @return The primary background color. * @return The primary background color.
*/ */
public static @ColorRes int getPrimaryBackgroundColor(Resources res, boolean forceDarkBgColor) { public static @ColorInt int getPrimaryBackgroundColor(Resources res, boolean forceDarkBgColor) {
return forceDarkBgColor return forceDarkBgColor
? ApiCompatibilityUtils.getColor(res, org.chromium.ui.R.color.dark_primary_color) ? ApiCompatibilityUtils.getColor(res, org.chromium.ui.R.color.dark_primary_color)
: ApiCompatibilityUtils.getColor(res, org.chromium.ui.R.color.modern_primary_color); : ApiCompatibilityUtils.getColor(res, org.chromium.ui.R.color.modern_primary_color);
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
<color name="default_bg_color_elev_2">@color/default_bg_color_dark_elev_2</color> <color name="default_bg_color_elev_2">@color/default_bg_color_dark_elev_2</color>
<color name="default_bg_color_elev_3">@color/default_bg_color_dark_elev_3</color> <color name="default_bg_color_elev_3">@color/default_bg_color_dark_elev_3</color>
<color name="default_bg_color_elev_4">@color/default_bg_color_dark_elev_4</color> <color name="default_bg_color_elev_4">@color/default_bg_color_dark_elev_4</color>
<color name="default_bg_color_blue">@color/modern_blue_300</color>
<!-- Bottom sheet colors --> <!-- Bottom sheet colors -->
<color name="sheet_bg_color">@color/default_bg_color_dark_elev_4</color> <color name="sheet_bg_color">@color/default_bg_color_dark_elev_4</color>
......
...@@ -46,6 +46,8 @@ ...@@ -46,6 +46,8 @@
<color name="modern_primary_color" tools:ignore="UnusedResources">@color/default_bg_color_light</color> <color name="modern_primary_color" tools:ignore="UnusedResources">@color/default_bg_color_light</color>
<color name="modern_secondary_color" tools:ignore="UnusedResources">@color/modern_grey_100</color> <color name="modern_secondary_color" tools:ignore="UnusedResources">@color/modern_grey_100</color>
<color name="default_bg_color_blue" tools:ignore="UnusedResources">@color/modern_blue_600</color>
<!-- Dark background and branding color. --> <!-- Dark background and branding color. -->
<color name="dark_primary_color" tools:ignore="UnusedResources"> <color name="dark_primary_color" tools:ignore="UnusedResources">
@color/modern_grey_900 @color/modern_grey_900
......
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