1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "payments/ui/payments_edit_information.h"
9 
10 #include "payments/ui/payments_panel_delegate.h"
11 #include "payments/ui/payments_field.h"
12 #include "ui/widgets/scroll_area.h"
13 #include "ui/widgets/buttons.h"
14 #include "ui/widgets/labels.h"
15 #include "ui/widgets/checkbox.h"
16 #include "ui/wrap/vertical_layout.h"
17 #include "ui/wrap/fade_wrap.h"
18 #include "lang/lang_keys.h"
19 #include "styles/style_payments.h"
20 #include "styles/style_passport.h"
21 
22 namespace Payments::Ui {
23 namespace {
24 
25 constexpr auto kMaxStreetSize = 64;
26 constexpr auto kMaxPostcodeSize = 10;
27 constexpr auto kMaxNameSize = 64;
28 constexpr auto kMaxEmailSize = 128;
29 constexpr auto kMaxPhoneSize = 16;
30 constexpr auto kMinCitySize = 2;
31 constexpr auto kMaxCitySize = 64;
32 
33 } // namespace
34 
EditInformation(QWidget * parent,const Invoice & invoice,const RequestedInformation & current,InformationField field,not_null<PanelDelegate * > delegate)35 EditInformation::EditInformation(
36 	QWidget *parent,
37 	const Invoice &invoice,
38 	const RequestedInformation &current,
39 	InformationField field,
40 	not_null<PanelDelegate*> delegate)
41 : _delegate(delegate)
42 , _invoice(invoice)
43 , _information(current)
44 , _scroll(this, st::passportPanelScroll)
45 , _topShadow(this)
46 , _bottomShadow(this)
47 , _submit(
48 	this,
49 	tr::lng_settings_save(),
50 	st::paymentsPanelButton)
51 , _cancel(
52 		this,
53 		tr::lng_cancel(),
54 		st::paymentsPanelButton) {
55 	setupControls();
56 }
57 
58 EditInformation::~EditInformation() = default;
59 
setFocus(InformationField field)60 void EditInformation::setFocus(InformationField field) {
61 	_focusField = field;
62 	if (const auto control = lookupField(field)) {
63 		_scroll->ensureWidgetVisible(control->widget());
64 		control->setFocus();
65 	}
66 }
67 
setFocusFast(InformationField field)68 void EditInformation::setFocusFast(InformationField field) {
69 	_focusField = field;
70 	if (const auto control = lookupField(field)) {
71 		_scroll->ensureWidgetVisible(control->widget());
72 		control->setFocusFast();
73 	}
74 }
75 
showError(InformationField field)76 void EditInformation::showError(InformationField field) {
77 	if (const auto control = lookupField(field)) {
78 		_scroll->ensureWidgetVisible(control->widget());
79 		control->showError();
80 	}
81 }
82 
setupControls()83 void EditInformation::setupControls() {
84 	const auto inner = setupContent();
85 
86 	_submit->addClickHandler([=] {
87 		_delegate->panelValidateInformation(collect());
88 	});
89 	_cancel->addClickHandler([=] {
90 		_delegate->panelCancelEdit();
91 	});
92 
93 	using namespace rpl::mappers;
94 
95 	_topShadow->toggleOn(
96 		_scroll->scrollTopValue() | rpl::map(_1 > 0));
97 	_bottomShadow->toggleOn(rpl::combine(
98 		_scroll->scrollTopValue(),
99 		_scroll->heightValue(),
100 		inner->heightValue(),
101 		_1 + _2 < _3));
102 }
103 
setupContent()104 not_null<RpWidget*> EditInformation::setupContent() {
105 	const auto inner = _scroll->setOwnedWidget(
106 		object_ptr<VerticalLayout>(this));
107 
108 	_scroll->widthValue(
109 	) | rpl::start_with_next([=](int width) {
110 		inner->resizeToWidth(width);
111 	}, inner->lifetime());
112 
113 	const auto showBox = [=](object_ptr<BoxContent> box) {
114 		_delegate->panelShowBox(std::move(box));
115 	};
116 	auto last = (Field*)nullptr;
117 	const auto add = [&](FieldConfig &&config) {
118 		auto result = std::make_unique<Field>(inner, std::move(config));
119 		inner->add(result->ownedWidget(), st::paymentsFieldPadding);
120 		if (last) {
121 			last->setNextField(result.get());
122 			result->setPreviousField(last);
123 		}
124 		last = result.get();
125 		return result;
126 	};
127 	if (_invoice.isShippingAddressRequested) {
128 		_street1 = add({
129 			.placeholder = tr::lng_payments_address_street1(),
130 			.value = _information.shippingAddress.address1,
131 			.validator = RangeLengthValidator(1, kMaxStreetSize),
132 		});
133 		_street2 = add({
134 			.placeholder = tr::lng_payments_address_street2(),
135 			.value = _information.shippingAddress.address2,
136 			.validator = MaxLengthValidator(kMaxStreetSize),
137 		});
138 		_city = add({
139 			.placeholder = tr::lng_payments_address_city(),
140 			.value = _information.shippingAddress.city,
141 			.validator = RangeLengthValidator(kMinCitySize, kMaxCitySize),
142 		});
143 		_state = add({
144 			.placeholder = tr::lng_payments_address_state(),
145 			.value = _information.shippingAddress.state,
146 		});
147 		_country = add({
148 			.type = FieldType::Country,
149 			.placeholder = tr::lng_payments_address_country(),
150 			.value = _information.shippingAddress.countryIso2,
151 			.validator = RequiredFinishedValidator(),
152 			.showBox = showBox,
153 			.defaultCountry = _information.defaultCountry,
154 		});
155 		_postcode = add({
156 			.placeholder = tr::lng_payments_address_postcode(),
157 			.value = _information.shippingAddress.postcode,
158 			.validator = RangeLengthValidator(1, kMaxPostcodeSize),
159 		});
160 	}
161 	if (_invoice.isNameRequested) {
162 		_name = add({
163 			.placeholder = tr::lng_payments_info_name(),
164 			.value = _information.name,
165 			.validator = RangeLengthValidator(1, kMaxNameSize),
166 		});
167 	}
168 	if (_invoice.isEmailRequested) {
169 		_email = add({
170 			.type = FieldType::Email,
171 			.placeholder = tr::lng_payments_info_email(),
172 			.value = _information.email,
173 			.validator = RangeLengthValidator(1, kMaxEmailSize),
174 		});
175 	}
176 	if (_invoice.isPhoneRequested) {
177 		_phone = add({
178 			.type = FieldType::Phone,
179 			.placeholder = tr::lng_payments_info_phone(),
180 			.value = _information.phone,
181 			.validator = RangeLengthValidator(1, kMaxPhoneSize),
182 			.defaultPhone = _information.defaultPhone,
183 		});
184 	}
185 	const auto emailToProvider = _invoice.isEmailRequested
186 		&& _invoice.emailSentToProvider;
187 	const auto phoneToProvider = _invoice.isPhoneRequested
188 		&& _invoice.phoneSentToProvider;
189 	if (emailToProvider || phoneToProvider) {
190 		inner->add(
191 			object_ptr<Ui::FlatLabel>(
192 				inner,
193 				((emailToProvider && phoneToProvider)
194 					? tr::lng_payments_to_provider_phone_email
195 					: emailToProvider
196 					? tr::lng_payments_to_provider_email
197 					: tr::lng_payments_to_provider_phone)(
198 						lt_provider,
199 						rpl::single(_invoice.provider)),
200 				st::paymentsToProviderLabel),
201 			st::paymentsToProviderPadding);
202 	}
203 	_save = inner->add(
204 		object_ptr<Ui::Checkbox>(
205 			inner,
206 			tr::lng_payments_save_information(tr::now),
207 			true),
208 		st::paymentsSaveCheckboxPadding);
209 
210 	if (last) {
211 		last->submitted(
212 		) | rpl::start_with_next([=] {
213 			_delegate->panelValidateInformation(collect());
214 		}, lifetime());
215 	}
216 
217 	return inner;
218 }
219 
resizeEvent(QResizeEvent * e)220 void EditInformation::resizeEvent(QResizeEvent *e) {
221 	updateControlsGeometry();
222 }
223 
focusInEvent(QFocusEvent * e)224 void EditInformation::focusInEvent(QFocusEvent *e) {
225 	if (const auto control = lookupField(_focusField)) {
226 		control->setFocus();
227 	}
228 }
229 
updateControlsGeometry()230 void EditInformation::updateControlsGeometry() {
231 	const auto &padding = st::paymentsPanelPadding;
232 	const auto buttonsHeight = padding.top()
233 		+ _cancel->height()
234 		+ padding.bottom();
235 	const auto buttonsTop = height() - buttonsHeight;
236 	_scroll->setGeometry(0, 0, width(), buttonsTop);
237 	_topShadow->resizeToWidth(width());
238 	_topShadow->moveToLeft(0, 0);
239 	_bottomShadow->resizeToWidth(width());
240 	_bottomShadow->moveToLeft(0, buttonsTop - st::lineWidth);
241 	auto right = padding.right();
242 	_submit->moveToRight(right, buttonsTop + padding.top());
243 	right += _submit->width() + padding.left();
244 	_cancel->moveToRight(right, buttonsTop + padding.top());
245 
246 	_scroll->updateBars();
247 }
248 
lookupField(InformationField field) const249 auto EditInformation::lookupField(InformationField field) const -> Field* {
250 	switch (field) {
251 	case InformationField::ShippingStreet: return _street1.get();
252 	case InformationField::ShippingCity: return _city.get();
253 	case InformationField::ShippingState: return _state.get();
254 	case InformationField::ShippingCountry: return _country.get();
255 	case InformationField::ShippingPostcode: return _postcode.get();
256 	case InformationField::Name: return _name.get();
257 	case InformationField::Email: return _email.get();
258 	case InformationField::Phone: return _phone.get();
259 	}
260 	Unexpected("Unknown field in EditInformation::lookupField.");
261 }
262 
collect() const263 RequestedInformation EditInformation::collect() const {
264 	return {
265 		.defaultPhone = _information.defaultPhone,
266 		.defaultCountry = _information.defaultCountry,
267 		.save = _save->checked(),
268 		.name = _name ? _name->value() : QString(),
269 		.phone = _phone ? _phone->value() : QString(),
270 		.email = _email ? _email->value() : QString(),
271 		.shippingAddress = {
272 			.address1 = _street1 ? _street1->value() : QString(),
273 			.address2 = _street2 ? _street2->value() : QString(),
274 			.city = _city ? _city->value() : QString(),
275 			.state = _state ? _state->value() : QString(),
276 			.countryIso2 = _country ? _country->value() : QString(),
277 			.postcode = _postcode ? _postcode->value() : QString(),
278 		},
279 	};
280 }
281 
282 } // namespace Payments::Ui
283