chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/PromoDialogLayout.java

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.browser_ui.widget;

import android.content.Context;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.chromium.components.browser_ui.util.TraceEventVectorDrawableCompat;
import org.chromium.components.browser_ui.widget.DualControlLayout.ButtonType;
import org.chromium.components.browser_ui.widget.PromoDialog.DialogParams;

/**
 * Lays out a promo dialog that is shown when Clank starts up.
 *
 * Because of the versatility of dialog content and screen sizes, this layout exhibits a bunch of
 * specific behaviors (see go/snowflake-dialogs for details):
 *
 * + It hides controls when their resources are not specified by the {@link DialogParams}.
 *   The only two required components are the header text and the primary button label.
 *
 * + When the width is greater than the height, the promo content switches from vertical to
 *   horizontal and moves the illustration from the top of the text to the side of the text.
 *
 * + The buttons are always locked to the bottom of the dialog and stack when there isn't enough
 *   room to display them on one row.
 *
 * + If there is no promo illustration, the header text becomes locked to the top of the dialog and
 *   doesn't scroll away.
 */
public final class PromoDialogLayout extends BoundedLinearLayout {
    /** Content in the dialog that will flip orientation when the screen is wide. */
    private LinearLayout mFlippableContent;

    /** The scrolling container for the scrollable content. */
    private ViewGroup mScrollingContainer;

    /** Content in the dialog that can be scrolled. */
    private LinearLayout mScrollableContent;

    /** Illustration that teases the thing being promoted. */
    private ImageView mIllustrationView;

    /** View containing the header of the promo. */
    private TextView mHeaderView;

    /** View containing the header of the promo. */
    private TextView mFooterView;

    /** View containing text explaining the promo. */
    private TextView mSubheaderView;

    /** Paramters used to build the promo. */
    private DialogParams mParams;

    public PromoDialogLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onFinishInflate() {
        mFlippableContent = (LinearLayout) findViewById(R.id.full_promo_content);
        mScrollingContainer = (ViewGroup) findViewById(R.id.promo_container);
        mScrollableContent = (LinearLayout) findViewById(R.id.scrollable_promo_content);
        mIllustrationView = (ImageView) findViewById(R.id.illustration);
        mHeaderView = (TextView) findViewById(R.id.header);
        mSubheaderView = (TextView) findViewById(R.id.subheader);

        super.onFinishInflate();
    }

    /** Initializes the dialog contents using the given params.  Should only be called once. */
    void initialize(DialogParams params) {
        assert mParams == null && params != null;
        assert params.headerStringResource != 0 || params.headerCharSequence != null;
        assert params.primaryButtonStringResource != 0 || params.primaryButtonCharSequence != null;
        mParams = params;

        if (mParams.drawableInstance != null) {
            mIllustrationView.setImageDrawable(mParams.drawableInstance);
        } else if (mParams.vectorDrawableResource != 0) {
            mIllustrationView.setImageDrawable(
                    TraceEventVectorDrawableCompat.create(
                            getResources(),
                            mParams.vectorDrawableResource,
                            getContext().getTheme()));
        } else if (mParams.drawableResource != 0) {
            mIllustrationView.setImageResource(mParams.drawableResource);
        } else {
            // Dialogs with no illustration make the header stay visible at all times instead of
            // scrolling off on small screens.
            ((ViewGroup) mIllustrationView.getParent()).removeView(mIllustrationView);
        }

        // Create the header.
        if (mParams.headerCharSequence != null) {
            mHeaderView.setText(mParams.headerCharSequence);
        } else {
            mHeaderView.setText(mParams.headerStringResource);
        }

        // Set up the subheader text.
        if (mParams.subheaderCharSequence != null) {
            mSubheaderView.setText(mParams.subheaderCharSequence);
            if (mParams.subheaderIsLink) {
                mSubheaderView.setMovementMethod(LinkMovementMethod.getInstance());
            }
        } else if (mParams.subheaderStringResource == 0) {
            ((ViewGroup) mSubheaderView.getParent()).removeView(mSubheaderView);
        } else {
            mSubheaderView.setText(mParams.subheaderStringResource);
        }

        // Create the footer.
        ViewStub footerStub = (ViewStub) findViewById(R.id.footer_stub);
        if (mParams.footerStringResource == 0) {
            ((ViewGroup) footerStub.getParent()).removeView(footerStub);
        } else {
            mFooterView = (TextView) footerStub.inflate();
            mFooterView.setText(mParams.footerStringResource);
        }

        // Create the buttons.
        DualControlLayout buttonBar = (DualControlLayout) findViewById(R.id.button_bar);
        String primaryString =
                mParams.primaryButtonCharSequence != null
                        ? mParams.primaryButtonCharSequence.toString()
                        : getResources().getString(mParams.primaryButtonStringResource);
        buttonBar.addView(
                DualControlLayout.createButtonForLayout(
                        getContext(), ButtonType.PRIMARY_FILLED, primaryString, null));

        if (mParams.secondaryButtonStringResource != 0) {
            String secondaryString =
                    getResources().getString(mParams.secondaryButtonStringResource);
            buttonBar.addView(
                    DualControlLayout.createButtonForLayout(
                            getContext(), ButtonType.SECONDARY_TEXT, secondaryString, null));
        }
    }

    /**
     * Determines whether the header layout needs to be adjusted to ensure the scrollable content
     * is usable in small form factors.
     *
     * @return Whether the layout needed to be adjusted.
     */
    private boolean fixupHeader() {
        if (mParams.drawableResource != 0
                || mParams.vectorDrawableResource != 0
                || mParams.drawableInstance != null) {
            return false;
        }

        int minScrollHeight =
                getResources().getDimensionPixelSize(R.dimen.promo_dialog_min_scrollable_height);
        boolean shouldHeaderScroll = mScrollingContainer.getMeasuredHeight() < minScrollHeight;
        ViewGroup desiredParent;
        boolean applyHeaderPadding;
        if (shouldHeaderScroll) {
            desiredParent = mScrollableContent;
            applyHeaderPadding = false;
        } else {
            desiredParent = this;
            applyHeaderPadding = true;
        }
        if (mHeaderView.getParent() == desiredParent) return false;
        ((ViewGroup) mHeaderView.getParent()).removeView(mHeaderView);
        desiredParent.addView(mHeaderView, 0);

        int startEndPadding =
                applyHeaderPadding
                        ? getResources().getDimensionPixelSize(R.dimen.promo_dialog_padding)
                        : 0;
        mHeaderView.setPaddingRelative(startEndPadding, 0, startEndPadding, 0);
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
        int availableHeight = MeasureSpec.getSize(heightMeasureSpec);

        if (availableWidth > availableHeight * 1.5) {
            mFlippableContent.setOrientation(LinearLayout.HORIZONTAL);
        } else {
            mFlippableContent.setOrientation(LinearLayout.VERTICAL);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (fixupHeader()) super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /** Adds a View to the layout within the scrollable area. */
    void addControl(View control) {
        mScrollableContent.addView(
                control, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
    }
}