| // 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/web_apps/isolated_web_apps/isolated_web_app_installer_view_impl.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/callback.h" |
| #include "base/functional/overloaded.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_model.h" |
| #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view.h" |
| #include "chrome/browser/ui/views/web_apps/web_app_info_image_source.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/signed_web_bundle_metadata.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_header_macros.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/models/dialog_model.h" |
| #include "ui/base/models/dialog_model_field.h" |
| #include "ui/base/models/image_model.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/color/color_id.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/range/range.h" |
| #include "ui/gfx/vector_icon_types.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/background.h" |
| #include "ui/views/bubble/bubble_dialog_delegate_view.h" |
| #include "ui/views/bubble/bubble_dialog_model_host.h" |
| #include "ui/views/controls/progress_bar.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/box_layout_view.h" |
| #include "ui/views/layout/layout_provider.h" |
| #include "ui/views/style/typography.h" |
| #include "ui/views/view_class_properties.h" |
| #include "ui/views/view_observer.h" |
| |
| namespace web_app { |
| namespace { |
| |
| constexpr int kIconSize = 32; |
| constexpr int kNestedDialogIconSize = 24; |
| constexpr int kInfoPaneCornerRadius = 10; |
| constexpr int kProgressViewHorizontalPadding = 45; |
| |
| gfx::Insets BottomPadding(views::DistanceMetric distance) { |
| return gfx::Insets::TLBR( |
| 0, 0, ChromeLayoutProvider::Get()->GetDistanceMetric(distance), 0); |
| } |
| |
| std::unique_ptr<views::StyledLabel> CreateLabelWithContextAndStyle( |
| views::style::TextContext text_context, |
| views::style::TextStyle text_style, |
| std::optional<std::u16string> text = std::nullopt) { |
| auto label = std::make_unique<views::StyledLabel>(); |
| label->SetTextContext(text_context); |
| label->SetDefaultTextStyle(text_style); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| if (text) { |
| label->SetText(*text); |
| } |
| return label; |
| } |
| |
| ui::ImageModel CreateImageModelFromVector(const gfx::VectorIcon& vector_icon, |
| ui::ColorId color_id) { |
| return ui::ImageModel::FromVectorIcon(vector_icon, color_id, kIconSize); |
| } |
| |
| ui::ImageModel CreateImageModelFromBundleMetadata( |
| const SignedWebBundleMetadata& metadata) { |
| // WebAppInfoImageSource only stores images at specific sizes. Request the |
| // smallest size that's bigger than kIconSize. |
| int app_icon_size = 32; |
| gfx::ImageSkia icon_image(std::make_unique<WebAppInfoImageSource>( |
| app_icon_size, metadata.icons().any), |
| gfx::Size(app_icon_size, app_icon_size)); |
| return ui::ImageModel::FromImageSkia(icon_image); |
| } |
| |
| // Implicitly converts an id or raw string to a string. Used as an argument to |
| // functions that need a string, but want to accept either ids or raw strings. |
| class ToU16String { |
| public: |
| // NOLINTNEXTLINE(runtime/explicit) |
| ToU16String(int string_id) : string_(l10n_util::GetStringUTF16(string_id)) {} |
| |
| // NOLINTNEXTLINE(runtime/explicit) |
| ToU16String(const std::u16string& string) : string_(string) {} |
| |
| const std::u16string& get() const { return string_; } |
| |
| private: |
| std::u16string string_; |
| }; |
| |
| // A View containing a progress bar and a description string below it. |
| class AnnotatedProgressBar : public views::BoxLayoutView { |
| METADATA_HEADER(AnnotatedProgressBar, views::BoxLayoutView) |
| |
| public: |
| explicit AnnotatedProgressBar(const std::u16string& description) { |
| SetOrientation(views::BoxLayout::Orientation::kVertical); |
| SetInsideBorderInsets(gfx::Insets::VH(0, kProgressViewHorizontalPadding)); |
| |
| progress_bar_ = AddChildView(std::make_unique<views::ProgressBar>()); |
| progress_bar_->SetProperty( |
| views::kMarginsKey, |
| BottomPadding(views::DISTANCE_RELATED_CONTROL_VERTICAL)); |
| AddChildView(CreateLabelWithContextAndStyle( |
| views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_SECONDARY, |
| description)); |
| } |
| |
| void UpdateProgress(double percent) { progress_bar_->SetValue(percent); } |
| |
| private: |
| raw_ptr<views::ProgressBar> progress_bar_; |
| }; |
| BEGIN_METADATA(AnnotatedProgressBar) |
| END_METADATA |
| |
| // A View that displays key/value entries in a pane with a different |
| // background color and a rounded border. |
| class InfoPane : public views::BoxLayoutView { |
| METADATA_HEADER(InfoPane, views::BoxLayoutView) |
| |
| public: |
| explicit InfoPane( |
| const std::vector<std::pair<int, std::u16string>>& metadata) { |
| views::LayoutProvider* provider = views::LayoutProvider::Get(); |
| SetInsideBorderInsets( |
| provider->GetInsetsMetric(views::InsetsMetric::INSETS_DIALOG)); |
| SetOrientation(views::BoxLayout::Orientation::kVertical); |
| SetBackground(views::CreateThemedRoundedRectBackground( |
| ui::kColorSubtleEmphasisBackground, kInfoPaneCornerRadius)); |
| SetData(metadata); |
| } |
| |
| void SetData(const std::vector<std::pair<int, std::u16string>>& metadata) { |
| RemoveAllChildViews(); |
| for (const auto& data : metadata) { |
| size_t offset; |
| views::StyledLabel* label = AddChildView(CreateLabelWithContextAndStyle( |
| views::style::CONTEXT_LABEL, views::style::STYLE_SECONDARY)); |
| label->SetText( |
| l10n_util::GetStringFUTF16(data.first, data.second, &offset)); |
| |
| if (data.second.size() > 0) { |
| views::StyledLabel::RangeStyleInfo style; |
| style.custom_font = label->GetFontList().Derive( |
| 0, gfx::Font::FontStyle::NORMAL, gfx::Font::Weight::BOLD); |
| label->AddStyleRange(gfx::Range(0, offset), style); |
| } |
| } |
| InvalidateLayout(); |
| } |
| }; |
| BEGIN_METADATA(InfoPane) |
| END_METADATA |
| |
| // The contents view used for all installer screens. This will handle rendering |
| // common UI elements like icon, title, subtitle, and an optional View for the |
| // body of the dialog. |
| class InstallerDialogView : public views::BoxLayoutView { |
| METADATA_HEADER(InstallerDialogView, views::BoxLayoutView) |
| |
| public: |
| InstallerDialogView(const ui::ImageModel& icon_model, |
| const ToU16String& title, |
| int subtitle_id, |
| std::optional<ToU16String> subtitle_param = std::nullopt, |
| std::optional<base::RepeatingClosure> |
| subtitle_link_callback = std::nullopt) { |
| SetOrientation(views::BoxLayout::Orientation::kVertical); |
| SetInsideBorderInsets(views::LayoutProvider::Get()->GetInsetsMetric( |
| views::InsetsMetric::INSETS_DIALOG)); |
| SetCollapseMarginsSpacing(true); |
| |
| icon_ = AddChildView(std::make_unique<NonAccessibleImageView>()); |
| icon_->SetHorizontalAlignment(views::ImageView::Alignment::kLeading); |
| icon_->SetImageSize(gfx::Size(kIconSize, kIconSize)); |
| icon_->SetProperty( |
| views::kMarginsKey, |
| BottomPadding(views::DISTANCE_UNRELATED_CONTROL_VERTICAL)); |
| SetIcon(icon_model); |
| |
| title_label_ = AddChildView(CreateLabelWithContextAndStyle( |
| views::style::CONTEXT_DIALOG_TITLE, views::style::STYLE_PRIMARY)); |
| SetTitle(title); |
| |
| subtitle_label_ = AddChildView(CreateLabelWithContextAndStyle( |
| views::style::CONTEXT_DIALOG_BODY_TEXT, views::style::STYLE_SECONDARY)); |
| SetSubtitle(subtitle_id, subtitle_param, subtitle_link_callback); |
| } |
| |
| void SetIcon(const ui::ImageModel& icon_model) { |
| icon_->SetImage(icon_model); |
| } |
| |
| void SetTitle(const ToU16String& title) { |
| title_label_->SetText(title.get()); |
| } |
| |
| void SetSubtitle(int subtitle_id, |
| std::optional<ToU16String> subtitle_param = std::nullopt, |
| std::optional<base::RepeatingClosure> |
| subtitle_link_callback = std::nullopt) { |
| if (subtitle_param.has_value()) { |
| size_t offset; |
| subtitle_label_->SetText(l10n_util::GetStringFUTF16( |
| subtitle_id, subtitle_param->get(), &offset)); |
| if (subtitle_link_callback.has_value()) { |
| subtitle_label_->AddStyleRange( |
| gfx::Range(offset, offset + subtitle_param->get().length()), |
| views::StyledLabel::RangeStyleInfo::CreateForLink( |
| *subtitle_link_callback)); |
| } |
| } else { |
| subtitle_label_->SetText(l10n_util::GetStringUTF16(subtitle_id)); |
| } |
| } |
| |
| template <typename T> |
| T* SetContentsView(std::unique_ptr<T> contents_view) { |
| CHECK(!contents_wrapper_); |
| contents_wrapper_ = AddChildView(std::make_unique<views::BoxLayoutView>()); |
| contents_wrapper_->SetOrientation(views::BoxLayout::Orientation::kVertical); |
| contents_wrapper_->SetMainAxisAlignment( |
| views::BoxLayout::MainAxisAlignment::kCenter); |
| contents_wrapper_->SetProperty( |
| views::kMarginsKey, |
| gfx::Insets::VH(ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_UNRELATED_CONTROL_VERTICAL), |
| 0)); |
| SetFlexForView(contents_wrapper_, 1); |
| return contents_wrapper_->AddChildView(std::move(contents_view)); |
| } |
| |
| private: |
| raw_ptr<views::ImageView> icon_; |
| raw_ptr<views::StyledLabel> title_label_; |
| raw_ptr<views::StyledLabel> subtitle_label_; |
| raw_ptr<views::BoxLayoutView> contents_wrapper_; |
| }; |
| |
| BEGIN_METADATA(InstallerDialogView) |
| END_METADATA |
| |
| } // namespace |
| |
| class DisabledView : public InstallerDialogView { |
| METADATA_HEADER(DisabledView, InstallerDialogView) |
| |
| public: |
| explicit DisabledView(IsolatedWebAppInstallerView::Delegate* delegate) |
| : InstallerDialogView( |
| CreateImageModelFromVector(vector_icons::kErrorOutlineIcon, |
| ui::kColorAlertMediumSeverityIcon), |
| IDS_IWA_INSTALLER_DISABLED_TITLE, |
| IDS_IWA_INSTALLER_DISABLED_SUBTITLE, |
| IDS_IWA_INSTALLER_DISABLED_CHANGE_PREFERENCE, |
| base::BindRepeating( |
| &IsolatedWebAppInstallerView::Delegate::OnSettingsLinkClicked, |
| base::Unretained(delegate))) {} |
| }; |
| |
| BEGIN_METADATA(DisabledView) |
| END_METADATA |
| |
| class GetMetadataView : public InstallerDialogView { |
| METADATA_HEADER(GetMetadataView, InstallerDialogView) |
| |
| public: |
| GetMetadataView() |
| : InstallerDialogView( |
| CreateImageModelFromVector(kFingerprintIcon, ui::kColorAccent), |
| IDS_IWA_INSTALLER_VERIFICATION_TITLE, |
| IDS_IWA_INSTALLER_VERIFICATION_SUBTITLE) { |
| auto progress_bar = |
| std::make_unique<AnnotatedProgressBar>(l10n_util::GetPluralStringFUTF16( |
| IDS_IWA_INSTALLER_VERIFICATION_STATUS, 0)); |
| progress_bar_ = SetContentsView(std::move(progress_bar)); |
| } |
| |
| void UpdateProgress(double percent) { |
| progress_bar_->UpdateProgress(percent); |
| } |
| |
| private: |
| raw_ptr<AnnotatedProgressBar> progress_bar_; |
| }; |
| |
| BEGIN_METADATA(GetMetadataView) |
| END_METADATA |
| |
| class ShowMetadataView : public InstallerDialogView { |
| METADATA_HEADER(ShowMetadataView, InstallerDialogView) |
| |
| public: |
| explicit ShowMetadataView(IsolatedWebAppInstallerView::Delegate* delegate) |
| : InstallerDialogView( |
| CreateImageModelFromVector(kFingerprintIcon, ui::kColorAccent), |
| // The title will be updated to the app name when available. |
| IDS_IWA_INSTALLER_VERIFICATION_TITLE, |
| IDS_IWA_INSTALLER_SHOW_METADATA_SUBTITLE, |
| IDS_IWA_INSTALLER_SHOW_METADATA_MANAGE_PROFILES, |
| base::BindRepeating(&IsolatedWebAppInstallerView::Delegate:: |
| OnManageProfilesLinkClicked, |
| base::Unretained(delegate))) { |
| // Initialize the View with dummy data so the initial height calculation |
| // will be roughly accurate. |
| std::vector<std::pair<int, std::u16string>> info = { |
| {IDS_IWA_INSTALLER_SHOW_METADATA_APP_NAME_LABEL, u""}, |
| {IDS_IWA_INSTALLER_SHOW_METADATA_APP_VERSION_LABEL, u""}, |
| }; |
| info_pane_ = SetContentsView(std::make_unique<InfoPane>(info)); |
| } |
| |
| void UpdateInfoPaneContents( |
| const std::vector<std::pair<int, std::u16string>> data) { |
| info_pane_->SetData(data); |
| } |
| |
| private: |
| raw_ptr<InfoPane> info_pane_; |
| }; |
| |
| BEGIN_METADATA(ShowMetadataView) |
| END_METADATA |
| |
| class InstallView : public InstallerDialogView { |
| METADATA_HEADER(InstallView, InstallerDialogView) |
| |
| public: |
| InstallView() |
| : InstallerDialogView( |
| CreateImageModelFromVector(kFingerprintIcon, ui::kColorAccent), |
| // The title will be updated to the app name when available. |
| IDS_IWA_INSTALLER_VERIFICATION_TITLE, |
| IDS_IWA_INSTALLER_INSTALL_SUBTITLE) { |
| auto progress_bar = std::make_unique<AnnotatedProgressBar>( |
| l10n_util::GetStringUTF16(IDS_IWA_INSTALLER_INSTALL_PROGRESS)); |
| progress_bar_ = SetContentsView(std::move(progress_bar)); |
| } |
| |
| void UpdateProgress(double percent) { |
| progress_bar_->UpdateProgress(percent); |
| } |
| |
| private: |
| raw_ptr<AnnotatedProgressBar> progress_bar_; |
| }; |
| |
| BEGIN_METADATA(InstallView) |
| END_METADATA |
| |
| class InstallSuccessView : public InstallerDialogView { |
| METADATA_HEADER(InstallSuccessView, InstallerDialogView) |
| |
| public: |
| InstallSuccessView() |
| : InstallerDialogView( |
| CreateImageModelFromVector(kFingerprintIcon, ui::kColorAccent), |
| // The title will be updated to the app name when available. |
| IDS_IWA_INSTALLER_VERIFICATION_TITLE, |
| IDS_IWA_INSTALLER_SUCCESS_SUBTITLE) {} |
| }; |
| |
| BEGIN_METADATA(InstallSuccessView) |
| END_METADATA |
| |
| // static |
| void IsolatedWebAppInstallerView::SetDialogButtons( |
| views::DialogDelegate* dialog_delegate, |
| int close_button_label_id, |
| std::optional<int> accept_button_label_id) { |
| if (!dialog_delegate) { |
| return; |
| } |
| |
| int buttons = ui::DIALOG_BUTTON_CANCEL; |
| dialog_delegate->SetButtonLabel( |
| ui::DIALOG_BUTTON_CANCEL, |
| l10n_util::GetStringUTF16(close_button_label_id)); |
| if (accept_button_label_id.has_value()) { |
| buttons |= ui::DIALOG_BUTTON_OK; |
| dialog_delegate->SetButtonLabel( |
| ui::DIALOG_BUTTON_OK, |
| l10n_util::GetStringUTF16(accept_button_label_id.value())); |
| } |
| dialog_delegate->SetButtons(buttons); |
| } |
| |
| // static |
| std::unique_ptr<IsolatedWebAppInstallerView> |
| IsolatedWebAppInstallerView::Create(Delegate* delegate) { |
| return std::make_unique<IsolatedWebAppInstallerViewImpl>(delegate); |
| } |
| |
| IsolatedWebAppInstallerViewImpl::IsolatedWebAppInstallerViewImpl( |
| Delegate* delegate) |
| : delegate_(delegate), |
| disabled_view_(MakeAndAddChildView<DisabledView>(delegate)), |
| get_metadata_view_(MakeAndAddChildView<GetMetadataView>()), |
| show_metadata_view_(MakeAndAddChildView<ShowMetadataView>(delegate)), |
| install_view_(MakeAndAddChildView<InstallView>()), |
| install_success_view_(MakeAndAddChildView<InstallSuccessView>()) { |
| SetUseDefaultFillLayout(true); |
| ShowChildView(nullptr); |
| } |
| |
| IsolatedWebAppInstallerViewImpl::~IsolatedWebAppInstallerViewImpl() = default; |
| |
| void IsolatedWebAppInstallerViewImpl::ShowDisabledScreen() { |
| ShowChildView(disabled_view_); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowGetMetadataScreen() { |
| ShowChildView(get_metadata_view_); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::UpdateGetMetadataProgress( |
| double percent) { |
| get_metadata_view_->UpdateProgress(percent); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowMetadataScreen( |
| const SignedWebBundleMetadata& bundle_metadata) { |
| std::vector<std::pair<int, std::u16string>> data = { |
| {IDS_IWA_INSTALLER_SHOW_METADATA_APP_NAME_LABEL, |
| bundle_metadata.app_name()}, |
| {IDS_IWA_INSTALLER_SHOW_METADATA_APP_VERSION_LABEL, |
| base::UTF8ToUTF16(bundle_metadata.version().GetString())}, |
| }; |
| show_metadata_view_->UpdateInfoPaneContents(data); |
| show_metadata_view_->SetTitle(bundle_metadata.app_name()); |
| show_metadata_view_->SetIcon( |
| CreateImageModelFromBundleMetadata(bundle_metadata)); |
| ShowChildView(show_metadata_view_); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowInstallScreen( |
| const SignedWebBundleMetadata& bundle_metadata) { |
| install_view_->SetTitle(bundle_metadata.app_name()); |
| install_view_->SetIcon(CreateImageModelFromBundleMetadata(bundle_metadata)); |
| ShowChildView(install_view_); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::UpdateInstallProgress(double percent) { |
| install_view_->UpdateProgress(percent); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowInstallSuccessScreen( |
| const SignedWebBundleMetadata& bundle_metadata) { |
| install_success_view_->SetTitle(bundle_metadata.app_name()); |
| install_success_view_->SetSubtitle(IDS_IWA_INSTALLER_SUCCESS_SUBTITLE, |
| bundle_metadata.app_name()); |
| install_success_view_->SetIcon( |
| CreateImageModelFromBundleMetadata(bundle_metadata)); |
| ShowChildView(install_success_view_); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowDialog( |
| const IsolatedWebAppInstallerModel::Dialog& dialog) { |
| absl::visit( |
| base::Overloaded{ |
| [this](const IsolatedWebAppInstallerModel::BundleInvalidDialog&) { |
| ShowDialog( |
| IDS_IWA_INSTALLER_VERIFICATION_ERROR_TITLE, |
| ui::DialogModelLabel( |
| IDS_IWA_INSTALLER_VERIFICATION_ERROR_SUBTITLE), |
| CreateImageModelFromVector(vector_icons::kErrorOutlineIcon, |
| ui::kColorAlertMediumSeverityIcon), |
| /*ok_label=*/std::nullopt); |
| }, |
| [this]( |
| const IsolatedWebAppInstallerModel::BundleAlreadyInstalledDialog& |
| already_installed_dialog) { |
| std::string installed_version = |
| already_installed_dialog.installed_version.GetString(); |
| auto subtitle = ui::DialogModelLabel::CreateWithReplacements( |
| IDS_IWA_INSTALLER_ALREADY_INSTALLED_SUBTITLE, |
| { |
| ui::DialogModelLabel::CreatePlainText( |
| already_installed_dialog.bundle_name), |
| ui::DialogModelLabel::CreatePlainText( |
| base::UTF8ToUTF16(installed_version)), |
| }); |
| ShowDialog( |
| IDS_IWA_INSTALLER_ALREADY_INSTALLED_TITLE, subtitle, |
| CreateImageModelFromVector(vector_icons::kErrorOutlineIcon, |
| ui::kColorAlertMediumSeverityIcon), |
| /*ok_label=*/std::nullopt); |
| }, |
| [this](const IsolatedWebAppInstallerModel::BundleOutdatedDialog& |
| bundle_outdated_dialog) { |
| std::string installed_version = |
| bundle_outdated_dialog.installed_version.GetString(); |
| auto subtitle = ui::DialogModelLabel::CreateWithReplacements( |
| IDS_IWA_INSTALLER_BUNDLE_OUTDATED_SUBTITLE, |
| { |
| ui::DialogModelLabel::CreatePlainText( |
| bundle_outdated_dialog.bundle_name), |
| ui::DialogModelLabel::CreatePlainText( |
| base::UTF8ToUTF16(installed_version)), |
| }); |
| ShowDialog( |
| IDS_IWA_INSTALLER_BUNDLE_OUTDATED_TITLE, subtitle, |
| CreateImageModelFromVector(vector_icons::kErrorOutlineIcon, |
| ui::kColorAlertMediumSeverityIcon), |
| /*ok_label=*/std::nullopt); |
| }, |
| [this](const IsolatedWebAppInstallerModel::ConfirmInstallationDialog& |
| confirm_installation_dialog) { |
| auto subtitle = ui::DialogModelLabel::CreateWithReplacement( |
| IDS_IWA_INSTALLER_CONFIRM_SUBTITLE, |
| ui::DialogModelLabel::CreateLink( |
| IDS_IWA_INSTALLER_CONFIRM_LEARN_MORE, |
| confirm_installation_dialog.learn_more_callback)); |
| ShowDialog( |
| IDS_IWA_INSTALLER_CONFIRM_TITLE, subtitle, |
| CreateImageModelFromVector(kSecurityIcon, ui::kColorAccent), |
| IDS_IWA_INSTALLER_CONFIRM_CONTINUE); |
| }, |
| [this]( |
| const IsolatedWebAppInstallerModel::InstallationFailedDialog&) { |
| ShowDialog( |
| IDS_IWA_INSTALLER_INSTALL_FAILED_TITLE, |
| ui::DialogModelLabel(IDS_IWA_INSTALLER_INSTALL_FAILED_SUBTITLE), |
| CreateImageModelFromVector(vector_icons::kErrorOutlineIcon, |
| ui::kColorAlertMediumSeverityIcon), |
| IDS_IWA_INSTALLER_INSTALL_FAILED_RETRY); |
| }}, |
| dialog); |
| } |
| |
| gfx::Size IsolatedWebAppInstallerViewImpl::GetMaximumSize() const { |
| // `SetCanResize` only works in ash. ash will consider Lacros windows to be |
| // non-resizable if their min and max height are the same. To achieve this, |
| // we set the max size to the View's preferred size. |
| int width = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH); |
| return gfx::Size(width, GetHeightForWidth(width)); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowDialog( |
| int title, |
| const ui::DialogModelLabel& subtitle, |
| const ui::ImageModel& icon_model, |
| std::optional<int> ok_label) { |
| ui::DialogModel::Builder dialog_model_builder; |
| dialog_model_builder |
| .SetInternalName(IsolatedWebAppInstallerView::kNestedDialogWidgetName) |
| .SetTitle(l10n_util::GetStringUTF16(title)) |
| .AddParagraph(ui::DialogModelLabel(subtitle).set_is_secondary()) |
| .DisableCloseOnDeactivate() |
| .AddCancelButton(base::BindOnce(&Delegate::OnChildDialogCanceled, |
| base::Unretained(delegate_))); |
| if (ok_label.has_value()) { |
| dialog_model_builder.AddOkButton( |
| base::BindOnce(&Delegate::OnChildDialogAccepted, |
| base::Unretained(delegate_)), |
| ui::DialogModel::Button::Params().SetLabel( |
| l10n_util::GetStringUTF16(ok_label.value()))); |
| } |
| |
| // None of DialogModel's banner image, icon, or main image display the icon |
| // the way we want, so we have to manually create a header View that |
| // positions the icon correctly. |
| auto header = std::make_unique<views::BoxLayoutView>(); |
| int inset = ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_UNRELATED_CONTROL_VERTICAL); |
| header->SetInsideBorderInsets(gfx::Insets::TLBR(inset, inset, 0, inset)); |
| auto* icon = header->AddChildView(std::make_unique<NonAccessibleImageView>()); |
| icon->SetHorizontalAlignment(views::ImageView::Alignment::kLeading); |
| icon->SetImageSize(gfx::Size(kNestedDialogIconSize, kNestedDialogIconSize)); |
| icon->SetImage(icon_model); |
| |
| std::unique_ptr<views::BubbleDialogModelHost> bubble = |
| views::BubbleDialogModelHost::CreateModal(dialog_model_builder.Build(), |
| ui::MODAL_TYPE_CHILD); |
| bubble->SetAnchorView(GetWidget()->GetContentsView()); |
| bubble->SetArrow(views::BubbleBorder::FLOAT); |
| bubble->set_fixed_width(ChromeLayoutProvider::Get()->GetDistanceMetric( |
| views::DISTANCE_BUBBLE_PREFERRED_WIDTH)); |
| bubble->RegisterWidgetInitializedCallback(base::BindOnce( |
| [](views::BubbleDialogModelHost* bubble, |
| std::unique_ptr<views::View> header) { |
| bubble->GetBubbleFrameView()->SetHeaderView(std::move(header)); |
| }, |
| // base::Unretained is safe here because the callback is invoked when |
| // `bubble` is initialized, at which point it must still be alive. |
| base::Unretained(bubble.get()), std::move(header))); |
| |
| views::BubbleDialogDelegate::CreateBubble(std::move(bubble))->Show(); |
| } |
| |
| void IsolatedWebAppInstallerViewImpl::ShowChildView(views::View* view) { |
| for (views::View* child : children()) { |
| child->SetVisible(child == view); |
| } |
| } |
| |
| BEGIN_METADATA(IsolatedWebAppInstallerViewImpl) |
| END_METADATA |
| |
| } // namespace web_app |