chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/chips/ChipsCoordinator.java

// Copyright 2018 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.chips;

import android.content.Context;
import android.graphics.Rect;
import android.view.View;

import androidx.annotation.DrawableRes;
import androidx.annotation.Px;
import androidx.annotation.StyleRes;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
import androidx.recyclerview.widget.RecyclerView.State;

import org.chromium.base.Callback;
import org.chromium.components.browser_ui.widget.R;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;

/**
 * The coordinator responsible for managing a list of chips. To get the {@link View} that represents
 * this coordinator use {@link #getView()}.
 */
public class ChipsCoordinator {
    private final ModelList mModelList;
    private final RecyclerView mView;

    /**
     * Builds and initializes this coordinator, including all sub-components.
     *
     * @param context The {@link Context} to use to grab all of the resources.
     * @param modelList The list of chip models to be displayed.
     */
    public ChipsCoordinator(Context context, ModelList modelList) {
        this(context, modelList, R.style.SuggestionChipThemeOverlay);
    }

    public ChipsCoordinator(Context context, ModelList modelList, @StyleRes int themeOverlay) {
        assert context != null;
        assert modelList != null;

        mModelList = modelList;

        // Build the underlying components.
        mView = new RecyclerView(context);
        mView.setLayoutManager(
                new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
        mView.getItemAnimator().setChangeDuration(0);

        SimpleRecyclerViewAdapter adapter = new SimpleRecyclerViewAdapter(mModelList);
        adapter.registerType(
                ChipProperties.BASIC_CHIP,
                (parent) -> new ChipView(context, themeOverlay),
                ChipViewBinder::bind);
        mView.setAdapter(adapter);
    }

    /**
     * A utility method for more easily creating basic chip model. This model can be altered after
     * construction.
     *
     * @param id The ID of the chip.
     * @param text The text to display in the chip. The {@link ChipProperties#CONTENT_DESCRIPTION}
     *     also uses this value by default.
     * @param clickHandler A handler for when the chip is selected.
     * @param iconId An icon ID to show beside the chip's text. If not specified, this will be
     *     {@link ChipProperties#ICON}.
     * @return A list item containing a property model for a basic chip.
     */
    public static ListItem buildChipListItem(
            int id, String text, Callback<PropertyModel> clickHandler, @DrawableRes int iconId) {
        PropertyModel model =
                new PropertyModel.Builder(ChipProperties.ALL_KEYS)
                        .with(ChipProperties.ID, id)
                        .with(ChipProperties.TEXT, text)
                        .with(ChipProperties.CONTENT_DESCRIPTION, text)
                        .with(ChipProperties.CLICK_HANDLER, clickHandler)
                        .with(ChipProperties.ICON, iconId)
                        .with(ChipProperties.APPLY_ICON_TINT, true)
                        .with(ChipProperties.ENABLED, true)
                        .with(ChipProperties.SELECTED, false)
                        .with(ChipProperties.TEXT_MAX_WIDTH_PX, ChipProperties.SHOW_WHOLE_TEXT)
                        .build();
        return new ListItem(ChipProperties.BASIC_CHIP, model);
    }

    /** @see {@link #buildChipListItem(int, String, Callback, int)} */
    public static ListItem buildChipListItem(
            int id, String text, Callback<PropertyModel> clickHandler) {
        return buildChipListItem(id, text, clickHandler, ChipProperties.INVALID_ICON_ID);
    }

    /**
     * Destroys the coordinator. This should be called when the coordinator is no longer in use. The
     * coordinator should not be used after that point.
     */
    public void destroy() {}

    /** @return The {@link View} that represents this coordinator. */
    public View getView() {
        return mView;
    }

    /**
     * Set the spacing and padding between each chip.
     *
     * @param chipSpacingPx The spacing between each chip.
     * @param sidePaddingPx The side padding at the start and end of the list.
     */
    public void setSpaceItemDecoration(@Px int chipSpacingPx, @Px int sidePaddingPx) {
        mView.addItemDecoration(new SpaceItemDecoration(chipSpacingPx, sidePaddingPx));
    }

    private static class SpaceItemDecoration extends ItemDecoration {
        private final int mChipSpacingPx;
        private final int mSidePaddingPx;

        public SpaceItemDecoration(@Px int chipSpacingPx, @Px int sidePaddingPx) {
            mChipSpacingPx = chipSpacingPx;
            mSidePaddingPx = sidePaddingPx;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            int position = parent.getChildAdapterPosition(view);
            boolean isFirst = position == 0;
            boolean isLast = position == parent.getAdapter().getItemCount() - 1;
            // using 'parent' not 'view' since 'view' did not layout yet, so view's
            // #getLayoutDirection() won't return correct value.
            boolean isRtl = (parent.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);

            @Px int startPadding = isFirst ? mSidePaddingPx : mChipSpacingPx;
            @Px int endPadding = isLast ? mSidePaddingPx : mChipSpacingPx;

            outRect.left = isRtl ? endPadding : startPadding;
            outRect.right = isRtl ? startPadding : endPadding;
        }
    }
}