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 "intro/intro_step.h"
9 
10 #include "intro/intro_widget.h"
11 #include "storage/localstorage.h"
12 #include "storage/storage_account.h"
13 #include "lang/lang_keys.h"
14 #include "lang/lang_instance.h"
15 #include "lang/lang_cloud_manager.h"
16 #include "main/main_account.h"
17 #include "main/main_domain.h"
18 #include "main/main_session.h"
19 #include "main/main_session_settings.h"
20 #include "core/application.h"
21 #include "core/core_settings.h"
22 #include "apiwrap.h"
23 #include "api/api_peer_photo.h"
24 #include "mainwindow.h"
25 #include "ui/boxes/confirm_box.h"
26 #include "ui/text/text_utilities.h"
27 #include "ui/widgets/labels.h"
28 #include "ui/wrap/fade_wrap.h"
29 #include "ui/effects/slide_animation.h"
30 #include "ui/ui_utility.h"
31 #include "data/data_user.h"
32 #include "data/data_auto_download.h"
33 #include "data/data_session.h"
34 #include "data/data_chat_filters.h"
35 #include "window/window_controller.h"
36 #include "styles/style_intro.h"
37 #include "styles/style_window.h"
38 
39 namespace Intro {
40 namespace details {
41 namespace {
42 
PrepareSupportMode(not_null<Main::Session * > session)43 void PrepareSupportMode(not_null<Main::Session*> session) {
44 	using ::Data::AutoDownload::Full;
45 
46 	anim::SetDisabled(true);
47 	Core::App().settings().setDesktopNotify(false);
48 	Core::App().settings().setSoundNotify(false);
49 	Core::App().settings().setFlashBounceNotify(false);
50 	Core::App().saveSettings();
51 
52 	session->settings().autoDownload() = Full::FullDisabled();
53 	session->saveSettings();
54 }
55 
56 } // namespace
57 
58 Step::CoverAnimation::~CoverAnimation() = default;
59 
Step(QWidget * parent,not_null<Main::Account * > account,not_null<Data * > data,bool hasCover)60 Step::Step(
61 	QWidget *parent,
62 	not_null<Main::Account*> account,
63 	not_null<Data*> data,
64 	bool hasCover)
65 : RpWidget(parent)
66 , _account(account)
67 , _data(data)
68 , _hasCover(hasCover)
69 , _title(this, _hasCover ? st::introCoverTitle : st::introTitle)
70 , _description(
71 	this,
72 	object_ptr<Ui::FlatLabel>(
73 		this,
74 		_hasCover
75 			? st::introCoverDescription
76 			: st::introDescription)) {
77 	hide();
78 	style::PaletteChanged(
79 	) | rpl::start_with_next([=] {
80 		if (!_coverMask.isNull()) {
81 			_coverMask = QPixmap();
82 			prepareCoverMask();
83 		}
84 	}, lifetime());
85 
86 	_errorText.value(
87 	) | rpl::start_with_next([=](const QString &text) {
88 		refreshError(text);
89 	}, lifetime());
90 
91 	_titleText.value(
92 	) | rpl::start_with_next([=](const QString &text) {
93 		_title->setText(text);
94 		updateLabelsPosition();
95 	}, lifetime());
96 
97 	_descriptionText.value(
98 	) | rpl::start_with_next([=](const TextWithEntities &text) {
99 		_description->entity()->setMarkedText(text);
100 		updateLabelsPosition();
101 	}, lifetime());
102 }
103 
104 Step::~Step() = default;
105 
api() const106 MTP::Sender &Step::api() const {
107 	if (!_api) {
108 		_api.emplace(&_account->mtp());
109 	}
110 	return *_api;
111 }
112 
apiClear()113 void Step::apiClear() {
114 	_api.reset();
115 }
116 
nextButtonText() const117 rpl::producer<QString> Step::nextButtonText() const {
118 	return tr::lng_intro_next();
119 }
120 
goBack()121 void Step::goBack() {
122 	if (_goCallback) {
123 		_goCallback(nullptr, StackAction::Back, Animate::Back);
124 	}
125 }
126 
goNext(Step * step)127 void Step::goNext(Step *step) {
128 	if (_goCallback) {
129 		_goCallback(step, StackAction::Forward, Animate::Forward);
130 	}
131 }
132 
goReplace(Step * step,Animate animate)133 void Step::goReplace(Step *step, Animate animate) {
134 	if (_goCallback) {
135 		_goCallback(step, StackAction::Replace, animate);
136 	}
137 }
138 
finish(const MTPUser & user,QImage && photo)139 void Step::finish(const MTPUser &user, QImage &&photo) {
140 	if (user.type() != mtpc_user
141 		|| !user.c_user().is_self()
142 		|| !user.c_user().vid().v) {
143 		// No idea what to do here.
144 		// We could've reset intro and MTP, but this really should not happen.
145 		Ui::show(Box<Ui::InformBox>(
146 			"Internal error: bad user.is_self() after sign in."));
147 		return;
148 	}
149 
150 	// Check if such account is authorized already.
151 	for (const auto &[index, existing] : Core::App().domain().accounts()) {
152 		const auto raw = existing.get();
153 		if (const auto session = raw->maybeSession()) {
154 			if (raw->mtp().environment() == _account->mtp().environment()
155 				&& UserId(user.c_user().vid()) == session->userId()) {
156 				_account->logOut();
157 				crl::on_main(raw, [=] {
158 					Core::App().domain().activate(raw);
159 					Local::sync();
160 				});
161 				return;
162 			}
163 		}
164 	}
165 
166 	api().request(MTPmessages_GetDialogFilters(
167 	)).done([=](const MTPVector<MTPDialogFilter> &result) {
168 		createSession(user, photo, result.v);
169 	}).fail([=](const MTP::Error &error) {
170 		createSession(user, photo, QVector<MTPDialogFilter>());
171 	}).send();
172 }
173 
createSession(const MTPUser & user,QImage photo,const QVector<MTPDialogFilter> & filters)174 void Step::createSession(
175 		const MTPUser &user,
176 		QImage photo,
177 		const QVector<MTPDialogFilter> &filters) {
178 	// Save the default language if we've suggested some other and user ignored it.
179 	const auto currentId = Lang::Id();
180 	const auto defaultId = Lang::DefaultLanguageId();
181 	const auto suggested = Lang::CurrentCloudManager().suggestedLanguage();
182 	if (currentId.isEmpty() && !suggested.isEmpty() && suggested != defaultId) {
183 		Lang::GetInstance().switchToId(Lang::DefaultLanguage());
184 		Local::writeLangPack();
185 	}
186 
187 	auto settings = std::make_unique<Main::SessionSettings>();
188 	settings->setDialogsFiltersEnabled(!filters.isEmpty());
189 
190 	const auto account = _account;
191 	account->createSession(user, std::move(settings));
192 
193 	// "this" is already deleted here by creating the main widget.
194 	account->local().writeMtpData();
195 	auto &session = account->session();
196 	session.data().chatsFilters().setPreloaded(filters);
197 	if (!filters.isEmpty()) {
198 		session.saveSettingsDelayed();
199 	}
200 	if (!photo.isNull()) {
201 		session.api().peerPhoto().upload(session.user(), std::move(photo));
202 	}
203 	if (session.supportMode()) {
204 		PrepareSupportMode(&session);
205 	}
206 	Local::sync();
207 }
208 
paintEvent(QPaintEvent * e)209 void Step::paintEvent(QPaintEvent *e) {
210 	Painter p(this);
211 	paintAnimated(p, e->rect());
212 }
213 
resizeEvent(QResizeEvent * e)214 void Step::resizeEvent(QResizeEvent *e) {
215 	updateLabelsPosition();
216 }
217 
updateLabelsPosition()218 void Step::updateLabelsPosition() {
219 	Ui::SendPendingMoveResizeEvents(_description->entity());
220 	if (hasCover()) {
221 		_title->moveToLeft((width() - _title->width()) / 2, contentTop() + st::introCoverTitleTop);
222 		_description->moveToLeft((width() - _description->width()) / 2, contentTop() + st::introCoverDescriptionTop);
223 	} else {
224 		_title->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introTitleTop);
225 		_description->resizeToWidth(st::introDescription.minWidth);
226 		_description->moveToLeft(contentLeft() + st::buttonRadius, contentTop() + st::introDescriptionTop);
227 	}
228 	if (_error) {
229 		if (_errorCentered) {
230 			_error->entity()->resizeToWidth(width());
231 		}
232 		Ui::SendPendingMoveResizeEvents(_error->entity());
233 		auto errorLeft = _errorCentered ? 0 : (contentLeft() + st::buttonRadius);
234 		_error->moveToLeft(errorLeft, errorTop());
235 	}
236 }
237 
errorTop() const238 int Step::errorTop() const {
239 	return contentTop() + st::introErrorTop;
240 }
241 
setTitleText(rpl::producer<QString> titleText)242 void Step::setTitleText(rpl::producer<QString> titleText) {
243 	_titleText = std::move(titleText);
244 }
245 
setDescriptionText(rpl::producer<QString> descriptionText)246 void Step::setDescriptionText(
247 		rpl::producer<QString> descriptionText) {
248 	setDescriptionText(
249 		std::move(descriptionText) | Ui::Text::ToWithEntities());
250 }
251 
setDescriptionText(rpl::producer<TextWithEntities> richDescriptionText)252 void Step::setDescriptionText(
253 		rpl::producer<TextWithEntities> richDescriptionText) {
254 	_descriptionText = std::move(richDescriptionText);
255 }
256 
showFinished()257 void Step::showFinished() {
258 	_a_show.stop();
259 	_coverAnimation = CoverAnimation();
260 	_slideAnimation.reset();
261 	prepareCoverMask();
262 	activate();
263 }
264 
paintAnimated(Painter & p,QRect clip)265 bool Step::paintAnimated(Painter &p, QRect clip) {
266 	if (_slideAnimation) {
267 		_slideAnimation->paintFrame(p, (width() - st::introStepWidth) / 2, contentTop(), width());
268 		if (!_slideAnimation->animating()) {
269 			showFinished();
270 			return false;
271 		}
272 		return true;
273 	}
274 
275 	auto dt = _a_show.value(1.);
276 	if (!_a_show.animating()) {
277 		if (hasCover()) {
278 			paintCover(p, 0);
279 		}
280 		if (_coverAnimation.title) {
281 			showFinished();
282 		}
283 		if (!QRect(0, contentTop(), width(), st::introStepHeight).intersects(clip)) {
284 			return true;
285 		}
286 		return false;
287 	}
288 	if (!_coverAnimation.clipping.isEmpty()) {
289 		p.setClipRect(_coverAnimation.clipping);
290 	}
291 
292 	auto progress = (hasCover() ? anim::easeOutCirc(1., dt) : anim::linear(1., dt));
293 	auto arrivingAlpha = progress;
294 	auto departingAlpha = 1. - progress;
295 	auto showCoverMethod = progress;
296 	auto hideCoverMethod = progress;
297 	auto coverTop = (hasCover() ? anim::interpolate(-st::introCoverHeight, 0, showCoverMethod) : anim::interpolate(0, -st::introCoverHeight, hideCoverMethod));
298 
299 	paintCover(p, coverTop);
300 
301 	auto positionReady = hasCover() ? showCoverMethod : hideCoverMethod;
302 	_coverAnimation.title->paintFrame(p, positionReady, departingAlpha, arrivingAlpha);
303 	_coverAnimation.description->paintFrame(p, positionReady, departingAlpha, arrivingAlpha);
304 
305 	paintContentSnapshot(p, _coverAnimation.contentSnapshotWas, departingAlpha, showCoverMethod);
306 	paintContentSnapshot(p, _coverAnimation.contentSnapshotNow, arrivingAlpha, 1. - hideCoverMethod);
307 
308 	return true;
309 }
310 
fillSentCodeData(const MTPDauth_sentCode & data)311 void Step::fillSentCodeData(const MTPDauth_sentCode &data) {
312 	const auto &type = data.vtype();
313 	switch (type.type()) {
314 	case mtpc_auth_sentCodeTypeApp: {
315 		getData()->codeByTelegram = true;
316 		getData()->codeLength = type.c_auth_sentCodeTypeApp().vlength().v;
317 	} break;
318 	case mtpc_auth_sentCodeTypeSms: {
319 		getData()->codeByTelegram = false;
320 		getData()->codeLength = type.c_auth_sentCodeTypeSms().vlength().v;
321 	} break;
322 	case mtpc_auth_sentCodeTypeCall: {
323 		getData()->codeByTelegram = false;
324 		getData()->codeLength = type.c_auth_sentCodeTypeCall().vlength().v;
325 	} break;
326 	case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break;
327 	}
328 }
329 
showDescription()330 void Step::showDescription() {
331 	_description->show(anim::type::normal);
332 }
333 
hideDescription()334 void Step::hideDescription() {
335 	_description->hide(anim::type::normal);
336 }
337 
paintContentSnapshot(Painter & p,const QPixmap & snapshot,float64 alpha,float64 howMuchHidden)338 void Step::paintContentSnapshot(Painter &p, const QPixmap &snapshot, float64 alpha, float64 howMuchHidden) {
339 	if (!snapshot.isNull()) {
340 		auto contentTop = anim::interpolate(height() - (snapshot.height() / cIntRetinaFactor()), height(), howMuchHidden);
341 		if (contentTop < height()) {
342 			p.setOpacity(alpha);
343 			p.drawPixmap(QPoint(contentLeft(), contentTop), snapshot, QRect(0, 0, snapshot.width(), (height() - contentTop) * cIntRetinaFactor()));
344 		}
345 	}
346 }
347 
prepareCoverMask()348 void Step::prepareCoverMask() {
349 	if (!_coverMask.isNull()) return;
350 
351 	auto maskWidth = cIntRetinaFactor();
352 	auto maskHeight = st::introCoverHeight * cIntRetinaFactor();
353 	auto mask = QImage(maskWidth, maskHeight, QImage::Format_ARGB32_Premultiplied);
354 	auto maskInts = reinterpret_cast<uint32*>(mask.bits());
355 	Assert(mask.depth() == (sizeof(uint32) << 3));
356 	auto maskIntsPerLineAdded = (mask.bytesPerLine() >> 2) - maskWidth;
357 	Assert(maskIntsPerLineAdded >= 0);
358 	auto realHeight = static_cast<float64>(maskHeight - 1);
359 	for (auto y = 0; y != maskHeight; ++y) {
360 		auto color = anim::color(st::introCoverTopBg, st::introCoverBottomBg, y / realHeight);
361 		auto colorInt = anim::getPremultiplied(color);
362 		for (auto x = 0; x != maskWidth; ++x) {
363 			*maskInts++ = colorInt;
364 		}
365 		maskInts += maskIntsPerLineAdded;
366 	}
367 	_coverMask = Ui::PixmapFromImage(std::move(mask));
368 }
369 
paintCover(Painter & p,int top)370 void Step::paintCover(Painter &p, int top) {
371 	auto coverHeight = top + st::introCoverHeight;
372 	if (coverHeight > 0) {
373 		p.drawPixmap(QRect(0, 0, width(), coverHeight), _coverMask, QRect(0, -top * cIntRetinaFactor(), _coverMask.width(), coverHeight * cIntRetinaFactor()));
374 	}
375 
376 	auto left = 0;
377 	auto right = 0;
378 	if (width() < st::introCoverMaxWidth) {
379 		auto iconsMaxSkip = st::introCoverMaxWidth - st::introCoverLeft.width() - st::introCoverRight.width();
380 		auto iconsSkip = st::introCoverIconsMinSkip + (iconsMaxSkip - st::introCoverIconsMinSkip) * (width() - st::introStepWidth) / (st::introCoverMaxWidth - st::introStepWidth);
381 		auto outside = iconsSkip + st::introCoverLeft.width() + st::introCoverRight.width() - width();
382 		left = -outside / 2;
383 		right = -outside - left;
384 	}
385 	if (top < 0) {
386 		auto shown = float64(coverHeight) / st::introCoverHeight;
387 		auto leftShown = qRound(shown * (left + st::introCoverLeft.width()));
388 		left = leftShown - st::introCoverLeft.width();
389 		auto rightShown = qRound(shown * (right + st::introCoverRight.width()));
390 		right = rightShown - st::introCoverRight.width();
391 	}
392 	st::introCoverLeft.paint(p, left, coverHeight - st::introCoverLeft.height(), width());
393 	st::introCoverRight.paint(p, width() - right - st::introCoverRight.width(), coverHeight - st::introCoverRight.height(), width());
394 
395 	auto planeLeft = (width() - st::introCoverIcon.width()) / 2 - st::introCoverIconLeft;
396 	auto planeTop = top + st::introCoverIconTop;
397 	if (top < 0 && !_hasCover) {
398 		auto deltaLeft = -qRound(float64(st::introPlaneWidth / st::introPlaneHeight) * top);
399 //		auto deltaTop = top;
400 		planeLeft += deltaLeft;
401 	//	planeTop += top;
402 	}
403 	st::introCoverIcon.paint(p, planeLeft, planeTop, width());
404 }
405 
contentLeft() const406 int Step::contentLeft() const {
407 	return (width() - st::introNextButton.width) / 2;
408 }
409 
contentTop() const410 int Step::contentTop() const {
411 	auto result = (height() - st::introHeight) / 2;
412 	accumulate_max(result, st::introStepTopMin);
413 	if (_hasCover) {
414 		const auto currentHeightFull = result + st::introNextTop + st::introContentTopAdd;
415 		auto added = 1. - std::clamp(
416 			float64(currentHeightFull - st::windowMinHeight)
417 				/ (st::introStepHeightFull - st::windowMinHeight),
418 			0.,
419 			1.);
420 		result += qRound(added * st::introContentTopAdd);
421 	}
422 	return result;
423 }
424 
setErrorCentered(bool centered)425 void Step::setErrorCentered(bool centered) {
426 	_errorCentered = centered;
427 	_error.destroy();
428 }
429 
showError(rpl::producer<QString> text)430 void Step::showError(rpl::producer<QString> text) {
431 	_errorText = std::move(text);
432 }
433 
refreshError(const QString & text)434 void Step::refreshError(const QString &text) {
435 	if (text.isEmpty()) {
436 		if (_error) _error->hide(anim::type::normal);
437 	} else {
438 		if (!_error) {
439 			_error.create(
440 				this,
441 				object_ptr<Ui::FlatLabel>(
442 					this,
443 					_errorCentered
444 						? st::introErrorCentered
445 						: st::introError));
446 			_error->hide(anim::type::instant);
447 		}
448 		_error->entity()->setText(text);
449 		updateLabelsPosition();
450 		_error->show(anim::type::normal);
451 	}
452 }
453 
prepareShowAnimated(Step * after)454 void Step::prepareShowAnimated(Step *after) {
455 	setInnerFocus();
456 	if (hasCover() || after->hasCover()) {
457 		_coverAnimation = prepareCoverAnimation(after);
458 		prepareCoverMask();
459 	} else {
460 		auto leftSnapshot = after->prepareSlideAnimation();
461 		auto rightSnapshot = prepareSlideAnimation();
462 		_slideAnimation = std::make_unique<Ui::SlideAnimation>();
463 		_slideAnimation->setSnapshots(std::move(leftSnapshot), std::move(rightSnapshot));
464 		_slideAnimation->setOverflowHidden(false);
465 	}
466 }
467 
prepareCoverAnimation(Step * after)468 Step::CoverAnimation Step::prepareCoverAnimation(Step *after) {
469 	Ui::SendPendingMoveResizeEvents(this);
470 
471 	auto result = CoverAnimation();
472 	result.title = Ui::FlatLabel::CrossFade(
473 		after->_title,
474 		_title,
475 		st::introBg);
476 	result.description = Ui::FlatLabel::CrossFade(
477 		after->_description->entity(),
478 		_description->entity(),
479 		st::introBg,
480 		after->_description->pos(),
481 		_description->pos());
482 	result.contentSnapshotWas = after->prepareContentSnapshot();
483 	result.contentSnapshotNow = prepareContentSnapshot();
484 	return result;
485 }
486 
prepareContentSnapshot()487 QPixmap Step::prepareContentSnapshot() {
488 	auto otherTop = _description->y() + _description->height();
489 	auto otherRect = myrtlrect(contentLeft(), otherTop, st::introStepWidth, height() - otherTop);
490 	return Ui::GrabWidget(this, otherRect);
491 }
492 
prepareSlideAnimation()493 QPixmap Step::prepareSlideAnimation() {
494 	auto grabLeft = (width() - st::introStepWidth) / 2;
495 	auto grabTop = contentTop();
496 	return Ui::GrabWidget(
497 		this,
498 		QRect(grabLeft, grabTop, st::introStepWidth, st::introStepHeight));
499 }
500 
showAnimated(Animate animate)501 void Step::showAnimated(Animate animate) {
502 	setFocus();
503 	show();
504 	hideChildren();
505 	if (_slideAnimation) {
506 		auto slideLeft = (animate == Animate::Back);
507 		_slideAnimation->start(
508 			slideLeft,
509 			[=] { update(0, contentTop(), width(), st::introStepHeight); },
510 			st::introSlideDuration);
511 	} else {
512 		_a_show.start([this] { update(); }, 0., 1., st::introCoverDuration);
513 	}
514 }
515 
setShowAnimationClipping(QRect clipping)516 void Step::setShowAnimationClipping(QRect clipping) {
517 	_coverAnimation.clipping = clipping;
518 }
519 
setGoCallback(Fn<void (Step * step,StackAction action,Animate animate)> callback)520 void Step::setGoCallback(
521 		Fn<void(Step *step, StackAction action, Animate animate)> callback) {
522 	_goCallback = std::move(callback);
523 }
524 
setShowResetCallback(Fn<void ()> callback)525 void Step::setShowResetCallback(Fn<void()> callback) {
526 	_showResetCallback = std::move(callback);
527 }
528 
setShowTermsCallback(Fn<void ()> callback)529 void Step::setShowTermsCallback(Fn<void()> callback) {
530 	_showTermsCallback = std::move(callback);
531 }
532 
setCancelNearestDcCallback(Fn<void ()> callback)533 void Step::setCancelNearestDcCallback(Fn<void()> callback) {
534 	_cancelNearestDcCallback = std::move(callback);
535 }
536 
setAcceptTermsCallback(Fn<void (Fn<void ()> callback)> callback)537 void Step::setAcceptTermsCallback(
538 		Fn<void(Fn<void()> callback)> callback) {
539 	_acceptTermsCallback = std::move(callback);
540 }
541 
showFast()542 void Step::showFast() {
543 	show();
544 	showFinished();
545 }
546 
animating() const547 bool Step::animating() const {
548 	return (_slideAnimation && _slideAnimation->animating())
549 		|| _a_show.animating();
550 }
551 
hasCover() const552 bool Step::hasCover() const {
553 	return _hasCover;
554 }
555 
hasBack() const556 bool Step::hasBack() const {
557 	return false;
558 }
559 
activate()560 void Step::activate() {
561 	_title->show();
562 	_description->show(anim::type::instant);
563 	if (!_errorText.current().isEmpty()) {
564 		_error->show(anim::type::instant);
565 	}
566 }
567 
cancelled()568 void Step::cancelled() {
569 }
570 
finished()571 void Step::finished() {
572 	hide();
573 }
574 
575 } // namespace details
576 } // namespace Intro
577