1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
6
7 #include <utility>
8
9 #include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
10 #include "chrome/browser/ui/views/chrome_layout_provider.h"
11 #include "chrome/browser/ui/views/chrome_typography.h"
12 #include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
13 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
14 #include "chrome/grit/generated_resources.h"
15 #include "components/strings/grit/components_strings.h"
16 #include "components/vector_icons/vector_icons.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/gfx/color_utils.h"
19 #include "ui/gfx/paint_vector_icon.h"
20 #include "ui/native_theme/native_theme.h"
21 #include "ui/views/controls/button/image_button.h"
22 #include "ui/views/controls/button/image_button_factory.h"
23 #include "ui/views/controls/label.h"
24 #include "ui/views/controls/progress_bar.h"
25 #include "ui/views/layout/box_layout.h"
26 #include "ui/views/layout/fill_layout.h"
27
28 namespace {
29
30 // Fixed height of the illustration shown in the top half of the sheet.
31 constexpr int kIllustrationHeight = 148;
32
33 // Height of the progress bar style activity indicator shown at the top of some
34 // sheets.
35 constexpr int kActivityIndicatorHeight = 4;
36
37 using ImageColorScheme = AuthenticatorRequestSheetModel::ImageColorScheme;
38
39 } // namespace
40
41 using views::BoxLayout;
42
AuthenticatorRequestSheetView(std::unique_ptr<AuthenticatorRequestSheetModel> model)43 AuthenticatorRequestSheetView::AuthenticatorRequestSheetView(
44 std::unique_ptr<AuthenticatorRequestSheetModel> model)
45 : model_(std::move(model)) {}
46
47 AuthenticatorRequestSheetView::~AuthenticatorRequestSheetView() = default;
48
ReInitChildViews()49 void AuthenticatorRequestSheetView::ReInitChildViews() {
50 RemoveAllChildViews(true /* delete_children */);
51
52 // No need to add further spacing between the upper and lower half. The image
53 // is designed to fill the dialog's top half without any border/margins, and
54 // the |lower_half| will already contain the standard dialog borders.
55 SetLayoutManager(std::make_unique<BoxLayout>(
56 BoxLayout::Orientation::kVertical, gfx::Insets(),
57 0 /* between_child_spacing */));
58
59 std::unique_ptr<views::View> upper_half = CreateIllustrationWithOverlays();
60 std::unique_ptr<views::View> lower_half = CreateContentsBelowIllustration();
61 AddChildView(upper_half.release());
62 AddChildView(lower_half.release());
63 InvalidateLayout();
64 }
65
GetInitiallyFocusedView()66 views::View* AuthenticatorRequestSheetView::GetInitiallyFocusedView() {
67 return step_specific_content_;
68 }
69
70 std::unique_ptr<views::View>
BuildStepSpecificContent()71 AuthenticatorRequestSheetView::BuildStepSpecificContent() {
72 return nullptr;
73 }
74
75 std::unique_ptr<views::View>
CreateIllustrationWithOverlays()76 AuthenticatorRequestSheetView::CreateIllustrationWithOverlays() {
77 const int illustration_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
78 views::DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH);
79 const gfx::Size illustration_size(illustration_width, kIllustrationHeight);
80
81 // The container view has no layout, so its preferred size is hardcoded to
82 // match the size of the image, and all overlays are absolutely positioned.
83 auto image_with_overlays = std::make_unique<views::View>();
84 image_with_overlays->SetPreferredSize(illustration_size);
85
86 auto image_view = std::make_unique<NonAccessibleImageView>();
87 step_illustration_ = image_view.get();
88 UpdateIconImageFromModel();
89 image_view->SetSize(illustration_size);
90 image_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
91 image_with_overlays->AddChildView(image_view.release());
92
93 if (model()->IsActivityIndicatorVisible()) {
94 auto activity_indicator = std::make_unique<views::ProgressBar>(
95 kActivityIndicatorHeight, false /* allow_round_corner */);
96 activity_indicator->SetValue(-1 /* inifinite animation */);
97 activity_indicator->SetBackgroundColor(SK_ColorTRANSPARENT);
98 activity_indicator->SetPreferredSize(
99 gfx::Size(illustration_width, kActivityIndicatorHeight));
100 activity_indicator->SizeToPreferredSize();
101 image_with_overlays->AddChildView(activity_indicator.release());
102 }
103
104 if (model()->IsBackButtonVisible()) {
105 auto back_arrow = views::CreateVectorImageButton(base::BindRepeating(
106 &AuthenticatorRequestSheetModel::OnBack, base::Unretained(model())));
107 back_arrow->SetAccessibleName(l10n_util::GetStringUTF16(
108 IDS_BACK_BUTTON_AUTHENTICATOR_REQUEST_DIALOG));
109
110 // Position the back button so that there is the standard amount of padding
111 // between the top/left side of the back button and the dialog borders.
112 const gfx::Insets dialog_insets =
113 views::LayoutProvider::Get()->GetDialogInsetsForContentType(
114 views::CONTROL, views::CONTROL);
115 auto color_reference = std::make_unique<views::Label>(
116 base::string16(), views::style::CONTEXT_DIALOG_TITLE,
117 views::style::STYLE_PRIMARY);
118 back_arrow->SizeToPreferredSize();
119 back_arrow->SetX(dialog_insets.left());
120 back_arrow->SetY(dialog_insets.top());
121 back_arrow_ = back_arrow.get();
122 back_arrow_button_ =
123 image_with_overlays->AddChildView(std::move(back_arrow));
124 UpdateIconColors();
125 }
126
127 return image_with_overlays;
128 }
129
130 std::unique_ptr<views::View>
CreateContentsBelowIllustration()131 AuthenticatorRequestSheetView::CreateContentsBelowIllustration() {
132 auto contents = std::make_unique<views::View>();
133 BoxLayout* contents_layout =
134 contents->SetLayoutManager(std::make_unique<BoxLayout>(
135 BoxLayout::Orientation::kVertical, gfx::Insets(),
136 views::LayoutProvider::Get()->GetDistanceMetric(
137 views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
138
139 contents->SetBorder(views::CreateEmptyBorder(
140 views::LayoutProvider::Get()->GetDialogInsetsForContentType(
141 views::CONTROL, views::CONTROL)));
142
143 auto label_container = std::make_unique<views::View>();
144 label_container->SetLayoutManager(std::make_unique<BoxLayout>(
145 BoxLayout::Orientation::kVertical, gfx::Insets(),
146 views::LayoutProvider::Get()->GetDistanceMetric(
147 views::DISTANCE_RELATED_CONTROL_VERTICAL)));
148
149 auto title_label = std::make_unique<views::Label>(
150 model()->GetStepTitle(), views::style::CONTEXT_DIALOG_TITLE,
151 views::style::STYLE_PRIMARY);
152 title_label->SetMultiLine(true);
153 title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
154 title_label->SetAllowCharacterBreak(true);
155 label_container->AddChildView(title_label.release());
156
157 base::string16 description = model()->GetStepDescription();
158 if (!description.empty()) {
159 auto description_label = std::make_unique<views::Label>(
160 std::move(description), views::style::CONTEXT_DIALOG_BODY_TEXT);
161 description_label->SetMultiLine(true);
162 description_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
163 description_label->SetAllowCharacterBreak(true);
164 label_container->AddChildView(description_label.release());
165 }
166
167 base::string16 additional_desciption = model()->GetAdditionalDescription();
168 if (!additional_desciption.empty()) {
169 auto label =
170 std::make_unique<views::Label>(std::move(additional_desciption),
171 views::style::CONTEXT_DIALOG_BODY_TEXT);
172 label->SetMultiLine(true);
173 label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
174 label->SetAllowCharacterBreak(true);
175 label_container->AddChildView(label.release());
176 }
177
178 contents->AddChildView(label_container.release());
179
180 std::unique_ptr<views::View> step_specific_content =
181 BuildStepSpecificContent();
182 if (step_specific_content) {
183 step_specific_content_ = step_specific_content.get();
184 contents->AddChildView(step_specific_content.release());
185 contents_layout->SetFlexForView(step_specific_content_, 1);
186 }
187
188 base::string16 error = model()->GetError();
189 if (!error.empty()) {
190 auto error_label = std::make_unique<views::Label>(
191 std::move(error), views::style::CONTEXT_LABEL, STYLE_RED);
192 error_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
193 error_label->SetMultiLine(true);
194 error_label_ = contents->AddChildView(std::move(error_label));
195 }
196
197 return contents;
198 }
199
OnThemeChanged()200 void AuthenticatorRequestSheetView::OnThemeChanged() {
201 views::View::OnThemeChanged();
202 UpdateIconImageFromModel();
203 UpdateIconColors();
204 }
205
UpdateIconImageFromModel()206 void AuthenticatorRequestSheetView::UpdateIconImageFromModel() {
207 if (!step_illustration_)
208 return;
209
210 gfx::IconDescription icon_description(model()->GetStepIllustration(
211 GetNativeTheme()->ShouldUseDarkColors() ? ImageColorScheme::kDark
212 : ImageColorScheme::kLight));
213 step_illustration_->SetImage(gfx::CreateVectorIcon(icon_description));
214 }
215
UpdateIconColors()216 void AuthenticatorRequestSheetView::UpdateIconColors() {
217 if (back_arrow_) {
218 views::SetImageFromVectorIcon(
219 back_arrow_, vector_icons::kBackArrowIcon,
220 color_utils::DeriveDefaultIconColor(views::style::GetColor(
221 *this, views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY)));
222 }
223 }
224