intro_step.cpp 18 KB

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