chromium/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemViewBinder.java

// Copyright 2021 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.chrome.browser.ui.appmenu;

import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;

import androidx.annotation.ColorRes;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.ImageViewCompat;

import org.chromium.chrome.browser.ui.appmenu.internal.R;
import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams;
import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape;
import org.chromium.components.browser_ui.widget.text.TextViewWithCompoundDrawables;
import org.chromium.ui.UiUtils;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.widget.ChromeImageButton;
import org.chromium.ui.widget.ChromeImageView;

/** The binder to bind the app menu {@link PropertyModel} with the view. */
class AppMenuItemViewBinder {
    /** IDs of all of the buttons in icon_row_menu_item.xml. */
    private static final int[] BUTTON_IDS = {
        R.id.button_one, R.id.button_two, R.id.button_three, R.id.button_four, R.id.button_five
    };

    public static void bindStandardItem(PropertyModel model, View view, PropertyKey key) {
        AppMenuUtil.bindStandardItemEnterAnimation(model, view, key);

        if (key == AppMenuItemProperties.MENU_ITEM_ID) {
            int id = model.get(AppMenuItemProperties.MENU_ITEM_ID);
            view.setId(id);
        } else if (key == AppMenuItemProperties.TITLE) {
            ((TextView) view.findViewById(R.id.menu_item_text))
                    .setText(model.get(AppMenuItemProperties.TITLE));
        } else if (key == AppMenuItemProperties.TITLE_CONDENSED) {
            setContentDescription(view.findViewById(R.id.menu_item_text), model);
        } else if (key == AppMenuItemProperties.ENABLED) {
            boolean enabled = model.get(AppMenuItemProperties.ENABLED);
            view.setEnabled(enabled);
        } else if (key == AppMenuItemProperties.HIGHLIGHTED) {
            if (model.get(AppMenuItemProperties.HIGHLIGHTED)) {
                ViewHighlighter.turnOnHighlight(
                        view, new HighlightParams(HighlightShape.RECTANGLE));
            } else {
                ViewHighlighter.turnOffHighlight(view);
            }
        } else if (key == AppMenuItemProperties.ICON) {
            Drawable icon = model.get(AppMenuItemProperties.ICON);
            ChromeImageView imageView = (ChromeImageView) view.findViewById(R.id.menu_item_icon);

            @ColorRes int colorResId = model.get(AppMenuItemProperties.ICON_COLOR_RES);
            if (colorResId == 0) {
                // If there is no color assigned to the icon, use the default color.
                colorResId = R.color.default_icon_color_secondary_tint_list;
            }
            ColorStateList tintList =
                    AppCompatResources.getColorStateList(imageView.getContext(), colorResId);

            if (model.get(AppMenuItemProperties.ICON_SHOW_BADGE)) {
                // Draw the icon with a red badge on top.
                icon =
                        UiUtils.drawIconWithBadge(
                                imageView.getContext(),
                                icon,
                                colorResId,
                                R.dimen.menu_item_icon_badge_size,
                                R.dimen.menu_item_icon_badge_border_size,
                                R.color.default_red);
                // `colorResId` has already been applied by `drawIconWithBadge` and thus, passing
                // `tintList` is not required.
                // Note that tint is set to null to clear any tint previously set via XML.
                tintList = null;
            }

            imageView.setImageDrawable(icon);
            imageView.setVisibility(icon == null ? View.GONE : View.VISIBLE);

            // tint the icon
            ImageViewCompat.setImageTintList(imageView, tintList);
        } else if (key == AppMenuItemProperties.CLICK_HANDLER) {
            view.setOnClickListener(
                    v -> model.get(AppMenuItemProperties.CLICK_HANDLER).onItemClick(model));
        }
    }

    public static void bindTitleButtonItem(PropertyModel model, View view, PropertyKey key) {
        AppMenuUtil.bindStandardItemEnterAnimation(model, view, key);

        if (key == AppMenuItemProperties.SUBMENU) {
            ModelList subList = model.get(AppMenuItemProperties.SUBMENU);
            PropertyModel titleModel = subList.get(0).model;

            view.setId(titleModel.get(AppMenuItemProperties.MENU_ITEM_ID));

            TextViewWithCompoundDrawables title =
                    (TextViewWithCompoundDrawables) view.findViewById(R.id.title);
            title.setText(titleModel.get(AppMenuItemProperties.TITLE));
            title.setEnabled(titleModel.get(AppMenuItemProperties.ENABLED));
            title.setFocusable(titleModel.get(AppMenuItemProperties.ENABLED));
            title.setCompoundDrawablesRelative(
                    titleModel.get(AppMenuItemProperties.ICON), null, null, null);
            setContentDescription(title, titleModel);

            AppMenuClickHandler appMenuClickHandler =
                    model.get(AppMenuItemProperties.CLICK_HANDLER);
            title.setOnClickListener(v -> appMenuClickHandler.onItemClick(titleModel));
            if (titleModel.get(AppMenuItemProperties.HIGHLIGHTED)) {
                ViewHighlighter.turnOnHighlight(
                        view, new HighlightParams(HighlightShape.RECTANGLE));
            } else {
                ViewHighlighter.turnOffHighlight(view);
            }

            AppMenuItemIcon checkbox = (AppMenuItemIcon) view.findViewById(R.id.checkbox);
            ChromeImageButton button = (ChromeImageButton) view.findViewById(R.id.button);
            PropertyModel buttonModel = null;
            boolean checkable = false;
            boolean checked = false;
            boolean buttonEnabled = true;
            Drawable subIcon = null;

            if (subList.size() == 2) {
                buttonModel = subList.get(1).model;
                checkable = buttonModel.get(AppMenuItemProperties.CHECKABLE);
                checked = buttonModel.get(AppMenuItemProperties.CHECKED);
                buttonEnabled = buttonModel.get(AppMenuItemProperties.ENABLED);
                subIcon = buttonModel.get(AppMenuItemProperties.ICON);
            }

            if (checkable) {
                // Display a checkbox for the MenuItem.
                button.setVisibility(View.GONE);
                checkbox.setVisibility(View.VISIBLE);
                checkbox.setChecked(checked);
                ImageViewCompat.setImageTintList(
                        checkbox,
                        AppCompatResources.getColorStateList(
                                checkbox.getContext(), R.color.selection_control_button_tint_list));
                setupMenuButton(checkbox, buttonModel, appMenuClickHandler);
            } else if (subIcon != null) {
                // Display an icon alongside the MenuItem.
                checkbox.setVisibility(View.GONE);
                button.setVisibility(View.VISIBLE);
                if (!buttonEnabled) {
                    // Only grey out the icon when disabled. When the menu is enabled, use the
                    // icon's original color.
                    Drawable icon = buttonModel.get(AppMenuItemProperties.ICON);
                    DrawableCompat.setTintList(
                            icon,
                            AppCompatResources.getColorStateList(
                                    button.getContext(),
                                    R.color.default_icon_color_secondary_tint_list));
                    buttonModel.set(AppMenuItemProperties.ICON, icon);
                }
                setupImageButton(button, buttonModel, appMenuClickHandler);
            } else {
                // Display just the label of the MenuItem.
                checkbox.setVisibility(View.GONE);
                button.setVisibility(View.GONE);
            }
        } else if (key == AppMenuItemProperties.HIGHLIGHTED) {
            if (model.get(AppMenuItemProperties.HIGHLIGHTED)) {
                ViewHighlighter.turnOnHighlight(
                        view, new HighlightParams(HighlightShape.RECTANGLE));
            } else {
                ViewHighlighter.turnOffHighlight(view);
            }
        }
    }

