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_panel.h"
9 
10 #include "payments/ui/payments_form_summary.h"
11 #include "payments/ui/payments_edit_information.h"
12 #include "payments/ui/payments_edit_card.h"
13 #include "payments/ui/payments_panel_delegate.h"
14 #include "payments/ui/payments_field.h"
15 #include "ui/widgets/separate_panel.h"
16 #include "ui/widgets/checkbox.h"
17 #include "ui/wrap/fade_wrap.h"
18 #include "ui/boxes/single_choice_box.h"
19 #include "ui/text/format_values.h"
20 #include "ui/text/text_utilities.h"
21 #include "ui/effects/radial_animation.h"
22 #include "lang/lang_keys.h"
23 #include "webview/webview_embed.h"
24 #include "webview/webview_interface.h"
25 #include "styles/style_payments.h"
26 #include "styles/style_layers.h"
27 
28 namespace Payments::Ui {
29 namespace {
30 
31 constexpr auto kProgressDuration = crl::time(200);
32 constexpr auto kProgressOpacity = 0.3;
33 
34 } // namespace
35 
36 struct Panel::Progress {
37 	Progress(QWidget *parent, Fn<QRect()> rect);
38 
39 	RpWidget widget;
40 	InfiniteRadialAnimation animation;
41 	Animations::Simple shownAnimation;
42 	bool shown = true;
43 	rpl::lifetime geometryLifetime;
44 };
45 
46 struct Panel::WebviewWithLifetime {
47 	WebviewWithLifetime(
48 		QWidget *parent = nullptr,
49 		Webview::WindowConfig config = Webview::WindowConfig());
50 
51 	Webview::Window window;
52 	QPointer<RpWidget> lastHidingBox;
53 	rpl::lifetime lifetime;
54 };
55 
WebviewWithLifetime(QWidget * parent,Webview::WindowConfig config)56 Panel::WebviewWithLifetime::WebviewWithLifetime(
57 	QWidget *parent,
58 	Webview::WindowConfig config)
59 : window(parent, std::move(config)) {
60 }
61 
Progress(QWidget * parent,Fn<QRect ()> rect)62 Panel::Progress::Progress(QWidget *parent, Fn<QRect()> rect)
63 : widget(parent)
64 , animation(
65 	[=] { if (!anim::Disabled()) widget.update(rect()); },
66 	st::paymentsLoading) {
67 }
68 
Panel(not_null<PanelDelegate * > delegate)69 Panel::Panel(not_null<PanelDelegate*> delegate)
70 : _delegate(delegate)
71 , _widget(std::make_unique<SeparatePanel>()) {
72 	_widget->setInnerSize(st::paymentsPanelSize);
73 	_widget->setWindowFlag(Qt::WindowStaysOnTopHint, false);
74 
75 	_widget->closeRequests(
76 	) | rpl::start_with_next([=] {
77 		_delegate->panelRequestClose();
78 	}, _widget->lifetime());
79 
80 	_widget->closeEvents(
81 	) | rpl::start_with_next([=] {
82 		_delegate->panelCloseSure();
83 	}, _widget->lifetime());
84 }
85 
~Panel()86 Panel::~Panel() {
87 	_webview = nullptr;
88 	_progress = nullptr;
89 	_widget = nullptr;
90 }
91 
requestActivate()92 void Panel::requestActivate() {
93 	_widget->showAndActivate();
94 }
95 
toggleProgress(bool shown)96 void Panel::toggleProgress(bool shown) {
97 	if (!_progress) {
98 		if (!shown) {
99 			return;
100 		}
101 		_progress = std::make_unique<Progress>(
102 			_widget.get(),
103 			[=] { return progressRect(); });
104 		_progress->widget.paintRequest(
105 		) | rpl::start_with_next([=](QRect clip) {
106 			auto p = QPainter(&_progress->widget);
107 			p.setOpacity(
108 				_progress->shownAnimation.value(_progress->shown ? 1. : 0.));
109 			auto thickness = st::paymentsLoading.thickness;
110 			if (progressWithBackground()) {
111 				auto color = st::windowBg->c;
112 				color.setAlphaF(kProgressOpacity);
113 				p.fillRect(clip, color);
114 			}
115 			const auto rect = progressRect().marginsRemoved(
116 				{ thickness, thickness, thickness, thickness });
117 			InfiniteRadialAnimation::Draw(
118 				p,
119 				_progress->animation.computeState(),
120 				rect.topLeft(),
121 				rect.size() - QSize(),
122 				_progress->widget.width(),
123 				st::paymentsLoading.color,
124 				thickness);
125 		}, _progress->widget.lifetime());
126 		_progress->widget.show();
127 		_progress->animation.start();
128 	} else if (_progress->shown == shown) {
129 		return;
130 	}
131 	const auto callback = [=] {
132 		if (!_progress->shownAnimation.animating() && !_progress->shown) {
133 			_progress = nullptr;
134 		} else {
135 			_progress->widget.update();
136 		}
137 	};
138 	_progress->shown = shown;
139 	_progress->shownAnimation.start(
140 		callback,
141 		shown ? 0. : 1.,
142 		shown ? 1. : 0.,
143 		kProgressDuration);
144 	if (shown) {
145 		setupProgressGeometry();
146 	}
147 }
148 
progressWithBackground() const149 bool Panel::progressWithBackground() const {
150 	return (_progress->widget.width() == _widget->innerGeometry().width());
151 }
152 
progressRect() const153 QRect Panel::progressRect() const {
154 	const auto rect = _progress->widget.rect();
155 	if (!progressWithBackground()) {
156 		return rect;
157 	}
158 	const auto size = st::defaultBoxButton.height;
159 	return QRect(
160 		rect.x() + (rect.width() - size) / 2,
161 		rect.y() + (rect.height() - size) / 2,
162 		size,
163 		size);
164 }
165 
setupProgressGeometry()166 void Panel::setupProgressGeometry() {
167 	if (!_progress || !_progress->shown) {
168 		return;
169 	}
170 	_progress->geometryLifetime.destroy();
171 	if (_webviewBottom) {
172 		_webviewBottom->geometryValue(
173 		) | rpl::start_with_next([=](QRect bottom) {
174 			const auto height = bottom.height();
175 			const auto size = st::paymentsLoading.size;
176 			const auto skip = (height - size.height()) / 2;
177 			const auto inner = _widget->innerGeometry();
178 			const auto right = inner.x() + inner.width();
179 			const auto top = inner.y() + inner.height() - height;
180 			// This doesn't work, because first we get the correct bottom
181 			// geometry and after that we get the previous event (which
182 			// triggered the 'fire' of correct geometry before getting here).
183 			//const auto right = bottom.x() + bottom.width();
184 			//const auto top = bottom.y();
185 			_progress->widget.setGeometry(QRect{
186 				QPoint(right - skip - size.width(), top + skip),
187 				size });
188 		}, _progress->geometryLifetime);
189 	} else if (_weakFormSummary) {
190 		_weakFormSummary->sizeValue(
191 		) | rpl::start_with_next([=](QSize form) {
192 			const auto full = _widget->innerGeometry();
193 			const auto size = st::defaultBoxButton.height;
194 			const auto inner = _weakFormSummary->contentHeight();
195 			const auto left = full.height() - inner;
196 			if (left >= 2 * size) {
197 				_progress->widget.setGeometry(
198 					full.x() + (full.width() - size) / 2,
199 					full.y() + inner + (left - size) / 2,
200 					size,
201 					size);
202 			} else {
203 				_progress->widget.setGeometry(full);
204 			}
205 		}, _progress->geometryLifetime);
206 	} else if (_weakEditInformation) {
207 		_weakEditInformation->geometryValue(
208 		) | rpl::start_with_next([=] {
209 			_progress->widget.setGeometry(_widget->innerGeometry());
210 		}, _progress->geometryLifetime);
211 	} else if (_weakEditCard) {
212 		_weakEditCard->geometryValue(
213 		) | rpl::start_with_next([=] {
214 			_progress->widget.setGeometry(_widget->innerGeometry());
215 		}, _progress->geometryLifetime);
216 	}
217 	_progress->widget.show();
218 	_progress->widget.raise();
219 	if (_progress->shown) {
220 		_progress->widget.setFocus();
221 	}
222 }
223 
showForm(const Invoice & invoice,const RequestedInformation & current,const PaymentMethodDetails & method,const ShippingOptions & options)224 void Panel::showForm(
225 		const Invoice &invoice,
226 		const RequestedInformation &current,
227 		const PaymentMethodDetails &method,
228 		const ShippingOptions &options) {
229 	if (invoice && !method.ready && !method.native.supported) {
230 		const auto available = Webview::Availability();
231 		if (available.error != Webview::Available::Error::None) {
232 			showWebviewError(
233 				tr::lng_payments_webview_no_use(tr::now),
234 				available);
235 			return;
236 		}
237 	}
238 
239 	_testMode = invoice.isTest;
240 	setTitle(invoice.receipt
241 		? tr::lng_payments_receipt_title()
242 		: tr::lng_payments_checkout_title());
243 	auto form = base::make_unique_q<FormSummary>(
244 		_widget.get(),
245 		invoice,
246 		current,
247 		method,
248 		options,
249 		_delegate,
250 		_formScrollTop.current());
251 	_weakFormSummary = form.get();
252 	_widget->showInner(std::move(form));
253 	_widget->setBackAllowed(false);
254 	_formScrollTop = _weakFormSummary->scrollTopValue();
255 	setupProgressGeometry();
256 }
257 
updateFormThumbnail(const QImage & thumbnail)258 void Panel::updateFormThumbnail(const QImage &thumbnail) {
259 	if (_weakFormSummary) {
260 		_weakFormSummary->updateThumbnail(thumbnail);
261 	}
262 }
263 
showEditInformation(const Invoice & invoice,const RequestedInformation & current,InformationField field)264 void Panel::showEditInformation(
265 		const Invoice &invoice,
266 		const RequestedInformation &current,
267 		InformationField field) {
268 	setTitle(tr::lng_payments_shipping_address_title());
269 	auto edit = base::make_unique_q<EditInformation>(
270 		_widget.get(),
271 		invoice,
272 		current,
273 		field,
274 		_delegate);
275 	_weakEditInformation = edit.get();
276 	_widget->showInner(std::move(edit));
277 	_widget->setBackAllowed(true);
278 	_weakEditInformation->setFocusFast(field);
279 	setupProgressGeometry();
280 }
281 
showInformationError(const Invoice & invoice,const RequestedInformation & current,InformationField field)282 void Panel::showInformationError(
283 		const Invoice &invoice,
284 		const RequestedInformation &current,
285 		InformationField field) {
286 	if (_weakEditInformation) {
287 		_weakEditInformation->showError(field);
288 	} else {
289 		showEditInformation(invoice, current, field);
290 		if (_weakEditInformation
291 			&& field == InformationField::ShippingCountry) {
292 			_weakEditInformation->showError(field);
293 		}
294 	}
295 }
296 
chooseShippingOption(const ShippingOptions & options)297 void Panel::chooseShippingOption(const ShippingOptions &options) {
298 	showBox(Box([=](not_null<GenericBox*> box) {
299 		const auto i = ranges::find(
300 			options.list,
301 			options.selectedId,
302 			&ShippingOption::id);
303 		const auto index = (i != end(options.list))
304 			? int(i - begin(options.list))
305 			: -1;
306 		const auto group = std::make_shared<RadiobuttonGroup>(index);
307 
308 		const auto layout = box->verticalLayout();
309 		auto counter = 0;
310 		for (const auto &option : options.list) {
311 			const auto index = counter++;
312 			const auto button = layout->add(
313 				object_ptr<Radiobutton>(
314 					layout,
315 					group,
316 					index,
317 					QString(),
318 					st::defaultBoxCheckbox,
319 					st::defaultRadio),
320 				st::paymentsShippingMargin);
321 			const auto label = CreateChild<FlatLabel>(
322 				layout.get(),
323 				option.title,
324 				st::paymentsShippingLabel);
325 			const auto total = ranges::accumulate(
326 				option.prices,
327 				int64(0),
328 				std::plus<>(),
329 				&LabeledPrice::price);
330 			const auto price = CreateChild<FlatLabel>(
331 				layout.get(),
332 				FillAmountAndCurrency(total, options.currency),
333 				st::paymentsShippingPrice);
334 			const auto area = CreateChild<AbstractButton>(layout.get());
335 			area->setClickedCallback([=] { group->setValue(index); });
336 			button->geometryValue(
337 			) | rpl::start_with_next([=](QRect geometry) {
338 				label->move(
339 					geometry.topLeft() + st::paymentsShippingLabelPosition);
340 				price->move(
341 					geometry.topLeft() + st::paymentsShippingPricePosition);
342 				const auto right = geometry.x()
343 					+ st::paymentsShippingLabelPosition.x();
344 				area->setGeometry(
345 					right,
346 					geometry.y(),
347 					std::max(
348 						label->x() + label->width() - right,
349 						price->x() + price->width() - right),
350 					price->y() + price->height() - geometry.y());
351 			}, button->lifetime());
352 		}
353 
354 		box->setTitle(tr::lng_payments_shipping_method());
355 		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
356 		group->setChangedCallback([=](int index) {
357 			if (index >= 0) {
358 				_delegate->panelChangeShippingOption(
359 					options.list[index].id);
360 				box->closeBox();
361 			}
362 		});
363 	}));
364 }
365 
chooseTips(const Invoice & invoice)366 void Panel::chooseTips(const Invoice &invoice) {
367 	const auto max = invoice.tipsMax;
368 	const auto now = invoice.tipsSelected;
369 	const auto currency = invoice.currency;
370 	showBox(Box([=](not_null<GenericBox*> box) {
371 		box->setTitle(tr::lng_payments_tips_box_title());
372 		const auto row = box->lifetime().make_state<Field>(
373 			box,
374 			FieldConfig{
375 				.type = FieldType::Money,
376 				.value = QString::number(now),
377 				.currency = currency,
378 			});
379 		box->setFocusCallback([=] {
380 			row->setFocusFast();
381 		});
382 		box->addRow(row->ownedWidget());
383 		const auto errorWrap = box->addRow(
384 			object_ptr<FadeWrap<FlatLabel>>(
385 				box,
386 				object_ptr<FlatLabel>(
387 					box,
388 					tr::lng_payments_tips_max(
389 						lt_amount,
390 						rpl::single(FillAmountAndCurrency(max, currency))),
391 					st::paymentTipsErrorLabel)),
392 			st::paymentTipsErrorPadding);
393 		errorWrap->hide(anim::type::instant);
394 		const auto submit = [=] {
395 			const auto value = row->value().toLongLong();
396 			if (value > max) {
397 				row->showError();
398 				errorWrap->show(anim::type::normal);
399 			} else {
400 				_delegate->panelChangeTips(value);
401 				box->closeBox();
402 			}
403 		};
404 		row->submitted(
405 		) | rpl::start_with_next(submit, box->lifetime());
406 		box->addButton(tr::lng_settings_save(), submit);
407 		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
408 	}));
409 }
410 
showEditPaymentMethod(const PaymentMethodDetails & method)411 void Panel::showEditPaymentMethod(const PaymentMethodDetails &method) {
412 	auto bottomText = method.canSaveInformation
413 		? rpl::producer<QString>()
414 		: tr::lng_payments_processed_by(
415 			lt_provider,
416 			rpl::single(method.provider));
417 	setTitle(tr::lng_payments_card_title());
418 	if (method.native.supported) {
419 		showEditCard(method.native, CardField::Number);
420 	} else if (!showWebview(method.url, true, std::move(bottomText))) {
421 		const auto available = Webview::Availability();
422 		if (available.error != Webview::Available::Error::None) {
423 			showWebviewError(
424 				tr::lng_payments_webview_no_card(tr::now),
425 				available);
426 		} else {
427 			showCriticalError({ "Error: Could not initialize WebView." });
428 		}
429 		_widget->setBackAllowed(true);
430 	} else if (method.canSaveInformation) {
431 		const auto &padding = st::paymentsPanelPadding;
432 		_saveWebviewInformation = CreateChild<Checkbox>(
433 			_webviewBottom.get(),
434 			tr::lng_payments_save_information(tr::now),
435 			false);
436 		const auto height = padding.top()
437 			+ _saveWebviewInformation->heightNoMargins()
438 			+ padding.bottom();
439 		_saveWebviewInformation->moveToLeft(padding.right(), padding.top());
440 		_saveWebviewInformation->show();
441 		_webviewBottom->resize(_webviewBottom->width(), height);
442 	}
443 }
444 
showWebviewProgress()445 void Panel::showWebviewProgress() {
446 	if (_webviewProgress && _progress && _progress->shown) {
447 		return;
448 	}
449 	_webviewProgress = true;
450 	toggleProgress(true);
451 }
452 
hideWebviewProgress()453 void Panel::hideWebviewProgress() {
454 	if (!_webviewProgress) {
455 		return;
456 	}
457 	_webviewProgress = false;
458 	toggleProgress(false);
459 }
460 
showWebview(const QString & url,bool allowBack,rpl::producer<QString> bottomText)461 bool Panel::showWebview(
462 		const QString &url,
463 		bool allowBack,
464 		rpl::producer<QString> bottomText) {
465 	if (!_webview && !createWebview()) {
466 		return false;
467 	}
468 	showWebviewProgress();
469 	_widget->destroyLayer();
470 	_webview->window.navigate(url);
471 	_widget->setBackAllowed(allowBack);
472 	if (bottomText) {
473 		const auto &padding = st::paymentsPanelPadding;
474 		const auto label = CreateChild<FlatLabel>(
475 			_webviewBottom.get(),
476 			std::move(bottomText),
477 			st::paymentsWebviewBottom);
478 		const auto height = padding.top()
479 			+ label->heightNoMargins()
480 			+ padding.bottom();
481 		rpl::combine(
482 			_webviewBottom->widthValue(),
483 			label->widthValue()
484 		) | rpl::start_with_next([=](int outerWidth, int width) {
485 			label->move((outerWidth - width) / 2, padding.top());
486 		}, label->lifetime());
487 		label->show();
488 		_webviewBottom->resize(_webviewBottom->width(), height);
489 	}
490 	return true;
491 }
492 
createWebview()493 bool Panel::createWebview() {
494 	auto container = base::make_unique_q<RpWidget>(_widget.get());
495 
496 	_webviewBottom = std::make_unique<RpWidget>(_widget.get());
497 	const auto bottom = _webviewBottom.get();
498 	bottom->show();
499 
500 	bottom->heightValue(
501 	) | rpl::start_with_next([=, raw = container.get()](int height) {
502 		const auto inner = _widget->innerGeometry();
503 		bottom->move(inner.x(), inner.y() + inner.height() - height);
504 		raw->resize(inner.width(), inner.height() - height);
505 		bottom->resizeToWidth(inner.width());
506 	}, bottom->lifetime());
507 	container->show();
508 
509 	_webview = std::make_unique<WebviewWithLifetime>(
510 		container.get(),
511 		Webview::WindowConfig{
512 			.userDataPath = _delegate->panelWebviewDataPath(),
513 		});
514 	const auto raw = &_webview->window;
515 	QObject::connect(container.get(), &QObject::destroyed, [=] {
516 		if (_webview && &_webview->window == raw) {
517 			_webview = nullptr;
518 			if (_webviewProgress) {
519 				hideWebviewProgress();
520 				if (_progress && !_progress->shown) {
521 					_progress = nullptr;
522 				}
523 			}
524 		}
525 		if (_webviewBottom.get() == bottom) {
526 			_webviewBottom = nullptr;
527 		}
528 	});
529 	if (!raw->widget()) {
530 		return false;
531 	}
532 
533 	container->geometryValue(
534 	) | rpl::start_with_next([=](QRect geometry) {
535 		raw->widget()->setGeometry(geometry);
536 	}, _webview->lifetime);
537 
538 	raw->setMessageHandler([=](const QJsonDocument &message) {
539 		const auto save = _saveWebviewInformation
540 			&& _saveWebviewInformation->checked();
541 		_delegate->panelWebviewMessage(message, save);
542 	});
543 
544 	raw->setNavigationStartHandler([=](const QString &uri) {
545 		if (!_delegate->panelWebviewNavigationAttempt(uri)) {
546 			return false;
547 		}
548 		showWebviewProgress();
549 		return true;
550 	});
551 	raw->setNavigationDoneHandler([=](bool success) {
552 		hideWebviewProgress();
553 	});
554 
555 	raw->init(R"(
556 window.TelegramWebviewProxy = {
557 postEvent: function(eventType, eventData) {
558 	if (window.external && window.external.invoke) {
559 		window.external.invoke(JSON.stringify([eventType, eventData]));
560 	}
561 }
562 };)");
563 
564 	_widget->showInner(std::move(container));
565 
566 	setupProgressGeometry();
567 
568 	return true;
569 }
570 
choosePaymentMethod(const PaymentMethodDetails & method)571 void Panel::choosePaymentMethod(const PaymentMethodDetails &method) {
572 	if (!method.ready) {
573 		showEditPaymentMethod(method);
574 		return;
575 	}
576 	showBox(Box([=](not_null<GenericBox*> box) {
577 		const auto save = [=](int option) {
578 			if (option) {
579 				showEditPaymentMethod(method);
580 			}
581 		};
582 		SingleChoiceBox(box, {
583 			.title = tr::lng_payments_payment_method(),
584 			.options = { method.title, tr::lng_payments_new_card(tr::now) },
585 			.initialSelection = 0,
586 			.callback = save,
587 		});
588 	}));
589 }
590 
askSetPassword()591 void Panel::askSetPassword() {
592 	showBox(Box([=](not_null<GenericBox*> box) {
593 		box->addRow(
594 			object_ptr<FlatLabel>(
595 				box.get(),
596 				tr::lng_payments_need_password(),
597 				st::boxLabel),
598 			st::boxPadding);
599 		box->addButton(tr::lng_continue(), [=] {
600 			_delegate->panelSetPassword();
601 			box->closeBox();
602 		});
603 		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
604 	}));
605 }
606 
showCloseConfirm()607 void Panel::showCloseConfirm() {
608 	showBox(Box([=](not_null<GenericBox*> box) {
609 		box->addRow(
610 			object_ptr<FlatLabel>(
611 				box.get(),
612 				tr::lng_payments_sure_close(),
613 				st::boxLabel),
614 			st::boxPadding);
615 		box->addButton(tr::lng_close(), [=] {
616 			_delegate->panelCloseSure();
617 		});
618 		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
619 	}));
620 }
621 
showWarning(const QString & bot,const QString & provider)622 void Panel::showWarning(const QString &bot, const QString &provider) {
623 	showBox(Box([=](not_null<GenericBox*> box) {
624 		box->setTitle(tr::lng_payments_warning_title());
625 		box->addRow(object_ptr<FlatLabel>(
626 			box.get(),
627 			tr::lng_payments_warning_body(
628 				lt_bot1,
629 				rpl::single(bot),
630 				lt_provider,
631 				rpl::single(provider),
632 				lt_bot2,
633 				rpl::single(bot),
634 				lt_bot3,
635 				rpl::single(bot)),
636 			st::boxLabel));
637 		box->addButton(tr::lng_continue(), [=] {
638 			_delegate->panelTrustAndSubmit();
639 			box->closeBox();
640 		});
641 		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
642 	}));
643 }
644 
showEditCard(const NativeMethodDetails & native,CardField field)645 void Panel::showEditCard(
646 		const NativeMethodDetails &native,
647 		CardField field) {
648 	Expects(native.supported);
649 
650 	auto edit = base::make_unique_q<EditCard>(
651 		_widget.get(),
652 		native,
653 		field,
654 		_delegate);
655 	_weakEditCard = edit.get();
656 	_widget->showInner(std::move(edit));
657 	_widget->setBackAllowed(true);
658 	_weakEditCard->setFocusFast(field);
659 	setupProgressGeometry();
660 }
661 
showCardError(const NativeMethodDetails & native,CardField field)662 void Panel::showCardError(
663 		const NativeMethodDetails &native,
664 		CardField field) {
665 	if (_weakEditCard) {
666 		_weakEditCard->showError(field);
667 	} else {
668 		// We cancelled card edit already.
669 		//showEditCard(native, field);
670 		//if (_weakEditCard
671 		//	&& field == CardField::AddressCountry) {
672 		//	_weakEditCard->showError(field);
673 		//}
674 	}
675 }
676 
setTitle(rpl::producer<QString> title)677 void Panel::setTitle(rpl::producer<QString> title) {
678 	using namespace rpl::mappers;
679 	if (_testMode) {
680 		_widget->setTitle(std::move(title) | rpl::map(_1 + " (Test)"));
681 	} else {
682 		_widget->setTitle(std::move(title));
683 	}
684 }
685 
backRequests() const686 rpl::producer<> Panel::backRequests() const {
687 	return _widget->backRequests();
688 }
689 
showBox(object_ptr<BoxContent> box)690 void Panel::showBox(object_ptr<BoxContent> box) {
691 	if (const auto widget = _webview ? _webview->window.widget() : nullptr) {
692 		const auto hideNow = !widget->isHidden();
693 		if (hideNow || _webview->lastHidingBox) {
694 			const auto raw = _webview->lastHidingBox = box.data();
695 			box->boxClosing(
696 			) | rpl::start_with_next([=] {
697 				const auto widget = _webview
698 					? _webview->window.widget()
699 					: nullptr;
700 				if (widget
701 					&& widget->isHidden()
702 					&& _webview->lastHidingBox == raw) {
703 					widget->show();
704 				}
705 			}, _webview->lifetime);
706 			if (hideNow) {
707 				widget->hide();
708 			}
709 		}
710 	}
711 	_widget->showBox(
712 		std::move(box),
713 		LayerOption::KeepOther,
714 		anim::type::normal);
715 }
716 
showToast(const TextWithEntities & text)717 void Panel::showToast(const TextWithEntities &text) {
718 	_widget->showToast(text);
719 }
720 
showCriticalError(const TextWithEntities & text)721 void Panel::showCriticalError(const TextWithEntities &text) {
722 	_progress = nullptr;
723 	_webviewProgress = false;
724 	if (!_weakFormSummary || !_weakFormSummary->showCriticalError(text)) {
725 		auto error = base::make_unique_q<PaddingWrap<FlatLabel>>(
726 			_widget.get(),
727 			object_ptr<FlatLabel>(
728 				_widget.get(),
729 				rpl::single(text),
730 				st::paymentsCriticalError),
731 			st::paymentsCriticalErrorPadding);
732 		error->entity()->setClickHandlerFilter([=](
733 				const ClickHandlerPtr &handler,
734 				Qt::MouseButton) {
735 			const auto entity = handler->getTextEntity();
736 			if (entity.type != EntityType::CustomUrl) {
737 				return true;
738 			}
739 			_delegate->panelOpenUrl(entity.data);
740 			return false;
741 		});
742 		_widget->showInner(std::move(error));
743 	}
744 }
745 
showWebviewError(const QString & text,const Webview::Available & information)746 void Panel::showWebviewError(
747 		const QString &text,
748 		const Webview::Available &information) {
749 	using Error = Webview::Available::Error;
750 	Expects(information.error != Error::None);
751 
752 	auto rich = TextWithEntities{ text };
753 	rich.append("\n\n");
754 	switch (information.error) {
755 	case Error::NoWebview2: {
756 		const auto command = QString(QChar(TextCommand));
757 		const auto text = tr::lng_payments_webview_install_edge(
758 			tr::now,
759 			lt_link,
760 			command);
761 		const auto parts = text.split(command);
762 		rich.append(parts.value(0))
763 			.append(Text::Link(
764 				"Microsoft Edge WebView2 Runtime",
765 				"https://go.microsoft.com/fwlink/p/?LinkId=2124703"))
766 			.append(parts.value(1));
767 	} break;
768 	case Error::NoGtkOrWebkit2Gtk:
769 		rich.append(tr::lng_payments_webview_install_webkit(tr::now));
770 		break;
771 	case Error::MutterWM:
772 		rich.append(tr::lng_payments_webview_switch_mutter(tr::now));
773 		break;
774 	case Error::Wayland:
775 		rich.append(tr::lng_payments_webview_switch_wayland(tr::now));
776 		break;
777 	default:
778 		rich.append(QString::fromStdString(information.details));
779 		break;
780 	}
781 	showCriticalError(rich);
782 }
783 
lifetime()784 rpl::lifetime &Panel::lifetime() {
785 	return _widget->lifetime();
786 }
787 
788 } // namespace Payments::Ui
789