Commit ec6945d8 authored by Theresa's avatar Theresa Committed by Commit Bot

[EoC] Add toolbar view, coordinator, and close button binding

Adds a ToolbarCoordinator, ToolbarView, and ToolbarModelChangeProcessor
to display and manage the contextual suggestions toolbar.

Also introduces a PropertyObservable base class in the modelutil
package.

BUG=822943

Change-Id: I252e1c48afc8f053a5271cefa9f4a7142d816e32
Reviewed-on: https://chromium-review.googlesource.com/974289
Commit-Queue: Theresa <twellington@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#545182}
parent e5389906
......@@ -8,4 +8,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:paddingTop="@dimen/bottom_control_container_height" />
\ No newline at end of file
android:paddingTop="@dimen/contextual_suggestions_expanded_toolbar_height"
android:background="@android:color/white" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2018 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<org.chromium.chrome.browser.contextual_suggestions.ToolbarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@android:color/white" >
<!-- Use 48dp width and 16dp end/start padding to produce a 16dp, centered icon. -->
<ImageView
android:layout_height="match_parent"
android:layout_width="48dp"
android:src="@drawable/ic_logo_googleg_24dp"
android:scaleType="centerInside"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:contentDescription="@null" />
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/more"
android:textAppearance="@style/BlackBodyDefault" />
<!-- Use 50dp width and 16dp end/start padding to produce an 18dp, centered icon. -->
<ImageView
android:id="@+id/close_button"
android:layout_height="match_parent"
android:layout_width="50dp"
android:src="@drawable/btn_close"
android:scaleType="centerInside"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:contentDescription="@string/close" />
</org.chromium.chrome.browser.contextual_suggestions.ToolbarView>
\ No newline at end of file
......@@ -204,7 +204,7 @@
<!-- Full Screen Dimensions -->
<!-- Should match toolbar_height_no_shadow -->
<dimen name="control_container_height">56dp</dimen>
<dimen name="bottom_control_container_height">56dp</dimen>
<dimen name="bottom_control_container_height">40dp</dimen>
<dimen name="custom_tabs_control_container_height">56dp</dimen>
<dimen name="fullscreen_activity_control_container_height">0dp</dimen>
......@@ -546,4 +546,7 @@
<item type="dimen" name="dialog_fixed_width_minor">100%</item>
<item type="dimen" name="dialog_fixed_height_major">100%</item>
<item type="dimen" name="dialog_fixed_height_minor">100%</item>
<!-- Contextual suggestions dimensions -->
<dimen name="contextual_suggestions_expanded_toolbar_height">56dp</dimen>
</resources>
......@@ -19,7 +19,7 @@ import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
/**
* Coordinator for the content sub-component. Responsible for communication with the parent
* {@link ContextualSuggestionsCoordinator} and lifecycle of component objects.
* {@link ContextualSuggestionsCoordinator} and lifecycle of sub-component objects.
*/
class ContentCoordinator {
private final ContextualSuggestionsModel mModel;
......@@ -49,7 +49,7 @@ class ContentCoordinator {
mRecyclerView.setAdapter(adapter);
mModelChangeProcessor = new RecyclerViewModelChangeProcessor<>(adapter);
mModel.addObserver(mModelChangeProcessor);
mModel.mSuggestionsList.addObserver(mModelChangeProcessor);
}
/** @return The content {@link View}. */
......@@ -64,12 +64,8 @@ class ContentCoordinator {
/** Destroy the content component. */
void destroy() {
if (mRecyclerView == null) return;
mRecyclerView.setAdapter(null);
mRecyclerView = null;
mModel.removeObserver(mModelChangeProcessor);
mModelChangeProcessor = null;
// The model outlives the content sub-component. Remove the observer so that this object
// can be garbage collected.
mModel.mSuggestionsList.removeObserver(mModelChangeProcessor);
}
}
......@@ -64,7 +64,7 @@ class ContextualSuggestionsAdapter
*/
ContextualSuggestionsAdapter(Context context, Profile profile, UiConfig uiConfig,
SuggestionsUiDelegate uiDelegate, ContextualSuggestionsModel model) {
super(model);
super(model.mSuggestionsList);
setViewBinder(new ContextualSuggestionsViewBinder());
......
......@@ -10,15 +10,20 @@ import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetCon
/** A {@link BottomSheetContent} that displays contextual suggestions. */
public class ContextualSuggestionsBottomSheetContent implements BottomSheetContent {
private ContentCoordinator mContentCoordinator;
private final ContentCoordinator mContentCoordinator;
private final ToolbarCoordinator mToolbarCoordinator;
/**
* Construct a new {@link ContextualSuggestionsBottomSheetContent}.
* @param contentCoordinator The {@link ContentCoordinator} that manages content to be
* displayed.
* @param toolbarCoordinator The {@link ToolbarCoordinator} that manages the toolbar to be
* displayed.
*/
ContextualSuggestionsBottomSheetContent(ContentCoordinator contentCoordinator) {
ContextualSuggestionsBottomSheetContent(
ContentCoordinator contentCoordinator, ToolbarCoordinator toolbarCoordinator) {
mContentCoordinator = contentCoordinator;
mToolbarCoordinator = toolbarCoordinator;
}
@Override
......@@ -28,7 +33,7 @@ public class ContextualSuggestionsBottomSheetContent implements BottomSheetConte
@Override
public View getToolbarView() {
return null;
return mToolbarCoordinator.getView();
}
@Override
......@@ -37,9 +42,7 @@ public class ContextualSuggestionsBottomSheetContent implements BottomSheetConte
}
@Override
public void destroy() {
mContentCoordinator = null;
}
public void destroy() {}
@Override
public boolean applyDefaultTopPadding() {
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.contextual_suggestions;
import android.support.annotation.Nullable;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
import org.chromium.chrome.browser.profiles.Profile;
......@@ -22,17 +24,16 @@ import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
* They share a {@link ContextualSuggestionsMediator} and {@link ContextualSuggestionsModel}.
*/
public class ContextualSuggestionsCoordinator {
private ChromeActivity mActivity;
private BottomSheet mBottomSheet;
private Profile mProfile;
private ContextualSuggestionsModel mModel;
private ContextualSuggestionsMediator mMediator;
private ContentCoordinator mContentCoordinator;
private final ChromeActivity mActivity;
private final BottomSheet mBottomSheet;
private final Profile mProfile;
private final ContextualSuggestionsModel mModel;
private final ContextualSuggestionsMediator mMediator;
private final SuggestionsUiDelegateImpl mUiDelegate;
private SuggestionsUiDelegateImpl mUiDelegate;
private ContextualSuggestionsBottomSheetContent mBottomSheetContent;
private @Nullable ToolbarCoordinator mToolbarCoordinator;
private @Nullable ContentCoordinator mContentCoordinator;
private @Nullable ContextualSuggestionsBottomSheetContent mBottomSheetContent;
/**
* Construct a new {@link ContextualSuggestionsCoordinator}.
......@@ -62,6 +63,7 @@ public class ContextualSuggestionsCoordinator {
public void destroy() {
mMediator.destroy();
if (mToolbarCoordinator != null) mToolbarCoordinator.destroy();
if (mContentCoordinator != null) mContentCoordinator.destroy();
if (mBottomSheetContent != null) mBottomSheetContent.destroy();
}
......@@ -73,24 +75,30 @@ public class ContextualSuggestionsCoordinator {
// TODO(twellington): Introduce another method that creates bottom sheet content with only
// a toolbar view when suggestions are fist available, and use this method to construct the
// content view when the sheet is opened.
mToolbarCoordinator = new ToolbarCoordinator(mActivity, mBottomSheet, mModel);
mContentCoordinator =
new ContentCoordinator(mActivity, mBottomSheet, mProfile, mUiDelegate, mModel);
mBottomSheetContent = new ContextualSuggestionsBottomSheetContent(mContentCoordinator);
mBottomSheetContent = new ContextualSuggestionsBottomSheetContent(
mContentCoordinator, mToolbarCoordinator);
mBottomSheet.showContent(mBottomSheetContent);
}
/** Removes contextual suggestions from the {@link BottomSheet}. */
void removeSuggestions() {
if (mToolbarCoordinator != null) {
mToolbarCoordinator.destroy();
mToolbarCoordinator = null;
}
if (mContentCoordinator != null) {
mContentCoordinator.destroy();
mContentCoordinator = null;
}
if (mBottomSheetContent == null) return;
mBottomSheet.showContent(null);
mBottomSheetContent.destroy();
mBottomSheetContent = null;
if (mBottomSheetContent != null) {
mBottomSheet.showContent(null);
mBottomSheetContent.destroy();
mBottomSheetContent = null;
}
}
}
......@@ -23,6 +23,7 @@ import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.ui.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* A mediator for the contextual suggestions UI component responsible for interacting with
......@@ -120,18 +121,22 @@ class ContextualSuggestionsMediator {
suggestions.size() + " suggestions fetched", Toast.LENGTH_SHORT)
.show();
if (suggestions.size() > 0) {
mModel.setSuggestions(suggestions);
mCoordinator.displaySuggestions();
};
if (suggestions.size() > 0) displaySuggestions(suggestions);
});
}
private void clearSuggestions() {
mModel.setSuggestions(new ArrayList<SnippetArticle>());
mModel.setCloseButtonOnClickListener(null);
mCoordinator.removeSuggestions();
}
private void displaySuggestions(List<SnippetArticle> suggestions) {
mModel.setSuggestions(suggestions);
mModel.setCloseButtonOnClickListener(view -> { clearSuggestions(); });
mCoordinator.displaySuggestions();
}
private boolean isContextTheSame(String newUrl) {
return UrlUtilities.urlsMatchIgnoringFragments(newUrl, mCurrentContextUrl);
}
......
......@@ -4,48 +4,70 @@
package org.chromium.chrome.browser.contextual_suggestions;
import android.view.View.OnClickListener;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import java.util.ArrayList;
import java.util.List;
/** A model for the contextual suggestions UI component. */
class ContextualSuggestionsModel extends ListObservable<SnippetArticle> {
private List<SnippetArticle> mSuggestions = new ArrayList<>();
class ContextualSuggestionsModel extends PropertyObservable<PropertyKey> {
/** A {@link ListObservable} containing the current list of suggestions. */
class SuggestionsList extends ListObservable<SnippetArticle> {
private List<SnippetArticle> mSuggestions = new ArrayList<>();
private void setSuggestions(List<SnippetArticle> suggestions) {
assert suggestions != null;
int oldLength = getItemCount();
mSuggestions = suggestions;
if (oldLength != 0) notifyItemRangeRemoved(0, oldLength);
if (mSuggestions.size() != 0) {
notifyItemRangeInserted(0, mSuggestions.size());
}
}
@Override
public int getItemCount() {
return mSuggestions.size();
}
@Override
public SnippetArticle getItem(int position) {
assert position < mSuggestions.size();
return mSuggestions.get(position);
}
}
SuggestionsList mSuggestionsList = new SuggestionsList();
private OnClickListener mCloseButtonOnClickListener;
/**
* @param suggestions The list of current suggestions. May be an empty list if no
* suggestions are available.
*/
void setSuggestions(List<SnippetArticle> suggestions) {
assert suggestions != null;
if (suggestions.size() == 0 && mSuggestions.size() == 0) return;
int oldLength = getItemCount();
mSuggestions = suggestions;
if (oldLength != 0) notifyItemRangeRemoved(0, oldLength);
if (mSuggestions.size() != 0) {
notifyItemRangeInserted(0, mSuggestions.size());
}
mSuggestionsList.setSuggestions(suggestions);
}
/** @return The list of current suggestions. */
List<SnippetArticle> getSuggestions() {
return mSuggestions;
return mSuggestionsList.mSuggestions;
}
@Override
public int getItemCount() {
return mSuggestions.size();
/** @param listener The {@link OnClickListener} for the close button. */
void setCloseButtonOnClickListener(OnClickListener listener) {
mCloseButtonOnClickListener = listener;
notifyPropertyChanged(new PropertyKey(PropertyKey.ON_CLICK_LISTENER_PROPERTY));
}
@Override
public SnippetArticle getItem(int position) {
assert position < mSuggestions.size();
return mSuggestions.get(position);
/** @return The {@link OnClickListener} for the close button. */
OnClickListener getCloseButtonOnClickListener() {
return mCloseButtonOnClickListener;
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextual_suggestions;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains a key uniquely identifying properties held in the {@link ContextualSuggestionsModel}.
*/
class PropertyKey {
/** The unique identifiers for properties held in the model. */
@IntDef({ON_CLICK_LISTENER_PROPERTY})
@Retention(RetentionPolicy.SOURCE)
@interface Key {}
static final int ON_CLICK_LISTENER_PROPERTY = 0;
// TODO(twellington): Add a property for the toolbar title.
@Key
Integer mKey;
PropertyKey(@Key Integer key) {
mKey = key;
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextual_suggestions;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.R;
/**
* Coordinator for the toolbar sub-component. Responsible for communication with the parent
* {@link ContextualSuggestionsCoordinator} and lifecycle of sub-component objects.
*/
class ToolbarCoordinator {
private final ContextualSuggestionsModel mModel;
private ToolbarView mToolbarView;
private ToolbarModelChangeProcessor mModelChangeProcessor;
/**
* Construct a new {@link ToolbarCoordinator}.
* @param context The {@link Context} used to retrieve resources.
* @param parentView The parent {@link View} to which the content will eventually be attached.
* @param model The {@link ContextualSuggestionsModel} for the component.
*/
ToolbarCoordinator(Context context, ViewGroup parentView, ContextualSuggestionsModel model) {
mModel = model;
mToolbarView = (ToolbarView) LayoutInflater.from(context).inflate(
R.layout.contextual_suggestions_toolbar, parentView, false);
mModelChangeProcessor = new ToolbarModelChangeProcessor(mToolbarView, mModel);
mModel.addObserver(mModelChangeProcessor);
}
/** @return The content {@link View}. */
View getView() {
return mToolbarView;
}
/** Destroy the toolbar component. */
void destroy() {
// The model outlives the toolbar sub-component. Remove the observer so that this object
// can be garbage collected.
mModel.removeObserver(mModelChangeProcessor);
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextual_suggestions;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable.PropertyObserver;
/**
* A model change processor for use with a {@link ToolbarView}. The
* {@link ToolbarModelChangeProcessor} should be registered as a property observer of
* {@link ContextualSuggestionsModel}. Internally uses a view binder to bind model
* properties to the toolbar view.
*/
class ToolbarModelChangeProcessor implements PropertyObserver<PropertyKey> {
private static class ViewBinder {
/**
* Bind the changed property to the toolbar view.
* @param view The {@link ToolbarView} to which data will be bound.
* @param model The {@link ContextualSuggestionsModel} containing the data to be bound.
* @param propertyKey The {@link PropertyKey} of the property that has changed.
*/
private static void bindProperty(
ToolbarView view, ContextualSuggestionsModel model, PropertyKey propertyKey) {
switch (propertyKey.mKey) {
case PropertyKey.ON_CLICK_LISTENER_PROPERTY:
view.setOnClickListener(model.getCloseButtonOnClickListener());
break;
default:
assert false;
}
}
}
private final ToolbarView mToolbarView;
private final ContextualSuggestionsModel mModel;
/**
* Construct a new ToolbarModelChangeProcessor.
* @param view The {@link ToolbarView} to which data will be bound.
* @param model The {@link ContextualSuggestionsModel} containing the data to be bound.
*/
ToolbarModelChangeProcessor(ToolbarView view, ContextualSuggestionsModel model) {
mToolbarView = view;
mModel = model;
// The ToolbarCoordinator is created dynamically as needed, so the initial model state
// needs to be bound on creation.
ViewBinder.bindProperty(
mToolbarView, mModel, new PropertyKey(PropertyKey.ON_CLICK_LISTENER_PROPERTY));
}
@Override
public void onPropertyChanged(PropertyObservable<PropertyKey> source, PropertyKey propertyKey) {
ViewBinder.bindProperty(mToolbarView, mModel, propertyKey);
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.contextual_suggestions;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import org.chromium.chrome.R;
/** The toolbar view, containing an icon, title and close button. */
public class ToolbarView extends LinearLayout {
private View mCloseButton;
public ToolbarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCloseButton = findViewById(R.id.close_button);
}
void setCloseButtonOnClickListener(OnClickListener listener) {
mCloseButton.setOnClickListener(listener);
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.modelutil;
import android.support.annotation.Nullable;
import org.chromium.base.ObserverList;
/**
* A base class for models maintaining a set of properties.
*
* @param <T> The type of the property key used for uniquely identifying properties.
*/
public class PropertyObservable<T> {
/**
* An observer to be notified of changes to a {@link PropertyObservable}.
*
* @param <T> The type of the property key used for uniquely identifying properties.
*/
public interface PropertyObserver<T> {
/**
* Notifies that the given {@code property} of the observed {@code source} has changed.
* @param source The object whose property has changed
* @param property The key of the property that has changed.
*/
void onPropertyChanged(PropertyObservable<T> source, @Nullable T propertyKey);
}
private final ObserverList<PropertyObserver<T>> mObservers = new ObserverList<>();
/**
* @param observer An observer to be notified of changes to the model.
*/
public void addObserver(PropertyObserver<T> observer) {
mObservers.addObserver(observer);
}
/**
* @param observer The observer to remove.
*/
public void removeObserver(PropertyObserver<T> observer) {
mObservers.removeObserver(observer);
}
/**
* Notifies observers that the property identified by {@code propertyKey} has changed.
*
* @param propertyKey The key of the property that has changed.
*/
protected void notifyPropertyChanged(T propertyKey) {
for (PropertyObserver<T> observer : mObservers) {
observer.onPropertyChanged(this, propertyKey);
}
}
}
......@@ -495,7 +495,8 @@ public class BottomSheet
mToolbarHolder =
(TouchRestrictingFrameLayout) findViewById(R.id.bottom_sheet_toolbar_container);
mDefaultToolbarView = mToolbarHolder.findViewById(R.id.bottom_sheet_toolbar);
mToolbarHeight = mDefaultToolbarView.getHeight();
mToolbarHeight = activity.getResources().getDimensionPixelSize(
R.dimen.bottom_control_container_height);
mActivity = activity;
mActionBarDelegate = new ViewShiftingActionBarDelegate(mActivity, this);
......@@ -591,9 +592,6 @@ public class BottomSheet
return;
}
mToolbarHeight = bottom - top;
updateSheetStateRatios();
if (!mGestureDetector.isScrolling()) {
cancelAnimation();
......
......@@ -297,6 +297,10 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsMediator.java",
"java/src/org/chromium/chrome/browser/contextual_suggestions/ContextualSuggestionsModel.java",
"java/src/org/chromium/chrome/browser/contextual_suggestions/DummyEventReporter.java",
"java/src/org/chromium/chrome/browser/contextual_suggestions/PropertyKey.java",
"java/src/org/chromium/chrome/browser/contextual_suggestions/ToolbarCoordinator.java",
"java/src/org/chromium/chrome/browser/contextual_suggestions/ToolbarModelChangeProcessor.java",
"java/src/org/chromium/chrome/browser/contextual_suggestions/ToolbarView.java",
"java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java",
"java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java",
"java/src/org/chromium/chrome/browser/coordinator/CoordinatorLayoutForPointer.java",
......@@ -660,6 +664,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java",
"java/src/org/chromium/chrome/browser/modelutil/ListObservable.java",
"java/src/org/chromium/chrome/browser/modelutil/PropertyObservable.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewModelChangeProcessor.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java",
"java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java",
......
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