blob: 08ef1231945513798cec0ca41af9cc1ab8dc5cb2 [file] [log] [blame]
// 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_assistant;
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantCarouselCoordinator;
import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantChip;
import org.chromium.chrome.browser.autofill_assistant.details.AssistantDetailsCoordinator;
import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderCoordinator;
import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderModel;
import org.chromium.chrome.browser.autofill_assistant.infobox.AssistantInfoBoxCoordinator;
import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayModel;
import org.chromium.chrome.browser.autofill_assistant.overlay.AssistantOverlayState;
import org.chromium.chrome.browser.autofill_assistant.payment.AssistantPaymentRequestCoordinator;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
import org.chromium.ui.modelutil.ListModel;
/**
* Coordinator responsible for the Autofill Assistant bottom bar.
*/
class AssistantBottomBarCoordinator {
private final AssistantModel mModel;
private final BottomSheetController mBottomSheetController;
private final AssistantBottomSheetContent mContent;
// Child coordinators.
private AssistantInfoBoxCoordinator mInfoBoxCoordinator;
private final AssistantHeaderCoordinator mHeaderCoordinator;
private final AssistantDetailsCoordinator mDetailsCoordinator;
private final AssistantPaymentRequestCoordinator mPaymentRequestCoordinator;
private final AssistantCarouselCoordinator mSuggestionsCoordinator;
private final AssistantCarouselCoordinator mActionsCoordinator;
@Nullable
private ScrollView mOnboardingScrollView;
AssistantBottomBarCoordinator(
Context context, AssistantModel model, BottomSheetController controller) {
mModel = model;
mBottomSheetController = controller;
mContent = new AssistantBottomSheetContent(context);
// Instantiate child components.
mHeaderCoordinator = new AssistantHeaderCoordinator(
context, mContent.mBottomBarView, model.getHeaderModel());
mInfoBoxCoordinator = new AssistantInfoBoxCoordinator(context, model.getInfoBoxModel());
mDetailsCoordinator = new AssistantDetailsCoordinator(context, model.getDetailsModel());
mPaymentRequestCoordinator =
new AssistantPaymentRequestCoordinator(context, model.getPaymentRequestModel());
mSuggestionsCoordinator =
new AssistantCarouselCoordinator(context, model.getSuggestionsModel());
mActionsCoordinator = new AssistantCarouselCoordinator(context, model.getActionsModel());
// Add child views to bottom bar container.
mContent.mBottomBarView.addView(mInfoBoxCoordinator.getView());
mContent.mBottomBarView.addView(mDetailsCoordinator.getView());
mContent.mBottomBarView.addView(mPaymentRequestCoordinator.getView());
mContent.mBottomBarView.addView(mSuggestionsCoordinator.getView());
mContent.mBottomBarView.addView(mActionsCoordinator.getView());
// Set children top margins to have a spacing between them. For the carousels, we set their
// margin only when they are not empty given that they are always shown, even if empty. We
// do not hide them because there is an incompatibility bug between the animateLayoutChanges
// attribute set on mBottomBarView and the animations ran by the carousels
// RecyclerView.
int childSpacing = context.getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_bottombar_vertical_spacing);
setChildMarginTop(mDetailsCoordinator.getView(), childSpacing);
setChildMarginTop(mPaymentRequestCoordinator.getView(), childSpacing);
setCarouselMarginTop(mSuggestionsCoordinator.getView(),
model.getSuggestionsModel().getChipsModel(), childSpacing);
setCarouselMarginTop(mActionsCoordinator.getView(), model.getActionsModel().getChipsModel(),
childSpacing);
// We set the horizontal margins of the details and payment request. We don't set a padding
// to the container as we want the carousels children to be scrolled at the limit of the
// screen.
setHorizontalMargins(mInfoBoxCoordinator.getView());
setHorizontalMargins(mDetailsCoordinator.getView());
setHorizontalMargins(mPaymentRequestCoordinator.getView());
// Set the toolbar background color to white only in the PEEK state, to make sure it does
// not hide parts of the content view (which it overlaps).
controller.getBottomSheet().addObserver(new EmptyBottomSheetObserver() {
@Override
public void onSheetOpened(int reason) {
mContent.mToolbarView.setBackgroundColor(Color.TRANSPARENT);
}
@Override
public void onSheetClosed(int reason) {
mContent.mToolbarView.setBackgroundColor(ApiCompatibilityUtils.getColor(
context.getResources(), R.color.modern_primary_color));
}
});
// Show or hide the bottom sheet content when the Autofill Assistant visibility is changed.
model.addObserver((source, propertyKey) -> {
if (AssistantModel.VISIBLE == propertyKey) {
if (model.get(AssistantModel.VISIBLE)) {
showAndExpand();
} else {
hide();
}
}
});
}
/**
* Cleanup resources when this goes out of scope.
*/
public void destroy() {
mInfoBoxCoordinator.destroy();
mInfoBoxCoordinator = null;
}
/**
* Show the onboarding screen and call {@code callback} with {@code true} if the user agreed to
* proceed, false otherwise.
*/
public void showOnboarding(String experimentIds, Callback<Boolean> callback) {
mModel.getHeaderModel().set(AssistantHeaderModel.VISIBLE, false);
// Show overlay to prevent user from interacting with the page during onboarding.
mModel.getOverlayModel().set(AssistantOverlayModel.STATE, AssistantOverlayState.FULL);
View onboardingView = AssistantOnboardingCoordinator.show(experimentIds,
mContent.mBottomBarView.getContext(), mContent.mBottomBarView, accepted -> {
mOnboardingScrollView = null;
if (!accepted) {
callback.onResult(false);
return;
}
mModel.getHeaderModel().set(AssistantHeaderModel.VISIBLE, true);
// Hide overlay.
mModel.getOverlayModel().set(
AssistantOverlayModel.STATE, AssistantOverlayState.HIDDEN);
callback.onResult(true);
});
mOnboardingScrollView = onboardingView.findViewById(R.id.onboarding_scroll_view);
}
/** Request showing the Assistant bottom bar view and expand the sheet. */
public void showAndExpand() {
if (mBottomSheetController.requestShowContent(mContent, /* animate= */ true)) {
mBottomSheetController.expandSheet();
}
}
/** Hide the Assistant bottom bar view. */
public void hide() {
mBottomSheetController.hideContent(mContent, /* animate= */ true);
}
private void setChildMarginTop(View child, int marginTop) {
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) child.getLayoutParams();
params.topMargin = marginTop;
child.setLayoutParams(params);
}
/**
* Observe {@code model} such that we set the topMargin of {@code carouselView} to {@code
* marginTop} when {@code model} is not empty and set it to 0 otherwise.
*/
private void setCarouselMarginTop(
View carouselView, ListModel<AssistantChip> chipsModel, int marginTop) {
chipsModel.addObserver(new AbstractListObserver<Void>() {
@Override
public void onDataSetChanged() {
setChildMarginTop(carouselView, chipsModel.size() > 0 ? marginTop : 0);
}
});
}
@VisibleForTesting
public AssistantCarouselCoordinator getSuggestionsCoordinator() {
return mSuggestionsCoordinator;
}
@VisibleForTesting
public AssistantCarouselCoordinator getActionsCoordinator() {
return mActionsCoordinator;
}
private void setHorizontalMargins(View view) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams();
int horizontalMargin = view.getContext().getResources().getDimensionPixelSize(
R.dimen.autofill_assistant_bottombar_horizontal_spacing);
layoutParams.setMarginStart(horizontalMargin);
layoutParams.setMarginEnd(horizontalMargin);
view.setLayoutParams(layoutParams);
}
// TODO(crbug.com/806868): Move this class at the top of the file once it is a static class.
private class AssistantBottomSheetContent implements BottomSheet.BottomSheetContent {
private final View mToolbarView;
private final SizeListenableLinearLayout mBottomBarView;
public AssistantBottomSheetContent(Context context) {
mToolbarView = LayoutInflater.from(context).inflate(
R.layout.autofill_assistant_bottom_sheet_toolbar, /* root= */ null);
mBottomBarView = (SizeListenableLinearLayout) LayoutInflater.from(context).inflate(
R.layout.autofill_assistant_bottom_sheet_content, /* root= */ null);
}
@Override
public View getContentView() {
return mBottomBarView;
}
@Nullable
@Override
public View getToolbarView() {
return mToolbarView;
}
@Override
public int getVerticalScrollOffset() {
// TODO(crbug.com/806868): Have a single ScrollView container that contains all child
// views (except carousels) instead.
if (mOnboardingScrollView != null && mOnboardingScrollView.isShown()) {
return mOnboardingScrollView.getScrollY();
}
if (mPaymentRequestCoordinator.getView().isShown()) {
return mPaymentRequestCoordinator.getView().getScrollY();
}
return 0;
}
@Override
public void destroy() {}
@Override
public int getPriority() {
return BottomSheet.ContentPriority.HIGH;
}
@Override
public boolean swipeToDismissEnabled() {
return false;
}
@Override
public boolean isPeekStateEnabled() {
return true;
}
@Override
public boolean wrapContentEnabled() {
return true;
}
@Override
public boolean hasCustomLifecycle() {
return true;
}
@Override
public boolean hasCustomScrimLifecycle() {
return true;
}
@Override
public boolean setContentSizeListener(@Nullable BottomSheet.ContentSizeListener listener) {
mBottomBarView.setContentSizeListener(listener);
return true;
}
@Override
public boolean hideOnScroll() {
return false;
}
@Override
public int getSheetContentDescriptionStringId() {
return R.string.autofill_assistant_sheet_content_description;
}
@Override
public int getSheetHalfHeightAccessibilityStringId() {
return R.string.autofill_assistant_sheet_half_height;
}
@Override
public int getSheetFullHeightAccessibilityStringId() {
return R.string.autofill_assistant_sheet_full_height;
}
@Override
public int getSheetClosedAccessibilityStringId() {
return R.string.autofill_assistant_sheet_closed;
}
}
}