Skip to content

Commit

Permalink
Render Tabs into Bottom sheet
Browse files Browse the repository at this point in the history
This CL shapes the role of a KeyboardAccessoryData.Tab element:
It is an element that provides a specific benefit to the Manual UI by...
... providing data that is used by the bar to render a tab (upcoming)
... providing the content for a separate tab in the bottom sheet.

This means, that the bottom sheet provides space for tabs and manages the
general visibility and active states of tabs.
The exact content of a tab doesn't matter to the AccessorySheetComponent.
That way, it should become fairly straight forward to implement a new tab
that is rendered into bottom sheet and accessory.

The first concrete instance of a bottom sheet will follow soon: a
bottom sheet that contains password related actions and data.
(It's important to not mix this specific data into the AccessorySheet
component as other upcoming sheets are already planned, like one for
payments and one for address data.)

Bug: 811747
Change-Id: Id8bd8c389496246166d26a6c298056a142c36566
Reviewed-on: https://chromium-review.googlesource.com/1047285
Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
Reviewed-by: Bernhard Bauer <bauerb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#559180}
  • Loading branch information
FHorschig authored and Commit Bot committed May 16, 2018
1 parent 4037935 commit a4ae2e1
Show file tree
Hide file tree
Showing 23 changed files with 618 additions and 145 deletions.
13 changes: 13 additions & 0 deletions chrome/android/java/res/layout/empty_accessory_sheet.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?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. -->

<!-- This Layout is mainly used in tests. Therefore use possible UnusedResource warnings. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="UnusedResources"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
5 changes: 1 addition & 4 deletions chrome/android/java/res/layout/keyboard_accessory_sheet.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->