    public static void bindIconRowItem(PropertyModel model, View view, PropertyKey key) {
        if (key == AppMenuItemProperties.SUBMENU) {
            ModelList iconList = model.get(AppMenuItemProperties.SUBMENU);

            int numItems = iconList.size();
            ImageButton[] buttons = new ImageButton[numItems];
            // Save references to all the buttons.
            for (int i = 0; i < numItems; i++) {
                buttons[i] = (ImageButton) view.findViewById(BUTTON_IDS[i]);
            }

            // Remove unused menu items.
            for (int i = numItems; i < 5; i++) {
                ((ViewGroup) view).removeView(view.findViewById(BUTTON_IDS[i]));
            }

            AppMenuClickHandler appMenuClickHandler =
                    model.get(AppMenuItemProperties.CLICK_HANDLER);
            for (int i = 0; i < numItems; i++) {
                setupImageButton(buttons[i], iconList.get(i).model, appMenuClickHandler);
            }

            boolean isMenuIconAtStart = model.get(AppMenuItemProperties.MENU_ICON_AT_START);
            view.setTag(
                    R.id.menu_item_enter_anim_id,
                    AppMenuUtil.buildIconItemEnterAnimator(buttons, isMenuIconAtStart));

            // Tint action bar's background.
            view.setBackgroundResource(R.drawable.menu_action_bar_bg);

            view.setEnabled(false);
        }
    }

    public static void setContentDescription(View view, final PropertyModel model) {
        CharSequence titleCondensed = model.get(AppMenuItemProperties.TITLE_CONDENSED);
        if (TextUtils.isEmpty(titleCondensed)) {
            view.setContentDescription(null);
        } else {
            view.setContentDescription(titleCondensed);
        }
    }

    private static void setupImageButton(
            ImageButton button,
            final PropertyModel model,
            AppMenuClickHandler appMenuClickHandler) {
        // Store and recover the level of image as button.setimageDrawable
        // resets drawable to default level.
        Drawable icon = model.get(AppMenuItemProperties.ICON);
        int currentLevel = icon.getLevel();
        button.setImageDrawable(icon);
        icon.setLevel(currentLevel);

        // TODO(gangwu): Resetting this tint if we go from checked -> not checked while the menu is
        // visible.
        if (model.get(AppMenuItemProperties.CHECKED)) {
            ImageViewCompat.setImageTintList(
                    button,
                    AppCompatResources.getColorStateList(
                            button.getContext(), R.color.default_icon_color_accent1_tint_list));
        }

        setupMenuButton(button, model, appMenuClickHandler);
    }

    private static void setupMenuButton(
            View button, final PropertyModel model, AppMenuClickHandler appMenuClickHandler) {
        // On Android M, even setEnabled(false), the button still focusable.
        button.setEnabled(model.get(AppMenuItemProperties.ENABLED));
        button.setFocusable(model.get(AppMenuItemProperties.ENABLED));

        CharSequence titleCondensed = model.get(AppMenuItemProperties.TITLE_CONDENSED);
        if (TextUtils.isEmpty(titleCondensed)) {
            button.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
        } else {
            button.setContentDescription(titleCondensed);
            button.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
        }

        button.setOnClickListener(v -> appMenuClickHandler.onItemClick(model));
        button.setOnLongClickListener(v -> appMenuClickHandler.onItemLongClick(model, button));

        if (model.get(AppMenuItemProperties.HIGHLIGHTED)) {
            ViewHighlighter.turnOnHighlight(button, new HighlightParams(HighlightShape.CIRCLE));
        } else {
            ViewHighlighter.turnOffHighlight(button);
        }

        // Menu items may be hidden by command line flags before they get to this point.
        button.setVisibility(View.VISIBLE);
    }
}