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