Commit 9087e817 authored by Yue Zhang's avatar Yue Zhang Committed by Commit Bot

Add selection mode ungrouping for TabGridDialog

* Make TabGridDialog component own a TabSelectionEditor to support
  ungrouping through selection mode for both dialog in tab switcher and
  dialog from strip.
* Add a TabSelectionEditorLayoutPositionProvider to specify the
  position to show selection editor so that it always shows on
  TabGridDialog.
* Add strings used in tab grid dialog selection mode ungrouping.

Bug: 1000381
Change-Id: I78fb56fcfe3ae058f8a6b921a72326e9358f7175
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1829239
Commit-Queue: Yue Zhang <yuezhanggg@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#702430}
parent 64621410
......@@ -120,6 +120,7 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabListRecyclerView.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabProperties.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionProvider.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorLayout.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorLayoutBinder.java",
......
......@@ -38,5 +38,12 @@
android:src="@drawable/plus"
app:tint="@color/standard_mode_tint"
android:contentDescription="@string/bottom_tab_grid_new_tab" />
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/toolbar_menu_button"
style="@style/ToolbarMenuButtonPhone"
android:src="@drawable/ic_more_vert_24dp"
android:layout_gravity="center"
app:tint="@color/standard_mode_tint"
android:contentDescription="@string/accessibility_toolbar_btn_menu" />
</LinearLayout>
</org.chromium.chrome.browser.tasks.tab_management.TabGroupUiToolbarView>
......@@ -27,11 +27,11 @@ import java.util.List;
*/
public class TabGridDialogCoordinator implements TabGridDialogMediator.DialogController {
private final String mComponentName;
private final Context mContext;
private final TabListCoordinator mTabListCoordinator;
private final TabGridDialogMediator mMediator;
private final PropertyModel mToolbarPropertyModel;
private final TabGridPanelToolbarCoordinator mToolbarCoordinator;
private final TabSelectionEditorCoordinator mTabSelectionEditorCoordinator;
private TabGridDialogParent mParentLayout;
TabGridDialogCoordinator(Context context, TabModelSelector tabModelSelector,
......@@ -39,27 +39,28 @@ public class TabGridDialogCoordinator implements TabGridDialogMediator.DialogCon
ViewGroup containerView, TabSwitcherMediator.ResetHandler resetHandler,
TabListMediator.GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
TabGridDialogMediator.AnimationParamsProvider animationParamsProvider) {
mContext = context;
mComponentName = animationParamsProvider == null ? "TabGridDialogFromStrip"
: "TabGridDialogInSwitcher";
mToolbarPropertyModel = new PropertyModel(TabGridPanelProperties.ALL_KEYS);
mMediator =
new TabGridDialogMediator(context, this, mToolbarPropertyModel, tabModelSelector,
tabCreatorManager, resetHandler, animationParamsProvider, mComponentName);
mParentLayout = new TabGridDialogParent(context, containerView);
mTabSelectionEditorCoordinator = new TabSelectionEditorCoordinator(
context, containerView, tabModelSelector, tabContentManager, mParentLayout);
mMediator = new TabGridDialogMediator(context, this, mToolbarPropertyModel,
tabModelSelector, tabCreatorManager, resetHandler, animationParamsProvider,
mTabSelectionEditorCoordinator.getController(), mComponentName);
mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
gridCardOnClickListenerProvider, mMediator.getTabGridDialogHandler(),
TabProperties.UiType.CLOSABLE, null, containerView, null, false, mComponentName);
mParentLayout = new TabGridDialogParent(context, containerView);
TabListRecyclerView recyclerView = mTabListCoordinator.getContainerView();
mToolbarCoordinator = new TabGridPanelToolbarCoordinator(
mContext, recyclerView, mToolbarPropertyModel, mParentLayout);
context, recyclerView, mToolbarPropertyModel, mParentLayout);
}
/**
......@@ -70,6 +71,7 @@ public class TabGridDialogCoordinator implements TabGridDialogMediator.DialogCon
mMediator.destroy();
mToolbarCoordinator.destroy();
mParentLayout.destroy();
mTabSelectionEditorCoordinator.destroy();
if (mToolbarCoordinator != null) {
mToolbarCoordinator.destroy();
}
......
......@@ -13,6 +13,7 @@ import android.view.View;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.tab.Tab;
......@@ -26,6 +27,7 @@ import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabSelectionType;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.chrome.tab_ui.R;
......@@ -88,13 +90,17 @@ public class TabGridDialogMediator {
private final TabSwitcherMediator.ResetHandler mTabSwitcherResetHandler;
private final AnimationParamsProvider mAnimationParamsProvider;
private final DialogHandler mTabGridDialogHandler;
private final TabSelectionEditorCoordinator
.TabSelectionEditorController mTabSelectionEditorController;
private final String mComponentName;
private int mCurrentTabId = Tab.INVALID_TAB_ID;
TabGridDialogMediator(Context context, DialogController dialogController, PropertyModel model,
TabModelSelector tabModelSelector, TabCreatorManager tabCreatorManager,
TabSwitcherMediator.ResetHandler tabSwitcherResetHandler,
AnimationParamsProvider animationParamsProvider, String componentName) {
AnimationParamsProvider animationParamsProvider,
TabSelectionEditorCoordinator.TabSelectionEditorController tabSelectionEditorController,
String componentName) {
mContext = context;
mModel = model;
mTabModelSelector = tabModelSelector;
......@@ -103,6 +109,7 @@ public class TabGridDialogMediator {
mTabSwitcherResetHandler = tabSwitcherResetHandler;
mAnimationParamsProvider = animationParamsProvider;
mTabGridDialogHandler = new DialogHandler();
mTabSelectionEditorController = tabSelectionEditorController;
mComponentName = componentName;
// Register for tab model.
......@@ -178,10 +185,15 @@ public class TabGridDialogMediator {
}
};
mTabModelSelector.addObserver(mTabModelSelectorObserver);
assert mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter()
instanceof TabGroupModelFilter;
// Setup toolbar property model.
setupToolbarClickHandlers();
// Setup dialog selection editor.
setupDialogSelectionEditor();
// Setup ScrimView observer.
setupScrimViewObserver();
}
......@@ -195,6 +207,7 @@ public class TabGridDialogMediator {
mAnimationParamsProvider.getAnimationParamsForTab(mCurrentTabId));
}
}
mTabSelectionEditorController.hide();
mDialogController.resetWithListOfTabs(null);
}
......@@ -272,6 +285,17 @@ public class TabGridDialogMediator {
mModel.set(
TabGridPanelProperties.COLLAPSE_CLICK_LISTENER, getCollapseButtonClickListener());
mModel.set(TabGridPanelProperties.ADD_CLICK_LISTENER, getAddButtonClickListener());
mModel.set(TabGridPanelProperties.MENU_CLICK_LISTENER, getMenuButtonClickListener());
}
private void setupDialogSelectionEditor() {
TabSelectionEditorActionProvider actionProvider = new TabSelectionEditorActionProvider(
mTabModelSelector, mTabSelectionEditorController,
TabSelectionEditorActionProvider.TabSelectionEditorAction.UNGROUP);
String actionButtonText =
mContext.getString(R.string.tab_grid_dialog_selection_mode_remove);
mTabSelectionEditorController.configureToolbar(actionButtonText, actionProvider, 1, null);
}
private void setupScrimViewObserver() {
......@@ -318,7 +342,13 @@ public class TabGridDialogMediator {
}
private View.OnClickListener getMenuButtonClickListener() {
return TabGridDialogMenuCoordinator.getTabGridDialogMenuOnClickListener(null);
Callback<Integer> callback = result -> {
if (result == R.id.ungroup_tab) {
List<Tab> tabs = getRelatedTabs(mCurrentTabId);
mTabSelectionEditorController.show(tabs);
}
};
return TabGridDialogMenuCoordinator.getTabGridDialogMenuOnClickListener(callback);
}
private List<Tab> getRelatedTabs(int tabId) {
......
......@@ -142,7 +142,8 @@ public class TabGridDialogMenuCoordinator {
ModelList itemList = new ModelList();
itemList.add(new ListItem(ListItemType.MENU_ITEM,
buildPropertyModel(context,
org.chromium.chrome.tab_ui.R.string.tab_grid_dialog_remove_from_group,
org.chromium.chrome.tab_ui.R.string
.tab_grid_dialog_toolbar_remove_from_group,
R.id.ungroup_tab)));
return itemList;
}
......
......@@ -46,7 +46,8 @@ import java.lang.annotation.RetentionPolicy;
/**
* Parent for TabGridDialog component.
*/
public class TabGridDialogParent {
public class TabGridDialogParent
implements TabSelectionEditorLayout.TabSelectionEditorLayoutPositionProvider {
private static final int DIALOG_ANIMATION_DURATION = 300;
private static final int DIALOG_ALPHA_ANIMATION_DURATION = 150;
private static final int CARD_FADE_ANIMATION_DURATION = 50;
......@@ -651,6 +652,22 @@ public class TabGridDialogParent {
mHideDialogAnimation.start();
}
/**
* {@link TabSelectionEditorLayout.TabSelectionEditorLayoutPositionProvider} implementation.
* Returns a {@link Rect} that indicates the current position of dialog.
*/
@Override
public Rect getSelectionEditorPositionRect() {
Rect positionRect = new Rect();
mDialogContainerView.getGlobalVisibleRect(positionRect);
Rect parentRect = new Rect();
mParent.getGlobalVisibleRect(parentRect);
// Offset by the status bar height.
positionRect.offset(0, parentRect.top);
return positionRect;
}
/**
* Destroy any members that needs clean up.
*/
......
......@@ -41,6 +41,7 @@ public class TabGroupUiToolbarView extends FrameLayout {
mLeftButton = findViewById(R.id.toolbar_left_button);
mRightButton = findViewById(R.id.toolbar_right_button);
mMenuButton = findViewById(R.id.toolbar_menu_button);
mContainerView = (ViewGroup) findViewById(R.id.toolbar_container_view);
mTitleTextView = (TextView) findViewById(R.id.title);
mMainContent = findViewById(R.id.main_content);
......
// Copyright 2019 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.tasks.tab_management;
import androidx.annotation.IntDef;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
* Provider of actions for a list of selected tabs in {@link TabSelectionEditorMediator}.
*/
class TabSelectionEditorActionProvider {
@IntDef({TabSelectionEditorAction.UNGROUP, TabSelectionEditorAction.GROUP})
@Retention(RetentionPolicy.SOURCE)
@interface TabSelectionEditorAction {
int GROUP = 0;
int UNGROUP = 1;
int NUM_ENTRIES = 2;
}
private final TabModelSelector mTabModelSelector;
private final TabSelectionEditorCoordinator
.TabSelectionEditorController mTabSelectionEditorController;
private final int mAction;
TabSelectionEditorActionProvider(TabModelSelector tabModelSelector,
TabSelectionEditorCoordinator.TabSelectionEditorController tabSelectionEditorController,
@TabSelectionEditorAction int action) {
mTabModelSelector = tabModelSelector;
mTabSelectionEditorController = tabSelectionEditorController;
mAction = action;
}
/**
* Defines how to process {@code selectedTabs} based on the {@link TabSelectionEditorAction}
* specified in the constructor.
*
* @param selectedTabs The list of selected tabs to process.
*/
void processSelectedTabs(List<Tab> selectedTabs) {
assert mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter()
instanceof TabGroupModelFilter;
switch (mAction) {
case TabSelectionEditorAction.GROUP:
Tab destinationTab = getDestinationTab(selectedTabs);
TabGroupModelFilter tabGroupModelFilter =
(TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter();
tabGroupModelFilter.mergeListOfTabsToGroup(
selectedTabs, destinationTab, false, true);
mTabSelectionEditorController.hide();
RecordUserAction.record("TabMultiSelect.Done");
RecordUserAction.record("TabGroup.Created.TabMultiSelect");
break;
case TabSelectionEditorAction.UNGROUP:
TabGroupModelFilter filter =
(TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter();
for (Tab tab : selectedTabs) {
filter.moveTabOutOfGroup(tab.getId());
}
mTabSelectionEditorController.hide();
break;
default:
assert false;
}
}
/**
* @return The {@link Tab} that has the greatest index in TabModel among the given list of tabs.
*/
private Tab getDestinationTab(List<Tab> tabs) {
int greatestIndex = TabModel.INVALID_TAB_INDEX;
for (int i = 0; i < tabs.size(); i++) {
int index = TabModelUtils.getTabIndexById(
mTabModelSelector.getCurrentModel(), tabs.get(i).getId());
greatestIndex = Math.max(index, greatestIndex);
}
return mTabModelSelector.getCurrentModel().getTabAt(greatestIndex);
}
}
......@@ -37,6 +37,11 @@ class TabSelectionEditorCoordinator {
*/
void show(List<Tab> tabs);
/**
* Hides the TabSelectionEditor.
*/
void hide();
/**
* @return Whether or not the TabSelectionEditor consumed the event.
*/
......@@ -45,13 +50,14 @@ class TabSelectionEditorCoordinator {
/**
* Configure the Toolbar for TabSelectionEditor. The default button text is "Group".
* @param actionButtonText Button text for the action button.
* @param actionButtonOnClickListener Click listener for the action button.
* @param actionProvider The {@link TabSelectionEditorActionProvider} that specifies the
* action when action button gets clicked.
* @param actionButtonEnablingThreshold The minimum threshold to enable the action button.
* If it's -1 use the default value.
* @param navigationButtonOnClickListener Click listener for the navigation button.
*/
void configureToolbar(@Nullable String actionButtonText,
@Nullable View.OnClickListener actionButtonOnClickListener,
@Nullable TabSelectionEditorActionProvider actionProvider,
int actionButtonEnablingThreshold,
@Nullable View.OnClickListener navigationButtonOnClickListener);
}
......@@ -67,7 +73,8 @@ class TabSelectionEditorCoordinator {
private final TabSelectionEditorMediator mTabSelectionEditorMediator;
public TabSelectionEditorCoordinator(Context context, View parentView,
TabModelSelector tabModelSelector, TabContentManager tabContentManager) {
TabModelSelector tabModelSelector, TabContentManager tabContentManager,
TabSelectionEditorLayout.TabSelectionEditorLayoutPositionProvider positionProvider) {
mContext = context;
mParentView = parentView;
mTabModelSelector = tabModelSelector;
......@@ -80,7 +87,8 @@ class TabSelectionEditorCoordinator {
.inflate(R.layout.tab_selection_editor_layout, null)
.findViewById(R.id.selectable_list);
mTabSelectionEditorLayout.initialize(mParentView, mTabListCoordinator.getContainerView(),
mTabListCoordinator.getContainerView().getAdapter(), mSelectionDelegate);
mTabListCoordinator.getContainerView().getAdapter(), mSelectionDelegate,
positionProvider);
mSelectionDelegate.setSelectionModeEnabledForZeroItems(true);
mTabSelectionEditorLayoutChangeProcessor = PropertyModelChangeProcessor.create(
......@@ -117,6 +125,7 @@ class TabSelectionEditorCoordinator {
*/
public void destroy() {
mTabListCoordinator.destroy();
mTabSelectionEditorLayout.destroy();
mTabSelectionEditorMediator.destroy();
mTabSelectionEditorLayoutChangeProcessor.destroy();
}
......
......@@ -5,13 +5,17 @@
package org.chromium.chrome.browser.tasks.tab_management;
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.PopupWindow;
import androidx.annotation.Nullable;
import org.chromium.chrome.browser.widget.selection.SelectableListLayout;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.chrome.tab_ui.R;
......@@ -20,11 +24,25 @@ import org.chromium.chrome.tab_ui.R;
* This class is used to show the {@link SelectableListLayout} in a {@link PopupWindow}.
*/
class TabSelectionEditorLayout extends SelectableListLayout<Integer> {
private TabSelectionEditorToolbar mToolbar;
private final PopupWindow mWindow;
private TabSelectionEditorToolbar mToolbar;
private View mParentView;
private TabSelectionEditorLayoutPositionProvider mPositionProvider;
private ViewTreeObserver.OnGlobalLayoutListener mParentLayoutListener;
private boolean mIsInitialized;
/**
* An interface to provide the {@link Rect} used to position the selection editor layout on
* screen.
*/
public interface TabSelectionEditorLayoutPositionProvider {
/**
* This method fetches the {@link Rect} used to position the selection editor layout.
* @return The {@link Rect} indicates where to show the selection editor layout.
*/
Rect getSelectionEditorPositionRect();
}
// TODO(meiliang): inflates R.layout.tab_selection_editor_layout in
// TabSelectionEditorCoordinator.
public TabSelectionEditorLayout(Context context, AttributeSet attrs) {
......@@ -34,23 +52,36 @@ class TabSelectionEditorLayout extends SelectableListLayout<Integer> {
}
/**
* Initializes the RecyclerView and the toolbar for the layout. This must be called before
* calling show/hide.
* Initializes the RecyclerView and the toolbar for the layout. Also initializes the selection
* editor layout provider if there is one.This must be called before calling show/hide.
*
* @param parentView The parent view for the {@link PopupWindow}.
* @param recyclerView The recycler view to be shown.
* @param adapter The adapter that provides views that represent items in the recycler view.
* @param selectionDelegate The {@link SelectionDelegate} that will inform the toolbar of
* selection changes.
* @param positionProvider The {@link TabSelectionEditorLayoutPositionProvider} that provides
* the position rect to show the selection editor layout.
*/
void initialize(View parentView, RecyclerView recyclerView, RecyclerView.Adapter adapter,
SelectionDelegate<Integer> selectionDelegate) {
SelectionDelegate<Integer> selectionDelegate,
@Nullable TabSelectionEditorLayoutPositionProvider positionProvider) {
mIsInitialized = true;
initializeRecyclerView(adapter, recyclerView);
mToolbar =
(TabSelectionEditorToolbar) initializeToolbar(R.layout.tab_selection_editor_toolbar,
selectionDelegate, 0, 0, 0, null, false, true);
mParentView = parentView;
mPositionProvider = positionProvider;
// Re-fetch the position rect and re-show the selection editor in case there is a global
// layout change on parent view, e.g. re-layout caused by orientation change.
mParentLayoutListener = () -> {
if (mWindow.isShowing() && mPositionProvider != null) {
hide();
show();
}
};
mParentView.getViewTreeObserver().addOnGlobalLayoutListener(mParentLayoutListener);
}
/**
......@@ -58,7 +89,14 @@ class TabSelectionEditorLayout extends SelectableListLayout<Integer> {
*/
public void show() {
assert mIsInitialized;
mWindow.showAtLocation(mParentView, Gravity.CENTER, 0, 0);
if (mPositionProvider == null) {
mWindow.showAtLocation(mParentView, Gravity.CENTER, 0, 0);
return;
}
Rect rect = mPositionProvider.getSelectionEditorPositionRect();
mWindow.setWidth(rect.width());
mWindow.setHeight(rect.height());
mWindow.showAtLocation(mParentView, Gravity.NO_GRAVITY, rect.left, rect.top);
}
/**
......@@ -75,4 +113,13 @@ class TabSelectionEditorLayout extends SelectableListLayout<Integer> {
public TabSelectionEditorToolbar getToolbar() {
return mToolbar;
}
/**
* Destroy any members that needs clean up.
*/
public void destroy() {
if (mParentView != null && mParentLayoutListener != null) {
mParentView.getViewTreeObserver().removeOnGlobalLayoutListener(mParentLayoutListener);
}
}
}
......@@ -22,7 +22,6 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modelutil.PropertyModel;
......@@ -55,6 +54,7 @@ class TabSelectionEditorMediator
private final SelectionDelegate<Integer> mSelectionDelegate;
private final TabModelSelectorTabModelObserver mTabModelObserver;
private final TabModelSelectorObserver mTabModelSelectorObserver;
private TabSelectionEditorActionProvider mActionProvider;
private final View.OnClickListener mNavigationClickListener = new View.OnClickListener() {
@Override
......@@ -64,7 +64,7 @@ class TabSelectionEditorMediator
}
};
private final View.OnClickListener mGroupButtonOnClickListener = new View.OnClickListener() {
private final View.OnClickListener mActionButtonOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
List<Tab> selectedTabs = new ArrayList<>();
......@@ -74,18 +74,8 @@ class TabSelectionEditorMediator
TabModelUtils.getTabById(mTabModelSelector.getCurrentModel(), tabId));
}
Tab destinationTab = getDestinationTab(selectedTabs);
TabGroupModelFilter tabGroupModelFilter =
(TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
.getCurrentTabModelFilter();
tabGroupModelFilter.mergeListOfTabsToGroup(selectedTabs, destinationTab, false, true);
hide();
RecordUserAction.record("TabMultiSelect.Done");
RecordUserAction.record("TabGroup.Created.TabMultiSelect");
if (mActionProvider == null) return;
mActionProvider.processSelectedTabs(selectedTabs);
}
};
......@@ -101,7 +91,7 @@ class TabSelectionEditorMediator
mModel.set(
TabSelectionEditorProperties.TOOLBAR_NAVIGATION_LISTENER, mNavigationClickListener);
mModel.set(TabSelectionEditorProperties.TOOLBAR_ACTION_BUTTON_LISTENER,
mGroupButtonOnClickListener);
mActionButtonOnClickListener);
mTabModelObserver = new TabModelSelectorTabModelObserver(mTabModelSelector) {
@Override
......@@ -150,30 +140,19 @@ class TabSelectionEditorMediator
}
};
mTabModelSelector.addObserver(mTabModelSelectorObserver);
}
/**
* @return The {@link Tab} that has the greatest index in TabModel among the given list of tabs.
*/
private Tab getDestinationTab(List<Tab> tabs) {
int greatestIndex = TabModel.INVALID_TAB_INDEX;
for (int i = 0; i < tabs.size(); i++) {
int index = TabModelUtils.getTabIndexById(
mTabModelSelector.getCurrentModel(), tabs.get(i).getId());
greatestIndex = Math.max(index, greatestIndex);
}
return mTabModelSelector.getCurrentModel().getTabAt(greatestIndex);
}
private void hide() {
mResetHandler.resetWithListOfTabs(null);
mModel.set(TabSelectionEditorProperties.IS_VISIBLE, false);
// Default action for action button is to group selected tabs.
mActionProvider = new TabSelectionEditorActionProvider(mTabModelSelector, this,
TabSelectionEditorActionProvider.TabSelectionEditorAction.GROUP);
}
private boolean isEditorVisible() {
return mModel.get(TabSelectionEditorProperties.IS_VISIBLE);
}
/**
* {@link TabSelectionEditorCoordinator.TabSelectionEditorController} implementation.
*/
@Override
public void show(List<Tab> tabs) {
mResetHandler.resetWithListOfTabs(tabs);
......@@ -183,15 +162,14 @@ class TabSelectionEditorMediator
@Override
public void configureToolbar(@Nullable String actionButtonText,
@Nullable View.OnClickListener actionButtonOnClickListener,
@Nullable TabSelectionEditorActionProvider actionProvider,
int actionButtonEnablingThreshold,
@Nullable View.OnClickListener navigationButtonOnClickListener) {
if (actionButtonText != null) {
mModel.set(TabSelectionEditorProperties.TOOLBAR_ACTION_BUTTON_TEXT, actionButtonText);
}
if (actionButtonOnClickListener != null) {
mModel.set(TabSelectionEditorProperties.TOOLBAR_ACTION_BUTTON_LISTENER,
actionButtonOnClickListener);
if (actionProvider != null) {
mActionProvider = actionProvider;
}
if (actionButtonEnablingThreshold != -1) {
mModel.set(TabSelectionEditorProperties.TOOLBAR_ACTION_BUTTON_ENABLING_THRESHOLD,
......@@ -211,6 +189,12 @@ class TabSelectionEditorMediator
return true;
}
@Override
public void hide() {
mResetHandler.resetWithListOfTabs(null);
mModel.set(TabSelectionEditorProperties.IS_VISIBLE, false);
}
/**
* Destroy any members that needs clean up.
*/
......
......@@ -87,7 +87,7 @@ public class TabSwitcherCoordinator implements Destroyable, TabSwitcher,
PropertyModel containerViewModel = new PropertyModel(TabListContainerProperties.ALL_KEYS);
mTabSelectionEditorCoordinator = new TabSelectionEditorCoordinator(
context, container, tabModelSelector, tabContentManager);
context, container, tabModelSelector, tabContentManager, null);
mMediator = new TabSwitcherMediator(this, containerViewModel, tabModelSelector,
fullscreenManager, container, mTabSelectionEditorCoordinator.getController(), mode);
......
......@@ -180,6 +180,12 @@
<message name="IDS_TAB_GRID_DIALOG_REMOVE_FROM_GROUP" desc="This text shows on the ungroup bar in TabGridDialog. When user drags a tab and drops it on the ungroup bar, this tab will be moved out of the group.">
Remove from group
</message>
<message name="IDS_TAB_GRID_DIALOG_TOOLBAR_REMOVE_FROM_GROUP" desc="This text shows in the TabGridDialog toolbar menu as one menu item. When user selects this item, user will enter a selection mode where they can select tabs that they want to move out of tab group. Two things worth mentioning here: 1. The remove here does not mean delete, it means move tab out of tab group and becomes a single tab. 2. If the translation goes beyond 30 characters, replace it with &quot;Remove tabs&quot; to keep this text under 30 characters.">
Remove tabs from group
</message>
<message name="IDS_TAB_GRID_DIALOG_SELECTION_MODE_REMOVE" desc="This texts shows on the action button of tab group selection mode. When user clicks this action button, all the tabs that are currently selected will be moved out of tab group. One thing worth mentioning here is that the remove here does not mean delete, it means move tab out of tab group and becomes a single tab.">
Remove
</message>
<!-- Tab Grid Drag-and-drop IPH strings -->
<message name="IDS_IPH_DRAG_AND_DROP_INTRODUCTION" desc="This text shows on the entry point for the in-product help for drag-and-drop. It introduces that this IPH is about how to use drag-and-drop.">
......
......@@ -47,6 +47,8 @@ public class TabSelectionEditorLayoutBinderTest extends DummyUiActivityTestCase
super.setUpTest();
ViewGroup view = new LinearLayout(getActivity());
TabSelectionEditorLayout.TabSelectionEditorLayoutPositionProvider positionProvider =
() -> null;
TestThreadUtils.runOnUiThreadBlocking(() -> {
getActivity().setContentView(view);
......@@ -68,7 +70,7 @@ public class TabSelectionEditorLayoutBinderTest extends DummyUiActivityTestCase
public int getItemCount() {
return 0;
}
}, mSelectionDelegate);
}, mSelectionDelegate, positionProvider);
});
mMCP = PropertyModelChangeProcessor.create(
mModel, mEditorLayoutView, TabSelectionEditorLayoutBinder::bind);
......
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.tasks.tab_management;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
......@@ -70,6 +71,7 @@ public class TabGridDialogMediatorUnitTest {
private static final String TAB3_TITLE = "Tab3";
private static final String DIALOG_TITLE1 = "1 Tab";
private static final String DIALOG_TITLE2 = "2 Tabs";
private static final String REMOVE_BUTTON_STRING = "Remove";
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final int TAB3_ID = 123;
......@@ -104,6 +106,8 @@ public class TabGridDialogMediatorUnitTest {
TabGroupModelFilter mTabGroupModelFilter;
@Mock
TabModel mTabModel;
@Mock
TabSelectionEditorCoordinator.TabSelectionEditorController mTabSelectionEditorController;
@Captor
ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
......@@ -160,11 +164,14 @@ public class TabGridDialogMediatorUnitTest {
.when(mAnimationParamsProvider)
.getAnimationParamsForTab(anyInt());
doReturn(mTabCreator).when(mTabCreatorManager).getTabCreator(anyBoolean());
doReturn(REMOVE_BUTTON_STRING)
.when(mContext)
.getString(R.string.tab_grid_dialog_selection_mode_remove);
mModel = new PropertyModel(TabGridPanelProperties.ALL_KEYS);
mMediator =
new TabGridDialogMediator(mContext, mDialogController, mModel, mTabModelSelector,
mTabCreatorManager, mTabSwitcherResetHandler, mAnimationParamsProvider, "");
mMediator = new TabGridDialogMediator(mContext, mDialogController, mModel,
mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler,
mAnimationParamsProvider, mTabSelectionEditorController, "");
}
@After
......@@ -184,6 +191,14 @@ public class TabGridDialogMediatorUnitTest {
instanceOf(View.OnClickListener.class));
}
@Test
public void setupDialogSelectionEditor() {
// The dialog selection editor for ungrouping should be setup.
verify(mTabSelectionEditorController)
.configureToolbar(eq(REMOVE_BUTTON_STRING),
any(TabSelectionEditorActionProvider.class), eq(1), eq(null));
}
@Test
public void onClickAdd_HasCurrentTab() {
// Mock that the animation source Rect is not null.
......@@ -417,7 +432,8 @@ public class TabGridDialogMediatorUnitTest {
// For strip we don't play zoom-in/zoom-out for show/hide dialog, and thus
// the animationParamsProvider is null.
mMediator = new TabGridDialogMediator(mContext, mDialogController, mModel,
mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler, null, "");
mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler, null,
mTabSelectionEditorController, "");
// Mock that the dialog is hidden and animation source Rect and header title are all null.
mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
mModel.set(TabGridPanelProperties.ANIMATION_PARAMS, null);
......
// Copyright 2019 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.tasks.tab_management;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.view.View;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.UserDataHost;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Tests for {@link TabSelectionEditorActionProvider}.
*/
@RunWith(LocalRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TabSelectionEditorActionProviderUnitTest {
@Rule
public TestRule mProcessor = new Features.JUnitProcessor();
private static final String TAB1_TITLE = "Tab1";
private static final String TAB2_TITLE = "Tab2";
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final int POSITION1 = 0;
private static final int POSITION2 = 1;
@Mock
TabModelSelectorImpl mTabModelSelector;
@Mock
TabSelectionEditorCoordinator.TabSelectionEditorController mTabSelectionEditorController;
@Mock
TabModel mTabModel;
@Mock
TabModelFilterProvider mTabModelFilterProvider;
@Mock
TabGroupModelFilter mTabGroupModelFilter;
private Tab mTab1;
private Tab mTab2;
@Before
public void setUp() {
RecordUserAction.setDisabledForTests(true);
RecordHistogram.setDisabledForTests(true);
MockitoAnnotations.initMocks(this);
mTab1 = prepareTab(TAB1_ID, TAB1_TITLE);
mTab2 = prepareTab(TAB2_ID, TAB2_TITLE);
doReturn(mTabModel).when(mTabModelSelector).getCurrentModel();
doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
doReturn(POSITION1).when(mTabModel).indexOf(mTab1);
doReturn(POSITION2).when(mTabModel).indexOf(mTab2);
doReturn(0).when(mTabModel).index();
doReturn(2).when(mTabModel).getCount();
doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
}
@After
public void tearDown() {
RecordUserAction.setDisabledForTests(false);
RecordHistogram.setDisabledForTests(false);
}
@Test
public void testGroupAction() {
TabSelectionEditorActionProvider tabSelectionEditorActionProvider =
new TabSelectionEditorActionProvider(mTabModelSelector,
mTabSelectionEditorController,
TabSelectionEditorActionProvider.TabSelectionEditorAction.GROUP);
List<Tab> selectedTabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
tabSelectionEditorActionProvider.processSelectedTabs(selectedTabs);
verify(mTabGroupModelFilter)
.mergeListOfTabsToGroup(eq(selectedTabs), eq(mTab2), eq(false), eq(true));
verify(mTabSelectionEditorController).hide();
}
@Test
public void testUngroupAction() {
TabSelectionEditorActionProvider tabSelectionEditorActionProvider =
new TabSelectionEditorActionProvider(mTabModelSelector,
mTabSelectionEditorController,
TabSelectionEditorActionProvider.TabSelectionEditorAction.UNGROUP);
List<Tab> selectedTabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
tabSelectionEditorActionProvider.processSelectedTabs(selectedTabs);
verify(mTabGroupModelFilter).moveTabOutOfGroup(TAB1_ID);
verify(mTabGroupModelFilter).moveTabOutOfGroup(TAB2_ID);
verify(mTabSelectionEditorController).hide();
}
private Tab prepareTab(int id, String title) {
Tab tab = mock(Tab.class);
when(tab.getView()).thenReturn(mock(View.class));
when(tab.getUserDataHost()).thenReturn(new UserDataHost());
doReturn(id).when(tab).getId();
doReturn("").when(tab).getUrl();
doReturn(title).when(tab).getTitle();
doReturn(false).when(tab).isClosing();
return tab;
}
}
......@@ -34,6 +34,7 @@ tab_management_test_java_sources = [
tab_management_junit_java_sources = [
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/MostVisitedListViewBinderUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorActionProviderUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMediatorUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java",
"//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallbackUnitTest.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