<LinearLayout
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_accessory"
android:background="@drawable/keyboard_accessory_background"
android:contentDescription="@string/autofill_keyboard_accessory_content_description"
android:fillViewport="true"
android:layout_gravity="start|bottom"
android:layout_height="@dimen/keyboard_accessory_sheet_height"
android:layout_width="match_parent"
android:orientation="vertical"
android:visibility="gone"/>
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import org.chromium.chrome.browser.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryCoordinator;
import org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingCoordinator;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
Expand Down Expand Up @@ -266,7 +267,7 @@ public AppMenuHandler get(Activity activity, AppMenuPropertiesDelegate delegate,
private BottomSheet mBottomSheet;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private FadingBackgroundView mFadingBackgroundView;
private KeyboardAccessoryCoordinator mKeyboardAccessoryCoordinator;
private ManualFillingCoordinator mManualFillingController;

// Time in ms that it took took us to inflate the initial layout
private long mInflateInitialLayoutDurationMs;
Expand Down Expand Up @@ -391,8 +392,9 @@ public void postInflationStartup() {
// SurfaceView's 'hole' clipping during animations that are notified to the window.
getWindowAndroid().setAnimationPlaceholderView(mCompositorViewHolder.getCompositorView());

mKeyboardAccessoryCoordinator = new KeyboardAccessoryCoordinator(
getWindowAndroid(), findViewById(R.id.keyboard_accessory_stub));
mManualFillingController = new ManualFillingCoordinator(getWindowAndroid(),
findViewById(R.id.keyboard_accessory_stub),
findViewById(R.id.keyboard_accessory_sheet_stub));

initializeToolbar();
initializeTabModels();
Expand Down Expand Up @@ -676,7 +678,7 @@ public FindToolbarManager getFindToolbarManager() {
* @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity.
*/
public KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mKeyboardAccessoryCoordinator;
return mManualFillingController.getKeyboardAccessory();
}

/**
Expand Down Expand Up @@ -1223,9 +1225,9 @@ protected final void onDestroy() {
mTabContentManager = null;
}

if (mKeyboardAccessoryCoordinator != null) {
mKeyboardAccessoryCoordinator.destroy();
mKeyboardAccessoryCoordinator = null;
if (mManualFillingController != null) {
mManualFillingController.destroy();
mManualFillingController = null;
}

AccessibilityManager manager = (AccessibilityManager)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.autofill.keyboard_accessory;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import org.chromium.chrome.browser.modelutil.SimpleListObservable;

import java.util.HashMap;
import java.util.Map;

/**
* This {@link PagerAdapter} renders an observable list of {@link KeyboardAccessoryData.Tab}s into a
* {@link ViewPager}. It instantiates the tab views based on the layout they provide.
*/
class AccessoryPagerAdapter extends PagerAdapter {
private final SimpleListObservable<KeyboardAccessoryData.Tab> mTabList;
private final Map<KeyboardAccessoryData.Tab, ViewGroup> mViews;

/**
* Creates the PagerAdapter that populates a ViewPager based on a held list of tabs.
* @param tabList The list that contains the tabs to instantiate.
*/
public AccessoryPagerAdapter(SimpleListObservable<KeyboardAccessoryData.Tab> tabList) {
mTabList = tabList;
mViews = new HashMap<>(mTabList.getItemCount());
}

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
KeyboardAccessoryData.Tab tab = mTabList.get(position);
ViewGroup layout = mViews.get(tab);
if (layout == null) {
layout = (ViewGroup) LayoutInflater.from(container.getContext())
.inflate(tab.getTabLayout(), container, false);
mViews.put(tab, layout);
if (container.indexOfChild(layout) == -1) container.addView(layout);
if (tab.getListener() != null) {
tab.getListener().onTabCreated(layout);
}
}
return layout;
}

@Override
public void destroyItem(@NonNull ViewGroup container, int position, @Nullable Object object) {
if (mViews.get(mTabList.get(position)) == null) return;
ViewGroup layout = mViews.get(mTabList.get(position));
if (container.indexOfChild(layout) != -1) container.removeView(layout);
}

@Override
public int getCount() {
return mTabList.getItemCount();
}

@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

package org.chromium.chrome.browser.autofill.keyboard_accessory;

import android.view.View;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.ViewStub;

import org.chromium.base.VisibleForTesting;
Expand All @@ -14,7 +16,7 @@
/**
* Creates and owns all elements which are part of the accessory sheet component.
* It's part of the controller but will mainly forward events (like showing the sheet) and handle
* communication with the {@link ManualFillingController} (e.g. add a tab to trigger the sheet).
* communication with the {@link ManualFillingCoordinator} (e.g. add a tab to trigger the sheet).
* to the {@link AccessorySheetMediator}.
*/
public class AccessorySheetCoordinator {
Expand All @@ -26,18 +28,42 @@ public class AccessorySheetCoordinator {
* @param viewStub The view stub that can be inflated into the accessory layout.
*/
public AccessorySheetCoordinator(ViewStub viewStub) {
LazyViewBinderAdapter.StubHolder<View> stubHolder =
LazyViewBinderAdapter.StubHolder<ViewPager> stubHolder =
new LazyViewBinderAdapter.StubHolder<>(viewStub);
AccessorySheetModel model = new AccessorySheetModel();
model.addObserver(new PropertyModelChangeProcessor<>(
model, stubHolder, new LazyViewBinderAdapter<>(new AccessorySheetViewBinder())));
mMediator = new AccessorySheetMediator(model);
}

/**
* Creates the {@link PagerAdapter} for the newly inflated {@link ViewPager}.
* If any ListModelChangeProcessor<> is needed, it would be created here. Currently, connecting
* the model.getTabList() to the tabViewBinder would have no effect as only the change of
* ACTIVE_TAB affects the view.
* @param model The model containing the list of tabs to be displayed.
* @return A fully initialized {@link PagerAdapter}.
*/
static PagerAdapter createTabViewAdapter(AccessorySheetModel model) {
return new AccessoryPagerAdapter(model.getTabList());
}

/**
* Adds the contents of a given {@link KeyboardAccessoryData.Tab} to the accessory sheet. If it
* is the first Tab, it automatically becomes the active Tab.
* Careful, if you want to show this tab as icon in the KeyboardAccessory, use the method
* {@link ManualFillingCoordinator#addTab(KeyboardAccessoryData.Tab)} instead.
* @param tab The tab which should be added to the AccessorySheet.
*/
void addTab(KeyboardAccessoryData.Tab tab) {
mMediator.addTab(tab);
}

/**
* Returns a {@link KeyboardAccessoryData.Tab} object that is used to display this bottom sheet.
* @return Returns a {@link KeyboardAccessoryData.Tab}.
*/
@Nullable
public KeyboardAccessoryData.Tab getTab() {
return mMediator.getTab();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package org.chromium.chrome.browser.autofill.keyboard_accessory;

import android.support.annotation.Nullable;

import org.chromium.base.VisibleForTesting;

/**
Expand All @@ -12,14 +14,15 @@
*/
class AccessorySheetMediator {
private final AccessorySheetModel mModel;
private KeyboardAccessoryData.Tab mTab;

AccessorySheetMediator(AccessorySheetModel model) {
mModel = model;
}

@Nullable
KeyboardAccessoryData.Tab getTab() {
return null; // TODO(fhorschig): Return the active tab.
if (mModel.getActiveTabIndex() == AccessorySheetModel.NO_ACTIVE_TAB) return null;
return mModel.getTabList().get(mModel.getActiveTabIndex());
}

@VisibleForTesting
Expand All @@ -34,4 +37,11 @@ public void show() {
public void hide() {
mModel.setVisible(false);
}

public void addTab(KeyboardAccessoryData.Tab tab) {
mModel.getTabList().add(tab);
if (mModel.getActiveTabIndex() == AccessorySheetModel.NO_ACTIVE_TAB) {
mModel.setActiveTabIndex(mModel.getTabList().getItemCount() - 1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,32 @@

package org.chromium.chrome.browser.autofill.keyboard_accessory;

import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;

/**
* This model holds all view state of the accessory sheet.
* It is updated by the {@link AccessorySheetMediator} and emits notification on which observers
* like the view binder react.
*/
class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.PropertyKey> {
public static class PropertyKey { public static final PropertyKey VISIBLE = new PropertyKey(); }
public static class PropertyKey {
public static final PropertyKey ACTIVE_TAB_INDEX = new PropertyKey();
public static final PropertyKey VISIBLE = new PropertyKey();
}

public static final int NO_ACTIVE_TAB = -1;

private int mActiveTabIndex = NO_ACTIVE_TAB;
private boolean mVisible;
private final SimpleListObservable<Tab> mTabList = new SimpleListObservable<>();

SimpleListObservable<Tab> getTabList() {
return mTabList;
}

public void setVisible(boolean visible) {
void setVisible(boolean visible) {
if (mVisible == visible) return; // Nothing to do here: same value.
mVisible = visible;
notifyPropertyChanged(PropertyKey.VISIBLE);
Expand All @@ -24,4 +38,17 @@ public void setVisible(boolean visible) {
boolean isVisible() {
return mVisible;
}

int getActiveTabIndex() {
return mActiveTabIndex;
}

void setActiveTabIndex(int activeTabPosition) {
if (mActiveTabIndex == activeTabPosition) return;
assert((activeTabPosition >= 0 && activeTabPosition < mTabList.getItemCount())
|| activeTabPosition == NO_ACTIVE_TAB)
: "Tried to set invalid index '" + activeTabPosition + "' as active tab!";
mActiveTabIndex = activeTabPosition;
notifyPropertyChanged(PropertyKey.ACTIVE_TAB_INDEX);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package org.chromium.chrome.browser.autofill.keyboard_accessory;

import android.support.v4.view.ViewPager;
import android.view.View;

import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey;
Expand All @@ -14,7 +15,8 @@
* {@link AccessorySheetViewBinder} which will modify the view accordingly.
*/
class AccessorySheetViewBinder
implements LazyViewBinderAdapter.SimpleViewBinder<AccessorySheetModel, View, PropertyKey> {
implements LazyViewBinderAdapter
.SimpleViewBinder<AccessorySheetModel, ViewPager, PropertyKey> {
@Override
public PropertyKey getVisibilityProperty() {
return PropertyKey.VISIBLE;
Expand All @@ -26,14 +28,23 @@ public boolean isVisible(AccessorySheetModel model) {
}

@Override
public void onInitialInflation(AccessorySheetModel model, View inflatedView) {}
public void onInitialInflation(AccessorySheetModel model, ViewPager inflatedView) {
if (model.getActiveTabIndex() != -1) inflatedView.setCurrentItem(model.getActiveTabIndex());
inflatedView.setAdapter(AccessorySheetCoordinator.createTabViewAdapter(model));
}

@Override
public void bind(AccessorySheetModel model, View inflatedView, PropertyKey propertyKey) {
public void bind(AccessorySheetModel model, ViewPager inflatedView, PropertyKey propertyKey) {
if (propertyKey == PropertyKey.VISIBLE) {
inflatedView.setVisibility(model.isVisible() ? View.VISIBLE : View.GONE);
return;
}
if (propertyKey == PropertyKey.ACTIVE_TAB_INDEX) {
if (model.getActiveTabIndex() != AccessorySheetModel.NO_ACTIVE_TAB) {
inflatedView.setCurrentItem(model.getActiveTabIndex());
}
return;
}
assert false : "Every possible property update needs to be handled!";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.RecyclerViewModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid;

Expand Down Expand Up @@ -49,12 +50,11 @@ public KeyboardAccessoryCoordinator(WindowAndroid windowAndroid, ViewStub viewSt
* @param model the {@link KeyboardAccessoryModel} the adapter gets its data from.
* @return Returns a fully initialized and wired adapter to an ActionViewBinder.
*/
static RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>,
ActionViewBinder.ViewHolder>
static RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
createActionsAdapter(KeyboardAccessoryModel model) {
RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>,
ActionViewBinder.ViewHolder> actionsAdapter =
new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder());
RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
actionsAdapter =
new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder());
model.addActionListObserver(new RecyclerViewModelChangeProcessor<>(actionsAdapter));
return actionsAdapter;
}
Expand All @@ -79,7 +79,7 @@ static TabViewBinder createTabViewBinder(
* the start of the accessory. It is meant to trigger various bottom sheets.
* @param tab The tab which contains representation data and links back to a bottom sheet.
*/
public void addTab(KeyboardAccessoryData.Tab tab) {
void addTab(KeyboardAccessoryData.Tab tab) {
mMediator.addTab(tab);
}

Expand All @@ -88,7 +88,7 @@ public void addTab(KeyboardAccessoryData.Tab tab) {
* from the accessory.
* @param tab The tab to be removed.
*/
public void removeTab(KeyboardAccessoryData.Tab tab) {
void removeTab(KeyboardAccessoryData.Tab tab) {
mMediator.removeTab(tab);
}

Expand Down
Loading

0 comments on commit a4ae2e1

Please sign in to comment.