/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "window/window_lock_widgets.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" #include "mainwindow.h" #include "core/application.h" #include "ui/text/text.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/labels.h" #include "ui/wrap/vertical_layout.h" #include "ui/toast/toast.h" #include "styles/style_boxes.h" #include "window/window_controller.h" #include "window/window_slide_animation.h" #include "window/window_session_controller.h" #include "main/main_session.h" namespace Window { LockWidget::LockWidget(QWidget *parent, not_null window) : RpWidget(parent) , _window(window) { show(); } void LockWidget::setInnerFocus() { if (const auto controller = _window->sessionController()) { controller->dialogsListFocused().set(false, true); } setFocus(); } void LockWidget::showAnimated(const QPixmap &bgAnimCache, bool back) { _showBack = back; (_showBack ? _cacheOver : _cacheUnder) = bgAnimCache; _a_show.stop(); showChildren(); setInnerFocus(); (_showBack ? _cacheUnder : _cacheOver) = Ui::GrabWidget(this); hideChildren(); _a_show.start( [this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); show(); } void LockWidget::animationCallback() { update(); if (!_a_show.animating()) { showChildren(); _window->widget()->setInnerFocus(); Ui::showChatsList(); _cacheUnder = _cacheOver = QPixmap(); } } void LockWidget::paintEvent(QPaintEvent *e) { Painter p(this); auto progress = _a_show.value(1.); if (_a_show.animating()) { auto coordUnder = _showBack ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); auto coordOver = _showBack ? anim::interpolate(0, width(), progress) : anim::interpolate(width(), 0, progress); auto shadow = _showBack ? (1. - progress) : progress; if (coordOver > 0) { p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * cRetinaFactor(), 0, coordOver * cRetinaFactor(), height() * cRetinaFactor())); p.setOpacity(shadow); p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); p.setOpacity(1); } p.drawPixmap(coordOver, 0, _cacheOver); p.setOpacity(shadow); st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); } else { paintContent(p); } } void LockWidget::paintContent(Painter &p) { p.fillRect(rect(), st::windowBg); } PasscodeLockWidget::PasscodeLockWidget( QWidget *parent, not_null window) : LockWidget(parent, window) , _passcode(this, st::passcodeInput, tr::lng_passcode_ph()) , _submit(this, tr::lng_passcode_submit(), st::passcodeSubmit) , _logout(this, tr::lng_passcode_logout(tr::now)) { connect(_passcode, &Ui::MaskedInputField::changed, [=] { changed(); }); connect(_passcode, &Ui::MaskedInputField::submitted, [=] { submit(); }); _submit->setClickedCallback([=] { submit(); }); _logout->setClickedCallback([=] { window->widget()->onLogout(); }); } void PasscodeLockWidget::paintContent(Painter &p) { LockWidget::paintContent(p); p.setFont(st::passcodeHeaderFont); p.setPen(st::windowFg); p.drawText(QRect(0, _passcode->y() - st::passcodeHeaderHeight, width(), st::passcodeHeaderHeight), tr::lng_passcode_enter(tr::now), style::al_center); if (!_error.isEmpty()) { p.setFont(st::boxTextFont); p.setPen(st::boxTextFgError); p.drawText(QRect(0, _passcode->y() + _passcode->height(), width(), st::passcodeSubmitSkip), _error, style::al_center); } } void PasscodeLockWidget::submit() { if (_passcode->text().isEmpty()) { _passcode->showError(); return; } if (!passcodeCanTry()) { _error = tr::lng_flood_error(tr::now); _passcode->showError(); update(); return; } const auto passcode = _passcode->text().toUtf8(); const auto correct = App::main() ? Local::checkPasscode(passcode) : (Local::readMap(passcode) != Local::ReadMapPassNeeded); if (!correct) { cSetPasscodeBadTries(cPasscodeBadTries() + 1); cSetPasscodeLastTry(crl::now()); error(); return; } Core::App().unlockPasscode(); // Destroys this widget. } void PasscodeLockWidget::error() { _error = tr::lng_passcode_wrong(tr::now); _passcode->selectAll(); _passcode->showError(); update(); } void PasscodeLockWidget::changed() { if (!_error.isEmpty()) { _error = QString(); update(); } } void PasscodeLockWidget::resizeEvent(QResizeEvent *e) { _passcode->move((width() - _passcode->width()) / 2, (height() / 3)); _submit->move(_passcode->x(), _passcode->y() + _passcode->height() + st::passcodeSubmitSkip); _logout->move(_passcode->x() + (_passcode->width() - _logout->width()) / 2, _submit->y() + _submit->height() + st::linkFont->ascent); } void PasscodeLockWidget::setInnerFocus() { LockWidget::setInnerFocus(); _passcode->setFocusFast(); } TermsLock TermsLock::FromMTP(const MTPDhelp_termsOfService &data) { const auto minAge = data.vmin_age_confirm(); return { bytes::make_vector(data.vid().c_dataJSON().vdata().v), TextWithEntities { TextUtilities::Clean(qs(data.vtext())), TextUtilities::EntitiesFromMTP(data.ventities().v) }, (minAge ? std::make_optional(minAge->v) : std::nullopt), data.is_popup() }; } TermsBox::TermsBox( QWidget*, const TermsLock &data, rpl::producer agree, rpl::producer cancel) : _data(data) , _agree(std::move(agree)) , _cancel(std::move(cancel)) { } TermsBox::TermsBox( QWidget*, const TextWithEntities &text, rpl::producer agree, rpl::producer cancel, bool attentionAgree) : _data{ {}, text, std::nullopt, false } , _agree(std::move(agree)) , _cancel(std::move(cancel)) , _attentionAgree(attentionAgree) { } rpl::producer<> TermsBox::agreeClicks() const { return _agreeClicks.events(); } rpl::producer<> TermsBox::cancelClicks() const { return _cancelClicks.events(); } void TermsBox::prepare() { setTitle(tr::lng_terms_header()); auto check = std::make_unique(st::defaultCheck, false); const auto ageCheck = check.get(); const auto age = _data.minAge ? Ui::CreateChild>( this, object_ptr( this, tr::lng_terms_age(tr::now, lt_count, *_data.minAge), st::defaultCheckbox, std::move(check)), st::termsAgePadding) : nullptr; if (age) { age->resizeToNaturalWidth(st::boxWideWidth); } const auto content = setInnerWidget( object_ptr>( this, object_ptr ( this, rpl::single(_data.text), st::termsContent), st::termsPadding), 0, age ? age->height() : 0); content->entity()->setClickHandlerFilter([=]( const ClickHandlerPtr &handler, Qt::MouseButton button) { const auto link = handler ? handler->copyToClipboardText() : QString(); if (TextUtilities::RegExpMention().match(link).hasMatch()) { _lastClickedMention = link; Ui::Toast::Show(tr::lng_terms_agree_to_proceed(tr::now, lt_bot, link)); return false; } return true; }); const auto errorAnimationCallback = [=] { const auto check = ageCheck; const auto error = _ageErrorAnimation.value( _ageErrorShown ? 1. : 0.); if (error == 0.) { check->setUntoggledOverride(std::nullopt); } else { const auto color = anim::color( st::defaultCheck.untoggledFg, st::boxTextFgError, error); check->setUntoggledOverride(color); } }; const auto toggleAgeError = [=](bool shown) { if (_ageErrorShown != shown) { _ageErrorShown = shown; _ageErrorAnimation.start( [=] { errorAnimationCallback(); }, _ageErrorShown ? 0. : 1., _ageErrorShown ? 1. : 0., st::defaultCheck.duration); } }; const auto &agreeStyle = _attentionAgree ? st::attentionBoxButton : st::defaultBoxButton; addButton(std::move(_agree), [=] {}, agreeStyle)->clicks( ) | rpl::filter([=] { if (age && !age->entity()->checked()) { toggleAgeError(true); return false; } return true; }) | rpl::map([] { return rpl::empty_value(); }) | rpl::start_to_stream(_agreeClicks, lifetime()); if (_cancel) { addButton(std::move(_cancel), [=] {})->clicks( ) | rpl::map([] { return rpl::empty_value(); }) | rpl::start_to_stream(_cancelClicks, lifetime()); } if (age) { age->entity()->checkedChanges( ) | rpl::start_with_next([=] { toggleAgeError(false); }, age->lifetime()); heightValue( ) | rpl::start_with_next([=](int height) { age->moveToLeft(0, height - age->height()); }, age->lifetime()); } content->resizeToWidth(st::boxWideWidth); using namespace rpl::mappers; rpl::combine( content->heightValue(), age ? age->heightValue() : rpl::single(0), _1 + _2 ) | rpl::start_with_next([=](int height) { setDimensions(st::boxWideWidth, height); }, content->lifetime()); } void TermsBox::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { _agreeClicks.fire({}); } else { BoxContent::keyPressEvent(e); } } QString TermsBox::lastClickedMention() const { return _lastClickedMention; } } // namespace Window