| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/autofill/popup/popup_cell_utils.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "build/branding_buildflags.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_controller.h" |
| #include "chrome/browser/ui/passwords/ui_utils.h" |
| #include "chrome/browser/ui/views/autofill/popup/popup_base_view.h" |
| #include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h" |
| #include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/chrome_typography.h" |
| #include "components/autofill/core/browser/data_model/credit_card.h" |
| #include "components/autofill/core/browser/ui/autofill_resource_utils.h" |
| #include "components/autofill/core/browser/ui/suggestion.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_payments_features.h" |
| #include "components/omnibox/browser/vector_icons.h" |
| #include "ui/color/color_id.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/views/controls/throbber.h" |
| #include "ui/views/style/typography.h" |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| #include "components/plus_addresses/resources/vector_icons.h" |
| #endif |
| #include "components/strings/grit/components_strings.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/menu/menu_config.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/box_layout_view.h" |
| #include "ui/views/layout/flex_layout.h" |
| #include "ui/views/layout/table_layout_view.h" |
| #include "ui/views/view.h" |
| |
| namespace autofill::popup_cell_utils { |
| |
| namespace { |
| |
| // The default icon size used in the suggestion drop down. |
| constexpr int kIconSize = 16; |
| constexpr int kChromeRefreshIconSize = 20; |
| |
| // Max width for the Autofill suggestion text. |
| constexpr int kAutofillSuggestionMaxWidth = 192; |
| |
| // Max width for address profile suggestion text when granular filling is |
| // enabled. |
| constexpr int kAutofillPopupAddressProfileGranularFillingEnabledMaxWidth = 320; |
| |
| // The additional height of the row in case it has two lines of text. |
| constexpr int kAutofillPopupAdditionalDoubleRowHeight = 22; |
| constexpr int kAutofillPopupAdditionalDoubleRowHeightNewStyle = 16; |
| |
| // The additional padding of the row in case it has three lines of text. |
| constexpr int kAutofillPopupAdditionalVerticalPadding = 16; |
| |
| // Vertical spacing between labels in one row. |
| constexpr int kAdjacentLabelsVerticalSpacing = 2; |
| |
| // The icon size used in the suggestion dropdown for displaying the Google |
| // Password Manager icon in the Manager Passwords entry. |
| constexpr int kGooglePasswordManagerIconSize = 20; |
| |
| // Metric to measure the duration of getting the image for the Autofill pop-up. |
| constexpr char kHistogramGetImageViewByName[] = |
| "Autofill.PopupGetImageViewTime"; |
| |
| // The opacity for grayed-out disabled views. |
| constexpr double kGrayedOutOpacity = 0.38; |
| |
| // Returns the name of the network for payment method icons, empty string |
| // otherwise. |
| std::u16string GetIconAccessibleName(Suggestion::Icon icon) { |
| // Networks for which icons are currently shown. |
| switch (icon) { |
| case Suggestion::Icon::kCardAmericanExpress: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX); |
| case Suggestion::Icon::kCardDiners: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DINERS); |
| case Suggestion::Icon::kCardDiscover: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DISCOVER); |
| case Suggestion::Icon::kCardElo: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_ELO); |
| case Suggestion::Icon::kCardJCB: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_JCB); |
| case Suggestion::Icon::kCardMasterCard: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MASTERCARD); |
| case Suggestion::Icon::kCardMir: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MIR); |
| case Suggestion::Icon::kCardTroy: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_TROY); |
| case Suggestion::Icon::kCardUnionPay: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_UNION_PAY); |
| case Suggestion::Icon::kCardVisa: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VISA); |
| // Other networks. |
| case Suggestion::Icon::kCardGeneric: |
| return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_GENERIC); |
| case Suggestion::Icon::kNoIcon: |
| case Suggestion::Icon::kAccount: |
| case Suggestion::Icon::kClear: |
| case Suggestion::Icon::kCreate: |
| case Suggestion::Icon::kCode: |
| case Suggestion::Icon::kDelete: |
| case Suggestion::Icon::kDevice: |
| case Suggestion::Icon::kEdit: |
| case Suggestion::Icon::kEmpty: |
| case Suggestion::Icon::kGlobe: |
| case Suggestion::Icon::kGoogle: |
| case Suggestion::Icon::kGooglePasswordManager: |
| case Suggestion::Icon::kGooglePay: |
| case Suggestion::Icon::kGooglePayDark: |
| case Suggestion::Icon::kHttpWarning: |
| case Suggestion::Icon::kHttpsInvalid: |
| case Suggestion::Icon::kKey: |
| case Suggestion::Icon::kLocation: |
| case Suggestion::Icon::kMagic: |
| case Suggestion::Icon::kOfferTag: |
| case Suggestion::Icon::kPenSpark: |
| case Suggestion::Icon::kScanCreditCard: |
| case Suggestion::Icon::kSettings: |
| case Suggestion::Icon::kSettingsAndroid: |
| case Suggestion::Icon::kUndo: |
| case Suggestion::Icon::kPlusAddress: |
| return std::u16string(); |
| } |
| NOTREACHED_NORETURN(); |
| } |
| |
| std::unique_ptr<views::ImageView> ImageViewFromImageSkia( |
| const gfx::ImageSkia& image_skia) { |
| if (image_skia.isNull()) { |
| return nullptr; |
| } |
| auto image_view = std::make_unique<views::ImageView>(); |
| image_view->SetImage(ui::ImageModel::FromImageSkia(image_skia)); |
| return image_view; |
| } |
| |
| std::unique_ptr<views::ImageView> GetIconImageViewFromIcon( |
| Suggestion::Icon icon) { |
| switch (icon) { |
| case Suggestion::Icon::kNoIcon: |
| return nullptr; |
| case Suggestion::Icon::kHttpWarning: |
| // For the http warning message, get the icon images from VectorIcon, |
| // which is the same as the security indicator icons in the location bar. |
| return ImageViewFromVectorIcon(omnibox::kHttpIcon, kIconSize); |
| case Suggestion::Icon::kHttpsInvalid: |
| return std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon( |
| vector_icons::kNotSecureWarningIcon, ui::kColorAlertHighSeverity, |
| kIconSize)); |
| case Suggestion::Icon::kKey: |
| return ImageViewFromVectorIcon(kKeyIcon, kIconSize); |
| case Suggestion::Icon::kEdit: |
| return ImageViewFromVectorIcon(vector_icons::kEditChromeRefreshIcon, |
| kChromeRefreshIconSize); |
| case Suggestion::Icon::kCode: |
| return ImageViewFromVectorIcon(vector_icons::kCodeIcon, kIconSize); |
| case Suggestion::Icon::kLocation: |
| return ShouldApplyNewAutofillPopupStyle() |
| ? ImageViewFromVectorIcon( |
| vector_icons::kLocationOnChromeRefreshIcon, |
| kChromeRefreshIconSize) |
| : ImageViewFromVectorIcon(vector_icons::kLocationOnIcon, |
| kIconSize); |
| case Suggestion::Icon::kDelete: |
| return ImageViewFromVectorIcon(kTrashCanRefreshIcon, |
| kChromeRefreshIconSize); |
| case Suggestion::Icon::kClear: |
| return ImageViewFromVectorIcon(kBackspaceIcon, kIconSize); |
| case Suggestion::Icon::kUndo: |
| return ImageViewFromVectorIcon(vector_icons::kUndoIcon, kIconSize); |
| case Suggestion::Icon::kGlobe: |
| return ImageViewFromVectorIcon(kGlobeIcon, kIconSize); |
| case Suggestion::Icon::kMagic: |
| return ImageViewFromVectorIcon(vector_icons::kMagicButtonIcon, kIconSize); |
| case Suggestion::Icon::kAccount: |
| return ImageViewFromVectorIcon(kAccountCircleIcon, kIconSize); |
| case Suggestion::Icon::kSettings: |
| return ImageViewFromVectorIcon(omnibox::kProductIcon, kIconSize); |
| case Suggestion::Icon::kEmpty: |
| return ImageViewFromVectorIcon(omnibox::kHttpIcon, kIconSize); |
| case Suggestion::Icon::kDevice: |
| return ImageViewFromVectorIcon(kDevicesIcon, kIconSize); |
| case Suggestion::Icon::kGoogle: |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| return ImageViewFromImageSkia(gfx::CreateVectorIcon( |
| vector_icons::kGoogleGLogoIcon, kIconSize, gfx::kPlaceholderColor)); |
| #else |
| return nullptr; |
| #endif |
| case Suggestion::Icon::kPenSpark: |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| return ImageViewFromVectorIcon(vector_icons::kPenSparkIcon, kIconSize); |
| #else |
| return ImageViewFromVectorIcon(vector_icons::kEditIcon, kIconSize); |
| #endif |
| case Suggestion::Icon::kGooglePasswordManager: |
| return ImageViewFromVectorIcon(GooglePasswordManagerVectorIcon(), |
| kGooglePasswordManagerIconSize); |
| case Suggestion::Icon::kPlusAddress: |
| #if BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| return ImageViewFromVectorIcon(plus_addresses::kPlusAddressesLogoIcon, |
| kIconSize); |
| #else |
| return ImageViewFromVectorIcon(vector_icons::kEmailIcon, kIconSize); |
| #endif |
| #if !BUILDFLAG(GOOGLE_CHROME_BRANDING) |
| case Suggestion::Icon::kGooglePay: |
| case Suggestion::Icon::kGooglePayDark: |
| return nullptr; |
| #else |
| case Suggestion::Icon::kGooglePay: |
| case Suggestion::Icon::kGooglePayDark: |
| #endif |
| case Suggestion::Icon::kCreate: |
| case Suggestion::Icon::kOfferTag: |
| case Suggestion::Icon::kScanCreditCard: |
| case Suggestion::Icon::kSettingsAndroid: |
| case Suggestion::Icon::kCardGeneric: |
| case Suggestion::Icon::kCardAmericanExpress: |
| case Suggestion::Icon::kCardDiners: |
| case Suggestion::Icon::kCardDiscover: |
| case Suggestion::Icon::kCardElo: |
| case Suggestion::Icon::kCardJCB: |
| case Suggestion::Icon::kCardMasterCard: |
| case Suggestion::Icon::kCardMir: |
| case Suggestion::Icon::kCardTroy: |
| case Suggestion::Icon::kCardUnionPay: |
| case Suggestion::Icon::kCardVisa: |
| // For other suggestion entries, get the icon from PNG files. |
| int icon_id = GetIconResourceID(icon); |
| DCHECK_NE(icon_id, 0); |
| return ImageViewFromImageSkia( |
| *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(icon_id)); |
| } |
| NOTREACHED_NORETURN(); |
| } |
| |
| } // namespace |
| |
| std::u16string GetVoiceOverStringFromSuggestion(const Suggestion& suggestion) { |
| if (suggestion.voice_over) { |
| return *suggestion.voice_over; |
| } |
| |
| std::vector<std::u16string> text; |
| auto add_if_not_empty = [&text](std::u16string value) { |
| if (!value.empty()) { |
| text.push_back(std::move(value)); |
| } |
| }; |
| |
| add_if_not_empty(GetIconAccessibleName(suggestion.icon)); |
| text.push_back(suggestion.main_text.value); |
| add_if_not_empty(suggestion.minor_text.value); |
| |
| for (const std::vector<Suggestion::Text>& row : suggestion.labels) { |
| for (const Suggestion::Text& label : row) { |
| // `label_text` is not populated for footers or autocomplete entries. |
| add_if_not_empty(label.value); |
| } |
| } |
| |
| // `additional_label` is only populated in a passwords context. |
| add_if_not_empty(suggestion.additional_label); |
| |
| return base::JoinString(text, u" "); |
| } |
| |
| // TODO(b/330912574): Refactor this code that is today confusing. Horizontal |
| // margins are being set both here and in the popup row. Furthermore, there is a |
| // `PopupBaseView::GetHorizontalMargin()` that makes things even more |
| // complicated. |
| gfx::Insets GetMarginsForContentCell(bool has_control_element) { |
| int left_margin = PopupBaseView::GetHorizontalMargin(); |
| int right_margin = left_margin; |
| if (ShouldApplyNewAutofillPopupStyle()) { |
| // If the feature is enabled, then the row already adds some extra |
| // horizontal margin on the left - deduct that. |
| left_margin = std::max( |
| 0, left_margin - ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_CONTENT_LIST_VERTICAL_SINGLE)); |
| |
| // If there is no control element, then this is the only cell and the same |
| // correction needs to be made on the right side, too. |
| if (!has_control_element) { |
| right_margin = left_margin; |
| } |
| } |
| return gfx::Insets::TLBR(0, left_margin, 0, right_margin); |
| } |
| |
| std::unique_ptr<views::ImageView> GetIconImageView( |
| const Suggestion& suggestion) { |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| |
| if (!suggestion.custom_icon.IsEmpty()) { |
| return ImageViewFromImageSkia(suggestion.custom_icon.AsImageSkia()); |
| } |
| std::unique_ptr<views::ImageView> icon_image_view = |
| GetIconImageViewFromIcon(suggestion.icon); |
| base::UmaHistogramTimes(kHistogramGetImageViewByName, |
| base::TimeTicks::Now() - start_time); |
| |
| if (icon_image_view && ShouldApplyNewAutofillPopupStyle()) { |
| // It is possible to have icons of different sizes (kChromeRefreshIconSize |
| // and kIconSize) on the same popup. Setting the icon view width to |
| // the largest value ensures that the icon occupies consistent horizontal |
| // space and makes icons (and the text after them) aligned. It expands |
| // the area of kIconSize icons only and doesn't change those that are bigger |
| // by design (e.g. payment card icons) and have no alignment issues. |
| gfx::Size size = icon_image_view->GetPreferredSize(); |
| size.set_width(std::max(kChromeRefreshIconSize, size.width())); |
| icon_image_view->SetPreferredSize(size); |
| } |
| |
| return icon_image_view; |
| } |
| |
| std::unique_ptr<views::ImageView> GetTrailingIconImageView( |
| const Suggestion& suggestion) { |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| std::unique_ptr<views::ImageView> icon_image_view = |
| GetIconImageViewFromIcon(suggestion.trailing_icon); |
| base::UmaHistogramTimes(kHistogramGetImageViewByName, |
| base::TimeTicks::Now() - start_time); |
| |
| return icon_image_view; |
| } |
| |
| // Adds a spacer with `spacer_width` to `view`. `layout` must be the |
| // LayoutManager of `view`. |
| void AddSpacerWithSize(views::View& view, |
| views::BoxLayout& layout, |
| int spacer_width, |
| bool resize) { |
| auto spacer = views::Builder<views::View>() |
| .SetPreferredSize(gfx::Size(spacer_width, 1)) |
| .Build(); |
| layout.SetFlexForView(view.AddChildView(std::move(spacer)), |
| /*flex=*/resize ? 1 : 0, |
| /*use_min_size=*/true); |
| } |
| |
| // Creates the table in which all the Autofill suggestion content apart from |
| // leading and trailing icons is contained and adds it to `content_view`. |
| // It registers `main_text_label`, `minor_text_label`, and `description_label` |
| // with `content_view` for tracking, but assumes that the labels inside of of |
| // `subtext_views` have already been registered for tracking with |
| // `content_view`. |
| void AddSuggestionContentTableToView( |
| std::unique_ptr<views::Label> main_text_label, |
| std::unique_ptr<views::Label> minor_text_label, |
| std::unique_ptr<views::Label> description_label, |
| std::vector<std::unique_ptr<views::View>> subtext_views, |
| PopupRowContentView& content_view) { |
| const int kDividerSpacing = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_RELATED_LABEL_HORIZONTAL_LIST); |
| auto content_table = |
| views::Builder<views::TableLayoutView>() |
| .AddColumn(views::LayoutAlignment::kStart, |
| views::LayoutAlignment::kStretch, |
| views::TableLayout::kFixedSize, |
| views::TableLayout::ColumnSize::kUsePreferred, 0, 0) |
| .AddPaddingColumn(views::TableLayout::kFixedSize, kDividerSpacing) |
| .AddColumn(views::LayoutAlignment::kStart, |
| views::LayoutAlignment::kStretch, |
| views::TableLayout::kFixedSize, |
| views::TableLayout::ColumnSize::kUsePreferred, 0, 0) |
| .Build(); |
| |
| // Major and minor text go into the first row, first column. |
| content_table->AddRows(1, 0); |
| if (minor_text_label) { |
| auto first_line_container = std::make_unique<views::View>(); |
| first_line_container |
| ->SetLayoutManager(std::make_unique<views::FlexLayout>()) |
| ->SetOrientation(views::LayoutOrientation::kHorizontal) |
| .SetMainAxisAlignment(views::LayoutAlignment::kStart) |
| .SetCrossAxisAlignment(views::LayoutAlignment::kCenter) |
| .SetIgnoreDefaultMainAxisMargins(true) |
| .SetCollapseMargins(true) |
| .SetDefault( |
| views::kMarginsKey, |
| gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_RELATED_LABEL_HORIZONTAL_LIST))); |
| |
| content_view.TrackLabel( |
| first_line_container->AddChildView(std::move(main_text_label))); |
| content_view.TrackLabel( |
| first_line_container->AddChildView(std::move(minor_text_label))); |
| content_table->AddChildView(std::move(first_line_container)); |
| } else { |
| content_view.TrackLabel( |
| content_table->AddChildView(std::move(main_text_label))); |
| } |
| |
| // The description goes into the first row, second column. |
| if (description_label) { |
| content_view.TrackLabel( |
| content_table->AddChildView(std::move(description_label))); |
| } else { |
| content_table->AddChildView(std::make_unique<views::View>()); |
| } |
| |
| // Every subtext label goes into an additional row. |
| for (std::unique_ptr<views::View>& subtext_view : subtext_views) { |
| content_table->AddPaddingRow(0, kAdjacentLabelsVerticalSpacing) |
| .AddRows(1, 0); |
| content_table->AddChildView(std::move(subtext_view)); |
| content_table->AddChildView(std::make_unique<views::View>()); |
| } |
| content_view.AddChildView(std::move(content_table)); |
| } |
| |
| // Creates the content structure shared by autocomplete, address, credit card, |
| // and password suggestions. |
| // - `minor_text_label`, `description_label`, and `subtext_labels` may all be |
| // null or empty. |
| // - `content_view` is the (assumed to be empty) view to which the content |
| // structure for the `suggestion` is added. |
| void AddSuggestionContentToView( |
| const Suggestion& suggestion, |
| std::unique_ptr<views::Label> main_text_label, |
| std::unique_ptr<views::Label> minor_text_label, |
| std::unique_ptr<views::Label> description_label, |
| std::vector<std::unique_ptr<views::View>> subtext_views, |
| PopupRowContentView& content_view) { |
| views::BoxLayout& layout = |
| *content_view.SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal, |
| GetMarginsForContentCell(/*has_control_element=*/false))); |
| |
| layout.set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kCenter); |
| |
| // Adjust the row height based on the number of subtexts (lines of text). |
| int row_height = views::MenuConfig::instance().touchable_menu_height; |
| if (!subtext_views.empty()) { |
| row_height += ShouldApplyNewAutofillPopupStyle() |
| ? kAutofillPopupAdditionalDoubleRowHeightNewStyle |
| : kAutofillPopupAdditionalDoubleRowHeight; |
| } |
| layout.set_minimum_cross_axis_size(row_height); |
| |
| // If there are three rows in total, add extra padding to avoid cramming. |
| DCHECK_LE(subtext_views.size(), 2u); |
| if (subtext_views.size() == 2u) { |
| layout.set_inside_border_insets( |
| gfx::Insets::TLBR(kAutofillPopupAdditionalVerticalPadding, |
| layout.inside_border_insets().left(), |
| kAutofillPopupAdditionalVerticalPadding, |
| layout.inside_border_insets().right())); |
| } |
| |
| // The leading icon. |
| if (suggestion.is_loading) { |
| content_view.AddChildView(std::make_unique<views::Throbber>())->Start(); |
| AddSpacerWithSize(content_view, layout, |
| PopupBaseView::GetHorizontalPadding(), |
| /*resize=*/false); |
| content_view.SetEnabled(false); |
| } else if (std::unique_ptr<views::ImageView> icon = |
| GetIconImageView(suggestion)) { |
| if (suggestion.apply_deactivated_style) { |
| ApplyDeactivatedStyle(*icon); |
| } |
| content_view.AddChildView(std::move(icon)); |
| AddSpacerWithSize(content_view, layout, |
| PopupBaseView::GetHorizontalPadding(), |
| /*resize=*/false); |
| } |
| |
| // The actual content table. |
| AddSuggestionContentTableToView( |
| std::move(main_text_label), std::move(minor_text_label), |
| std::move(description_label), std::move(subtext_views), content_view); |
| |
| // The trailing icon. |
| if (std::unique_ptr<views::ImageView> trailing_icon = |
| GetTrailingIconImageView(suggestion)) { |
| AddSpacerWithSize(content_view, layout, |
| PopupBaseView::GetHorizontalPadding(), |
| /*resize=*/true); |
| content_view.AddChildView(std::move(trailing_icon)); |
| } |
| |
| // Force a refresh to ensure all the labels'styles are correct. |
| content_view.UpdateStyle(/*selected=*/false); |
| } |
| |
| void FormatLabel(views::Label& label, |
| const Suggestion::Text& text, |
| FillingProduct main_filling_product, |
| int maximum_width_single_line) { |
| switch (main_filling_product) { |
| case FillingProduct::kAddress: |
| case FillingProduct::kAutocomplete: |
| case FillingProduct::kPlusAddresses: |
| label.SetMaximumWidthSingleLine(maximum_width_single_line); |
| break; |
| case FillingProduct::kCreditCard: |
| if (text.should_truncate.value()) { |
| // should_truncate should only be set to true iff the experiments are |
| // enabled. |
| DCHECK(base::FeatureList::IsEnabled( |
| autofill::features::kAutofillEnableVirtualCardMetadata)); |
| DCHECK(base::FeatureList::IsEnabled( |
| autofill::features::kAutofillEnableCardProductName)); |
| label.SetMaximumWidthSingleLine(maximum_width_single_line); |
| } |
| break; |
| case FillingProduct::kCompose: |
| case FillingProduct::kIban: |
| case FillingProduct::kMerchantPromoCode: |
| case FillingProduct::kPassword: |
| case FillingProduct::kNone: |
| break; |
| } |
| } |
| |
| // Creates a label for the suggestion's main text. |
| std::unique_ptr<views::Label> CreateMainTextLabel(const Suggestion& suggestion, |
| int primary_text_style) { |
| int non_primary_text_style = ShouldApplyNewAutofillPopupStyle() |
| ? views::style::TextStyle::STYLE_BODY_3 |
| : views::style::TextStyle::STYLE_PRIMARY; |
| auto label = std::make_unique<views::Label>( |
| suggestion.main_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT, |
| suggestion.main_text.is_primary ? primary_text_style |
| : non_primary_text_style); |
| |
| if (!suggestion.main_text.is_primary && ShouldApplyNewAutofillPopupStyle()) { |
| label->SetEnabledColorId(ui::kColorLabelForegroundSecondary); |
| } |
| if (suggestion.apply_deactivated_style) { |
| ApplyDeactivatedStyle(*label); |
| } |
| return label; |
| } |
| |
| // Creates a label for the suggestion's minor text. |
| std::unique_ptr<views::Label> CreateMinorTextLabel( |
| const Suggestion& suggestion) { |
| if (suggestion.minor_text.value.empty()) { |
| return nullptr; |
| } |
| |
| auto label = std::make_unique<views::Label>( |
| suggestion.minor_text.value, views::style::CONTEXT_DIALOG_BODY_TEXT, |
| GetSecondaryTextStyle()); |
| if (ShouldApplyNewAutofillPopupStyle()) { |
| label->SetEnabledColorId(ui::kColorLabelForegroundSecondary); |
| } |
| if (suggestion.apply_deactivated_style) { |
| ApplyDeactivatedStyle(*label); |
| } |
| return label; |
| } |
| |
| int GetMaxPopupAddressProfileWidth() { |
| // TODO(crbug.com/1459990): Remove feature check as part of the clean up. |
| return base::FeatureList::IsEnabled( |
| features::kAutofillGranularFillingAvailable) |
| ? kAutofillPopupAddressProfileGranularFillingEnabledMaxWidth |
| : kAutofillSuggestionMaxWidth; |
| } |
| |
| std::vector<std::unique_ptr<views::View>> CreateAndTrackSubtextViews( |
| PopupRowContentView& content_view, |
| const Suggestion& suggestion, |
| FillingProduct main_filling_product, |
| std::optional<int> text_style) { |
| std::vector<std::unique_ptr<views::View>> result; |
| const int kHorizontalSpacing = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| DISTANCE_RELATED_LABEL_HORIZONTAL_LIST); |
| |
| for (const std::vector<Suggestion::Text>& label_row : suggestion.labels) { |
| if (base::ranges::all_of(label_row, &std::u16string::empty, |
| &Suggestion::Text::value)) { |
| // If a row is empty, do not include any further rows. |
| return result; |
| } |
| |
| auto label_row_container_view = |
| views::Builder<views::BoxLayoutView>() |
| .SetOrientation(views::BoxLayout::Orientation::kHorizontal) |
| .SetBetweenChildSpacing(kHorizontalSpacing) |
| .Build(); |
| for (const Suggestion::Text& label_text : label_row) { |
| // If a column is empty, do not include any further columns. |
| if (label_text.value.empty()) { |
| break; |
| } |
| auto* label = |
| label_row_container_view->AddChildView(std::make_unique<views::Label>( |
| label_text.value, |
| ChromeTextContext::CONTEXT_DIALOG_BODY_TEXT_SMALL, |
| text_style ? *text_style : GetSecondaryTextStyle())); |
| if (ShouldApplyNewAutofillPopupStyle()) { |
| label->SetEnabledColorId(ui::kColorLabelForegroundSecondary); |
| } |
| content_view.TrackLabel(label); |
| // TODO(crbug.com/1459990): Remove feature check as part of the clean up. |
| if (!base::FeatureList::IsEnabled( |
| features::kAutofillGranularFillingAvailable)) { |
| FormatLabel(*label, label_text, main_filling_product, |
| GetMaxPopupAddressProfileWidth()); |
| } else { |
| // To make sure the popup width will not exceed its maximum value, |
| // divide the maximum label width by the number of labels. |
| FormatLabel(*label, label_text, main_filling_product, |
| GetMaxPopupAddressProfileWidth() / label_row.size()); |
| } |
| } |
| result.push_back(std::move(label_row_container_view)); |
| } |
| |
| return result; |
| } |
| |
| std::unique_ptr<views::ImageView> ImageViewFromVectorIcon( |
| const gfx::VectorIcon& vector_icon, |
| int icon_size = kIconSize) { |
| return std::make_unique<views::ImageView>( |
| ui::ImageModel::FromVectorIcon(vector_icon, ui::kColorIcon, icon_size)); |
| } |
| |
| void ApplyDeactivatedStyle(views::View& view) { |
| view.SetPaintToLayer(); |
| view.layer()->SetOpacity(kGrayedOutOpacity); |
| } |
| |
| } // namespace autofill::popup_cell_utils |