Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Side by Side Diff: chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc

Issue 2883333003: Payment request shipping address editor now properly handles countries (Closed)
Patch Set: Rebase Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/views/payments/shipping_address_editor_view_controll er.h" 5 #include "chrome/browser/ui/views/payments/shipping_address_editor_view_controll er.h"
6 6
7 #include <utility>
8
9 #include "base/bind.h" 7 #include "base/bind.h"
10 #include "base/bind_helpers.h" 8 #include "base/bind_helpers.h"
11 #include "base/callback.h" 9 #include "base/callback.h"
12 #include "base/memory/ptr_util.h" 10 #include "base/memory/ptr_util.h"
13 #include "base/strings/utf_string_conversions.h" 11 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/thread_task_runner_handle.h" 12 #include "base/threading/thread_task_runner_handle.h"
15 #include "chrome/browser/ui/views/payments/payment_request_dialog_view.h" 13 #include "chrome/browser/ui/views/payments/payment_request_dialog_view.h"
16 #include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h" 14 #include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h"
17 #include "chrome/browser/ui/views/payments/validating_combobox.h" 15 #include "chrome/browser/ui/views/payments/validating_combobox.h"
18 #include "chrome/browser/ui/views/payments/validating_textfield.h" 16 #include "chrome/browser/ui/views/payments/validating_textfield.h"
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
56 if (type == autofill::kPostalCodeField) 54 if (type == autofill::kPostalCodeField)
57 return autofill::ADDRESS_HOME_ZIP; 55 return autofill::ADDRESS_HOME_ZIP;
58 if (type == autofill::kSortingCodeField) 56 if (type == autofill::kSortingCodeField)
59 return autofill::ADDRESS_HOME_SORTING_CODE; 57 return autofill::ADDRESS_HOME_SORTING_CODE;
60 if (type == autofill::kCountryField) 58 if (type == autofill::kCountryField)
61 return autofill::ADDRESS_HOME_COUNTRY; 59 return autofill::ADDRESS_HOME_COUNTRY;
62 NOTREACHED(); 60 NOTREACHED();
63 return autofill::UNKNOWN_TYPE; 61 return autofill::UNKNOWN_TYPE;
64 } 62 }
65 63
64 const size_t kInvalidCountryIndex = static_cast<size_t>(-1);
Mathieu 2017/05/17 19:18:30 isn't size_t unsigned? What happens here?
MAD 2017/05/17 19:54:29 It created a SIZE_MAX value without having to incl
65
66 } // namespace 66 } // namespace
67 67
68 ShippingAddressEditorViewController::ShippingAddressEditorViewController( 68 ShippingAddressEditorViewController::ShippingAddressEditorViewController(
69 PaymentRequestSpec* spec, 69 PaymentRequestSpec* spec,
70 PaymentRequestState* state, 70 PaymentRequestState* state,
71 PaymentRequestDialogView* dialog, 71 PaymentRequestDialogView* dialog,
72 BackNavigationType back_navigation_type, 72 BackNavigationType back_navigation_type,
73 base::OnceClosure on_edited, 73 base::OnceClosure on_edited,
74 base::OnceCallback<void(const autofill::AutofillProfile&)> on_added, 74 base::OnceCallback<void(const autofill::AutofillProfile&)> on_added,
75 autofill::AutofillProfile* profile) 75 autofill::AutofillProfile* profile)
76 : EditorViewController(spec, state, dialog, back_navigation_type), 76 : EditorViewController(spec, state, dialog, back_navigation_type),
77 on_edited_(std::move(on_edited)), 77 on_edited_(std::move(on_edited)),
78 on_added_(std::move(on_added)), 78 on_added_(std::move(on_added)),
79 profile_to_edit_(profile), 79 profile_to_edit_(profile),
80 chosen_country_index_(0), 80 chosen_country_index_(kInvalidCountryIndex),
81 failed_to_load_region_data_(false) { 81 failed_to_load_region_data_(false) {
82 UpdateCountries(nullptr);
Mathieu 2017/05/17 19:18:30 nit: /*model=*/nullptr
MAD 2017/05/17 19:54:29 Done.
82 UpdateEditorFields(); 83 UpdateEditorFields();
83 } 84 }
84 85
85 ShippingAddressEditorViewController::~ShippingAddressEditorViewController() {} 86 ShippingAddressEditorViewController::~ShippingAddressEditorViewController() {}
86 87
87 std::vector<EditorField> 88 std::vector<EditorField>
88 ShippingAddressEditorViewController::GetFieldDefinitions() { 89 ShippingAddressEditorViewController::GetFieldDefinitions() {
89 return editor_fields_; 90 return editor_fields_;
90 } 91 }
91 92
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 std::unique_ptr<ui::ComboboxModel> 145 std::unique_ptr<ui::ComboboxModel>
145 ShippingAddressEditorViewController::GetComboboxModelForType( 146 ShippingAddressEditorViewController::GetComboboxModelForType(
146 const autofill::ServerFieldType& type) { 147 const autofill::ServerFieldType& type) {
147 switch (type) { 148 switch (type) {
148 case autofill::ADDRESS_HOME_COUNTRY: { 149 case autofill::ADDRESS_HOME_COUNTRY: {
149 std::unique_ptr<autofill::CountryComboboxModel> model = 150 std::unique_ptr<autofill::CountryComboboxModel> model =
150 base::MakeUnique<autofill::CountryComboboxModel>(); 151 base::MakeUnique<autofill::CountryComboboxModel>();
151 model->SetCountries(*state()->GetPersonalDataManager(), 152 model->SetCountries(*state()->GetPersonalDataManager(),
152 base::Callback<bool(const std::string&)>(), 153 base::Callback<bool(const std::string&)>(),
153 state()->GetApplicationLocale()); 154 state()->GetApplicationLocale());
154 country_codes_.clear(); 155 if (model->countries().size() != countries_.size())
155 for (size_t i = 0; i < model->countries().size(); ++i) { 156 UpdateCountries(model.get());
156 if (model->countries()[i].get())
157 country_codes_.push_back(model->countries()[i]->country_code());
158 else
159 country_codes_.push_back(""); // Separator.
160 }
161 return std::move(model); 157 return std::move(model);
162 } 158 }
163 case autofill::ADDRESS_HOME_STATE: { 159 case autofill::ADDRESS_HOME_STATE: {
164 std::unique_ptr<autofill::RegionComboboxModel> model = 160 std::unique_ptr<autofill::RegionComboboxModel> model =
165 base::MakeUnique<autofill::RegionComboboxModel>(); 161 base::MakeUnique<autofill::RegionComboboxModel>();
166 model->LoadRegionData(country_codes_[chosen_country_index_], 162 if (chosen_country_index_ < countries_.size()) {
167 state()->GetRegionDataLoader(), 163 model->LoadRegionData(countries_[chosen_country_index_].first,
168 /*timeout_ms=*/5000); 164 state()->GetRegionDataLoader(),
169 if (!model->IsPendingRegionDataLoad()) { 165 /*timeout_ms=*/5000);
170 // If the data was already pre-loaded, the observer won't get notified 166 if (!model->IsPendingRegionDataLoad()) {
171 // so we have to check for failure here. 167 // If the data was already pre-loaded, the observer won't get notified
172 failed_to_load_region_data_ = model->failed_to_load_data(); 168 // so we have to check for failure here.
173 if (failed_to_load_region_data_) { 169 failed_to_load_region_data_ = model->failed_to_load_data();
174 // We can update the view synchronously while building the view.
175 OnDataChanged(/*synchronous=*/false);
176 } 170 }
171 } else {
172 failed_to_load_region_data_ = true;
173 }
174 if (failed_to_load_region_data_) {
175 // We can't update the view synchronously while building the view.
176 OnDataChanged(/*synchronous=*/false);
177 } 177 }
178 return std::move(model); 178 return std::move(model);
179 } 179 }
180 default: 180 default:
181 NOTREACHED(); 181 NOTREACHED();
182 break; 182 break;
183 } 183 }
184 return std::unique_ptr<ui::ComboboxModel>(); 184 return std::unique_ptr<ui::ComboboxModel>();
185 } 185 }
186 186
187 void ShippingAddressEditorViewController::OnPerformAction( 187 void ShippingAddressEditorViewController::OnPerformAction(
188 views::Combobox* sender) { 188 views::Combobox* sender) {
189 EditorViewController::OnPerformAction(sender); 189 EditorViewController::OnPerformAction(sender);
190 if (sender->id() != autofill::ADDRESS_HOME_COUNTRY) 190 if (sender->id() != autofill::ADDRESS_HOME_COUNTRY)
191 return; 191 return;
192 DCHECK_GE(sender->selected_index(), 0); 192 DCHECK_GE(sender->selected_index(), 0);
193 if (chosen_country_index_ != static_cast<size_t>(sender->selected_index())) { 193 if (chosen_country_index_ != static_cast<size_t>(sender->selected_index())) {
194 chosen_country_index_ = sender->selected_index(); 194 chosen_country_index_ = sender->selected_index();
195 failed_to_load_region_data_ = false; 195 failed_to_load_region_data_ = false;
196 // View update must be asynchronous to let the combobox finish performing 196 // View update must be asynchronous to let the combobox finish performing
197 // the action. 197 // the action.
198 OnDataChanged(/*synchronous=*/false); 198 OnDataChanged(/*synchronous=*/false);
199 } 199 }
200 } 200 }
201 201
202 void ShippingAddressEditorViewController::UpdateEditorView() { 202 void ShippingAddressEditorViewController::UpdateEditorView() {
203 EditorViewController::UpdateEditorView(); 203 EditorViewController::UpdateEditorView();
204 if (chosen_country_index_ > 0UL) { 204 if (chosen_country_index_ > 0UL &&
205 chosen_country_index_ < countries_.size()) {
205 views::Combobox* country_combo_box = static_cast<views::Combobox*>( 206 views::Combobox* country_combo_box = static_cast<views::Combobox*>(
206 dialog()->GetViewByID(autofill::ADDRESS_HOME_COUNTRY)); 207 dialog()->GetViewByID(autofill::ADDRESS_HOME_COUNTRY));
207 DCHECK(country_combo_box); 208 DCHECK(country_combo_box);
209 DCHECK_EQ(countries_.size(),
210 static_cast<size_t>(country_combo_box->GetRowCount()));
208 country_combo_box->SetSelectedIndex(chosen_country_index_); 211 country_combo_box->SetSelectedIndex(chosen_country_index_);
212 } else if (countries_.size() > 0UL) {
213 chosen_country_index_ = 0UL;
214 } else {
215 chosen_country_index_ = kInvalidCountryIndex;
209 } 216 }
210 // Ignore temporary profile once the editor view has been updated. 217 // Ignore temporary profile once the editor view has been updated.
211 temporary_profile_.reset(nullptr); 218 temporary_profile_.reset(nullptr);
Mathieu 2017/05/17 19:18:30 just reset() is fine here
MAD 2017/05/17 19:54:29 Done.
212 } 219 }
213 220
214 base::string16 ShippingAddressEditorViewController::GetSheetTitle() { 221 base::string16 ShippingAddressEditorViewController::GetSheetTitle() {
215 // TODO(crbug.com/712074): Editor title should reflect the missing information 222 // TODO(crbug.com/712074): Editor title should reflect the missing information
216 // in the case that one or more fields are missing. 223 // in the case that one or more fields are missing.
217 return profile_to_edit_ ? l10n_util::GetStringUTF16(IDS_PAYMENTS_EDIT_ADDRESS) 224 return profile_to_edit_ ? l10n_util::GetStringUTF16(IDS_PAYMENTS_EDIT_ADDRESS)
218 : l10n_util::GetStringUTF16(IDS_PAYMENTS_ADD_ADDRESS); 225 : l10n_util::GetStringUTF16(IDS_PAYMENTS_ADD_ADDRESS);
219 } 226 }
220 227
221 std::unique_ptr<views::Button> 228 std::unique_ptr<views::Button>
222 ShippingAddressEditorViewController::CreatePrimaryButton() { 229 ShippingAddressEditorViewController::CreatePrimaryButton() {
223 std::unique_ptr<views::Button> button( 230 std::unique_ptr<views::Button> button(
224 EditorViewController::CreatePrimaryButton()); 231 EditorViewController::CreatePrimaryButton());
225 button->set_id(static_cast<int>(DialogViewID::SAVE_ADDRESS_BUTTON)); 232 button->set_id(static_cast<int>(DialogViewID::SAVE_ADDRESS_BUTTON));
226 return button; 233 return button;
227 } 234 }
228 235
236 void ShippingAddressEditorViewController::UpdateCountries(
237 autofill::CountryComboboxModel* model) {
238 autofill::CountryComboboxModel local_model;
239 if (!model) {
240 local_model.SetCountries(*state()->GetPersonalDataManager(),
241 base::Callback<bool(const std::string&)>(),
242 state()->GetApplicationLocale());
243 model = &local_model;
244 }
245
246 for (size_t i = 0; i < model->countries().size(); ++i) {
247 autofill::AutofillCountry* country(model->countries()[i].get());
248 if (country) {
249 countries_.push_back(
250 std::make_pair(country->country_code(), country->name()));
251 } else {
252 // Separator, kept to make sure the size of the vector stays the same.
253 countries_.push_back(std::make_pair("", base::UTF8ToUTF16("")));
254 }
255 }
256 // If there is a profile to edit, make sure to use its country for the initial
257 // |chosen_country_index_|.
258 if (profile_to_edit_) {
259 autofill::AutofillType country_type(autofill::ADDRESS_HOME_COUNTRY);
260 base::string16 chosen_country(profile_to_edit_->GetInfo(
261 country_type, state()->GetApplicationLocale()));
262 for (chosen_country_index_ = 0; chosen_country_index_ < countries_.size();
263 ++chosen_country_index_) {
264 if (chosen_country == countries_[chosen_country_index_].second)
265 break;
266 }
267 // Make sure the the country was actually found in |countries_|, otherwise
268 // set |chosen_country_index_| as the default country at index 0.
269 if (chosen_country_index_ >= countries_.size()) {
270 // But only if there is at least one country.
271 if (countries_.size() > 0) {
272 LOG(ERROR) << "Unexpected country: " << chosen_country;
273 chosen_country_index_ = 0;
274 profile_to_edit_->SetInfo(country_type,
275 countries_[chosen_country_index_].second,
276 state()->GetApplicationLocale());
277 } else {
278 LOG(ERROR) << "Unexpected empty country list!";
279 chosen_country_index_ = kInvalidCountryIndex;
280 }
281 }
282 } else if (countries_.size() > 0) {
283 chosen_country_index_ = 0;
284 }
285 }
286
229 void ShippingAddressEditorViewController::UpdateEditorFields() { 287 void ShippingAddressEditorViewController::UpdateEditorFields() {
230 editor_fields_.clear(); 288 editor_fields_.clear();
231 std::string chosen_country_code; 289 std::string chosen_country_code;
232 if (chosen_country_index_ < country_codes_.size()) 290 if (chosen_country_index_ < countries_.size())
233 chosen_country_code = country_codes_[chosen_country_index_]; 291 chosen_country_code = countries_[chosen_country_index_].first;
234 292
235 std::unique_ptr<base::ListValue> components(new base::ListValue); 293 std::unique_ptr<base::ListValue> components(new base::ListValue);
236 std::string unused; 294 std::string unused;
237 autofill::GetAddressComponents(chosen_country_code, 295 autofill::GetAddressComponents(chosen_country_code,
238 state()->GetApplicationLocale(), 296 state()->GetApplicationLocale(),
239 components.get(), &unused); 297 components.get(), &unused);
240 for (size_t line_index = 0; line_index < components->GetSize(); 298 for (size_t line_index = 0; line_index < components->GetSize();
241 ++line_index) { 299 ++line_index) {
242 const base::ListValue* line = nullptr; 300 const base::ListValue* line = nullptr;
243 if (!components->GetList(line_index, &line)) { 301 if (!components->GetList(line_index, &line)) {
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
316 FROM_HERE, 374 FROM_HERE,
317 base::BindOnce(&ShippingAddressEditorViewController::UpdateEditorView, 375 base::BindOnce(&ShippingAddressEditorViewController::UpdateEditorView,
318 base::Unretained(this))); 376 base::Unretained(this)));
319 } 377 }
320 } 378 }
321 379
322 bool ShippingAddressEditorViewController::SaveFieldsToProfile( 380 bool ShippingAddressEditorViewController::SaveFieldsToProfile(
323 autofill::AutofillProfile* profile, 381 autofill::AutofillProfile* profile,
324 bool ignore_errors) { 382 bool ignore_errors) {
325 const std::string& locale = state()->GetApplicationLocale(); 383 const std::string& locale = state()->GetApplicationLocale();
384 // The country must be set first, because the profile uses the country to
385 // interpret some of the data (e.g., phone numbers) passed to SetInfo.
386 views::Combobox* combobox = static_cast<views::Combobox*>(
387 dialog()->GetViewByID(autofill::ADDRESS_HOME_COUNTRY));
388 // The combobox can be null when saving to temporary profile while updating
389 // the view.
390 if (combobox) {
391 base::string16 country(combobox->GetTextForRow(combobox->selected_index()));
392 bool success =
393 profile->SetInfo(autofill::AutofillType(autofill::ADDRESS_HOME_COUNTRY),
394 country, locale);
395 LOG_IF(ERROR, !success && !ignore_errors)
396 << "Can't set profile country to: " << country;
397 if (!success && !ignore_errors)
398 return false;
399 } else {
400 DCHECK_EQ(temporary_profile_.get(), profile);
401 }
402
326 bool success = true; 403 bool success = true;
327 for (const auto& field : text_fields()) { 404 for (const auto& field : text_fields()) {
328 // Force a blur in case the value was left untouched. 405 // Force a blur in case the value was left untouched.
329 field.first->OnBlur(); 406 field.first->OnBlur();
330 // ValidatingTextfield* is the key, EditorField is the value. 407 // ValidatingTextfield* is the key, EditorField is the value.
331 if (field.first->invalid()) { 408 if (field.first->invalid()) {
332 success = false; 409 success = false;
333 } else { 410 } else {
334 success = profile->SetInfo(autofill::AutofillType(field.second.type), 411 success = profile->SetInfo(autofill::AutofillType(field.second.type),
335 field.first->text(), locale); 412 field.first->text(), locale);
336 } 413 }
337 LOG_IF(ERROR, !success && !ignore_errors) 414 LOG_IF(ERROR, !success && !ignore_errors)
338 << "Can't setinfo(" << field.second.type << ", " << field.first->text(); 415 << "Can't setinfo(" << field.second.type << ", " << field.first->text();
339 if (!success && !ignore_errors) 416 if (!success && !ignore_errors)
340 return false; 417 return false;
341 } 418 }
342 for (const auto& field : comboboxes()) { 419 for (const auto& field : comboboxes()) {
343 // ValidatingCombobox* is the key, EditorField is the value. 420 // ValidatingCombobox* is the key, EditorField is the value.
344 ValidatingCombobox* combobox = field.first; 421 ValidatingCombobox* combobox = field.first;
422 // The country has already been dealt with.
423 if (combobox->id() == autofill::ADDRESS_HOME_COUNTRY)
424 continue;
345 if (combobox->invalid()) { 425 if (combobox->invalid()) {
346 success = false; 426 success = false;
347 } else { 427 } else {
348 if (combobox->id() == autofill::ADDRESS_HOME_COUNTRY) { 428 success = profile->SetInfo(
349 success = profile->SetInfo( 429 autofill::AutofillType(field.second.type),
350 autofill::AutofillType(field.second.type), 430 combobox->GetTextForRow(combobox->selected_index()), locale);
351 base::UTF8ToUTF16(country_codes_[combobox->selected_index()]),
352 locale);
353 } else {
354 success = profile->SetInfo(
355 autofill::AutofillType(field.second.type),
356 combobox->GetTextForRow(combobox->selected_index()), locale);
357 }
358 } 431 }
359 LOG_IF(ERROR, !success && !ignore_errors) 432 LOG_IF(ERROR, !success && !ignore_errors)
360 << "Can't setinfo(" << field.second.type << ", " 433 << "Can't setinfo(" << field.second.type << ", "
361 << combobox->GetTextForRow(combobox->selected_index()); 434 << combobox->GetTextForRow(combobox->selected_index());
362 if (!success && !ignore_errors) 435 if (!success && !ignore_errors)
363 return false; 436 return false;
364 } 437 }
365 return success; 438 return success;
366 } 439 }
367 440
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
407 480
408 void ShippingAddressEditorViewController::ShippingAddressValidationDelegate:: 481 void ShippingAddressEditorViewController::ShippingAddressValidationDelegate::
409 ComboboxModelChanged(views::Combobox* combobox) { 482 ComboboxModelChanged(views::Combobox* combobox) {
410 controller_->OnComboboxModelChanged(combobox); 483 controller_->OnComboboxModelChanged(combobox);
411 } 484 }
412 485
413 bool ShippingAddressEditorViewController::ShippingAddressValidationDelegate:: 486 bool ShippingAddressEditorViewController::ShippingAddressValidationDelegate::
414 ValidateValue(const base::string16& value) { 487 ValidateValue(const base::string16& value) {
415 if (!value.empty()) { 488 if (!value.empty()) {
416 if (field_.type == autofill::PHONE_HOME_WHOLE_NUMBER && 489 if (field_.type == autofill::PHONE_HOME_WHOLE_NUMBER &&
490 controller_->chosen_country_index_ < controller_->countries_.size() &&
417 !autofill::IsValidPhoneNumber( 491 !autofill::IsValidPhoneNumber(
418 value, 492 value, controller_->countries_[controller_->chosen_country_index_]
419 controller_->country_codes_[controller_->chosen_country_index_])) { 493 .first)) {
420 controller_->DisplayErrorMessageForField( 494 controller_->DisplayErrorMessageForField(
421 field_, l10n_util::GetStringUTF16( 495 field_, l10n_util::GetStringUTF16(
422 IDS_PAYMENTS_PHONE_INVALID_VALIDATION_MESSAGE)); 496 IDS_PAYMENTS_PHONE_INVALID_VALIDATION_MESSAGE));
423 return false; 497 return false;
424 } 498 }
425 // As long as other field types are non-empty, they are valid. 499 // As long as other field types are non-empty, they are valid.
426 controller_->DisplayErrorMessageForField(field_, base::ASCIIToUTF16("")); 500 controller_->DisplayErrorMessageForField(field_, base::ASCIIToUTF16(""));
427 return true; 501 return true;
428 } 502 }
429 bool is_required_valid = !field_.required; 503 bool is_required_valid = !field_.required;
430 const base::string16 displayed_message = 504 const base::string16 displayed_message =
431 is_required_valid ? base::ASCIIToUTF16("") 505 is_required_valid ? base::ASCIIToUTF16("")
432 : l10n_util::GetStringUTF16( 506 : l10n_util::GetStringUTF16(
433 IDS_PAYMENTS_FIELD_REQUIRED_VALIDATION_MESSAGE); 507 IDS_PAYMENTS_FIELD_REQUIRED_VALIDATION_MESSAGE);
434 controller_->DisplayErrorMessageForField(field_, displayed_message); 508 controller_->DisplayErrorMessageForField(field_, displayed_message);
435 return is_required_valid; 509 return is_required_valid;
436 } 510 }
437 511
438 } // namespace payments 512 } // namespace payments
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698