diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f1bc07274..3714be0fe 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1493,6 +1493,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_info_feed_is_default" = "Group new channels"; "lng_info_feed_channels" = "Channels"; +"lng_terms_signup" = "By signing up,\nyou agree to the {link}."; +"lng_terms_signup_link" = "Terms of Service"; +"lng_terms_header" = "Terms of Service"; +"lng_terms_agree" = "Agree & Continue"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/boxes/confirm_box.cpp b/Telegram/SourceFiles/boxes/confirm_box.cpp index e2578d0da..adce3a044 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.cpp +++ b/Telegram/SourceFiles/boxes/confirm_box.cpp @@ -119,9 +119,19 @@ void ConfirmBox::prepare() { textUpdated(); } +void ConfirmBox::setMaxLineCount(int count) { + if (_maxLineCount != count) { + _maxLineCount = count; + textUpdated(); + } +} + void ConfirmBox::textUpdated() { _textWidth = st::boxWidth - st::boxPadding.left() - st::boxButtonPadding.right(); - _textHeight = qMin(_text.countHeight(_textWidth), 16 * st::boxLabelStyle.lineHeight); + _textHeight = _text.countHeight(_textWidth); + if (_maxLineCount > 0) { + accumulate_min(_textHeight, _maxLineCount * st::boxLabelStyle.lineHeight); + } setDimensions(st::boxWidth, st::boxPadding.top() + _textHeight + st::boxPadding.bottom()); setMouseTracking(_text.hasLinks()); @@ -198,7 +208,11 @@ void ConfirmBox::paintEvent(QPaintEvent *e) { // draw box title / text p.setPen(st::boxTextFg); - _text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), 16, style::al_left); + if (_maxLineCount > 0) { + _text.drawLeftElided(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), _maxLineCount, style::al_left); + } else { + _text.drawLeft(p, st::boxPadding.left(), st::boxPadding.top(), _textWidth, width(), style::al_left); + } } InformBox::InformBox(QWidget*, const QString &text, base::lambda closedCallback) : ConfirmBox(ConfirmBox::InformBoxTag(), text, lang(lng_box_ok), std::move(closedCallback)) { diff --git a/Telegram/SourceFiles/boxes/confirm_box.h b/Telegram/SourceFiles/boxes/confirm_box.h index 9b64eea40..1f5e21714 100644 --- a/Telegram/SourceFiles/boxes/confirm_box.h +++ b/Telegram/SourceFiles/boxes/confirm_box.h @@ -31,6 +31,8 @@ public: _strictCancel = strictCancel; } + void setMaxLineCount(int count); + // ClickHandlerHost interface void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override; void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; @@ -55,6 +57,7 @@ private: void confirmed(); void init(const QString &text); void textUpdated(); + void updateHover(); QString _confirmText; QString _cancelText; @@ -64,8 +67,7 @@ private: Text _text; int _textWidth = 0; int _textHeight = 0; - - void updateHover(); + int _maxLineCount = 16; QPoint _lastMousePos; diff --git a/Telegram/SourceFiles/intro/intro.style b/Telegram/SourceFiles/intro/intro.style index a10a8a778..bedc293c3 100644 --- a/Telegram/SourceFiles/intro/intro.style +++ b/Telegram/SourceFiles/intro/intro.style @@ -125,6 +125,15 @@ introResetButton: RoundButton(defaultLightButton) { } introResetBottom: 20px; +introTermsLabel: FlatLabel(defaultFlatLabel) { + align: align(top); +} +introTermsBottom: 20px; +introTermsContent: FlatLabel(defaultFlatLabel) { + minWidth: 285px; +} +introTermsPadding: margins(23px, 0px, 16px, 0px); + introCountryIcon: icon {{ "intro_country_dropdown", menuIconFg }}; introCountryIconPosition: point(8px, 37px); diff --git a/Telegram/SourceFiles/intro/introphone.cpp b/Telegram/SourceFiles/intro/introphone.cpp index f337b7110..bb3a3fefc 100644 --- a/Telegram/SourceFiles/intro/introphone.cpp +++ b/Telegram/SourceFiles/intro/introphone.cpp @@ -48,6 +48,15 @@ Locale: ") + Platform::SystemLanguage(); UrlClickHandler::doOpen(url); } +bool TermsAcceptRequired(const QString &countryCode) { + const auto codes = std::vector{ + "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", + "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", + "RO", "SK", "SI", "ES", "SE", "GB" + }; + return ranges::find(codes, countryCode.toUpper()) != end(codes); +} + } // namespace PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, data) @@ -64,6 +73,16 @@ PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, dat connect(_phone, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_code, SIGNAL(changed()), this, SLOT(onInputChange())); connect(_checkRequest, SIGNAL(timeout()), this, SLOT(onCheckRequest())); + connect( + _code, + &Ui::CountryCodeInput::codeChanged, + this, + &PhoneWidget::toggleTerms); + connect( + _country, + &CountryInput::codeChanged, + this, + &PhoneWidget::toggleTerms); setTitleText(langFactory(lng_phone_title)); setDescriptionText(langFactory(lng_phone_desc)); @@ -75,6 +94,10 @@ PhoneWidget::PhoneWidget(QWidget *parent, Widget::Data *data) : Step(parent, dat } _changed = false; + subscribe(Lang::Current().updated(), [this] { + _termsAccepted = false; + }); + Messenger::Instance().destroyStaleAuthorizationKeys(); } @@ -129,6 +152,13 @@ void PhoneWidget::countryChanged() { } } +void PhoneWidget::toggleTerms() { + _termsAccepted = false; + InvokeQueued(this, [=] { + Step::toggleTerms(_country->iso()); + }); +} + void PhoneWidget::onInputChange() { _changed = true; hidePhoneError(); @@ -143,14 +173,33 @@ void PhoneWidget::submit() { return; } - hidePhoneError(); + const auto sendCode = [=] { + hidePhoneError(); - _checkRequest->start(1000); + _checkRequest->start(1000); - _sentPhone = fullNumber(); - Messenger::Instance().mtp()->setUserPhone(_sentPhone); - //_sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&PhoneWidget::phoneCheckDone), rpcFail(&PhoneWidget::phoneSubmitFail)); - _sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(0), MTP_string(_sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash)), rpcDone(&PhoneWidget::phoneSubmitDone), rpcFail(&PhoneWidget::phoneSubmitFail)); + _sentPhone = fullNumber(); + Messenger::Instance().mtp()->setUserPhone(_sentPhone); + //_sentRequest = MTP::send(MTPauth_CheckPhone(MTP_string(_sentPhone)), rpcDone(&PhoneWidget::phoneCheckDone), rpcFail(&PhoneWidget::phoneSubmitFail)); + _sentRequest = MTP::send( + MTPauth_SendCode( + MTP_flags(0), + MTP_string(_sentPhone), + MTPBool(), + MTP_int(ApiId), + MTP_string(ApiHash)), + rpcDone(&PhoneWidget::phoneSubmitDone), + rpcFail(&PhoneWidget::phoneSubmitFail)); + }; + const auto code = _country->iso(); + if (!TermsAcceptRequired(code) || _termsAccepted) { + sendCode(); + } else { + acceptTerms(code, base::lambda_guarded(this, [=] { + _termsAccepted = true; + sendCode(); + })); + } } void PhoneWidget::stopCheck() { diff --git a/Telegram/SourceFiles/intro/introphone.h b/Telegram/SourceFiles/intro/introphone.h index 975d9b0a4..a1ba816e6 100644 --- a/Telegram/SourceFiles/intro/introphone.h +++ b/Telegram/SourceFiles/intro/introphone.h @@ -47,6 +47,7 @@ private slots: private: void updateSignupGeometry(); void countryChanged(); + void toggleTerms(); //void phoneCheckDone(const MTPauth_CheckedPhone &result); void phoneSubmitDone(const MTPauth_SentCode &result); @@ -66,6 +67,7 @@ private: object_ptr _country; object_ptr _code; object_ptr _phone; + bool _termsAccepted = false; object_ptr> _signup = { nullptr }; diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index d2923ab89..764162246 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -41,6 +41,67 @@ namespace { constexpr str_const kDefaultCountry = "US"; +class TermsBox : public BoxContent { +public: + TermsBox( + QWidget*, + const QString &text, + base::lambda button); + + rpl::producer<> agreeClicks() const; + +protected: + void prepare() override; + + void keyPressEvent(QKeyEvent *e) override; + +private: + QString _text; + base::lambda _button; + rpl::event_stream<> _agreeClicks; + +}; + +TermsBox::TermsBox( + QWidget*, + const QString &text, + base::lambda button) +: _text(text) +, _button(button) { +} + +rpl::producer<> TermsBox::agreeClicks() const { + return _agreeClicks.events(); +} + +void TermsBox::prepare() { + setTitle(langFactory(lng_terms_header)); + addButton(_button, [=] {})->clicks( + ) | rpl::start_to_stream(_agreeClicks, lifetime()); + + const auto content = Ui::CreateChild>( + this, + object_ptr( + this, + _text, + Ui::FlatLabel::InitType::Rich, + st::introTermsContent), + st::introTermsPadding); + content->resizeToWidth(st::boxWideWidth); + content->heightValue( + ) | 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); + } +} + } // namespace Widget::Widget(QWidget *parent) : RpWidget(parent) @@ -122,7 +183,9 @@ void Widget::createLanguageLink() { _changeLanguage->entity()->setClickedCallback([=] { Lang::CurrentCloudManager().switchToLanguage(languageId); }); - _changeLanguage->toggle(!_resetAccount, anim::type::normal); + _changeLanguage->toggle( + !_resetAccount && !_terms, + anim::type::normal); updateControlsGeometry(); }; @@ -209,14 +272,36 @@ void Widget::historyMove(Direction direction) { auto stepHasCover = getStep()->hasCover(); _settings->toggle(!stepHasCover, anim::type::normal); - if (_update) _update->toggle(!stepHasCover, anim::type::normal); - if (_changeLanguage) _changeLanguage->toggle(!_resetAccount, anim::type::normal); + if (_update) { + _update->toggle(!stepHasCover, anim::type::normal); + } _next->setText([this] { return getStep()->nextButtonText(); }); - if (_resetAccount) _resetAccount->hide(anim::type::normal); + if (_resetAccount) { + hideAndDestroy(std::exchange(_resetAccount, { nullptr })); + } + if (_terms) { + hideAndDestroy(std::exchange(_terms, { nullptr })); + } + if (_changeLanguage) { + _changeLanguage->toggle( + !_resetAccount && !_terms, + anim::type::normal); + } getStep()->showAnimated(direction); fixOrder(); } +void Widget::hideAndDestroy(object_ptr> widget) { + const auto weak = make_weak(widget.data()); + widget->hide(anim::type::normal); + widget->shownValue( + ) | rpl::start_with_next([=](bool shown) { + if (!shown && weak) { + weak->deleteLater(); + } + }, widget->lifetime()); +} + void Widget::fixOrder() { _next->raise(); if (_update) _update->raise(); @@ -240,30 +325,74 @@ void Widget::moveToStep(Step *step, Direction direction) { void Widget::appendStep(Step *step) { _stepHistory.push_back(step); step->setGeometry(calculateStepRect()); - step->setGoCallback([this](Step *step, Direction direction) { + step->setGoCallback([=](Step *step, Direction direction) { if (direction == Direction::Back) { historyMove(direction); } else { moveToStep(step, direction); } }); - step->setShowResetCallback([this] { + step->setShowResetCallback([=] { showResetButton(); }); + step->setToggleTermsCallback([=](QString countryCode) { + toggleTerms(countryCode); + }); + step->setAcceptTermsCallback([=]( + QString countryCode, + base::lambda callback) { + acceptTerms(countryCode, callback); + }); } void Widget::showResetButton() { if (!_resetAccount) { auto entity = object_ptr(this, langFactory(lng_signin_reset_account), st::introResetButton); - _resetAccount.create( - this, - std::move(entity)); + _resetAccount.create(this, std::move(entity)); _resetAccount->hide(anim::type::instant); _resetAccount->entity()->setClickedCallback([this] { resetAccount(); }); updateControlsGeometry(); } _resetAccount->show(anim::type::normal); - if (_changeLanguage) _changeLanguage->hide(anim::type::normal); + if (_changeLanguage) { + _changeLanguage->hide(anim::type::normal); + } +} + +void Widget::toggleTerms(const QString &countryCode) { + _termsCountryCode = countryCode; + if (countryCode.isEmpty()) { + if (_terms) hideAndDestroy(std::exchange(_terms, { nullptr })); + } else { + if (!_terms) { + auto entity = object_ptr( + this, + lng_terms_signup( + lt_link, + textcmdLink(1, lang(lng_terms_signup_link))), + Ui::FlatLabel::InitType::Rich, + st::introTermsLabel); + _terms.create(this, std::move(entity)); + _terms->hide(anim::type::instant); + _terms->entity()->setLink( + 1, + std::make_shared([=] { showTerms(); })); + updateControlsGeometry(); + } + _terms->toggle(!_termsCountryCode.isEmpty(), anim::type::normal); + } + if (_changeLanguage) { + _changeLanguage->toggle( + !_terms && !_resetAccount, + anim::type::normal); + } +} + +void Widget::acceptTerms( + const QString &countryCode, + base::lambda callback) { + _termsCountryCode = countryCode; + showTerms(callback); } void Widget::resetAccount() { @@ -308,7 +437,10 @@ void Widget::resetAccount() { void Widget::getNearestDC() { request(MTPhelp_GetNearestDc()).done([this](const MTPNearestDc &result) { auto &nearest = result.c_nearestDc(); - DEBUG_LOG(("Got nearest dc, country: %1, nearest: %2, this: %3").arg(qs(nearest.vcountry)).arg(nearest.vnearest_dc.v).arg(nearest.vthis_dc.v)); + DEBUG_LOG(("Got nearest dc, country: %1, nearest: %2, this: %3" + ).arg(qs(nearest.vcountry) + ).arg(nearest.vnearest_dc.v + ).arg(nearest.vthis_dc.v)); Messenger::Instance().suggestMainDcId(nearest.vnearest_dc.v); auto nearestCountry = qs(nearest.vcountry); if (getData()->country != nearestCountry) { @@ -318,6 +450,56 @@ void Widget::getNearestDC() { }).send(); } +void Widget::showTerms(base::lambda callback) { + if (_termsCountryCode.isEmpty()) { + return; + } + const auto showLastTerms = [=] { + const auto box = Ui::show(Box( + _termsLastText, + langFactory(callback ? lng_terms_agree : lng_box_ok))); + box->agreeClicks( + ) | rpl::start_with_next([=] { + if (callback) { + callback(); + } + if (box) { + box->closeBox(); + } + }, box->lifetime()); + }; + const auto langPack = Lang::Current().id(); + const auto code = _termsCountryCode; + if (_termsLastCountryCode == code && _termsLastLangPack == langPack) { + showLastTerms(); + return; + } + + request(MTPhelp_GetTermsOfService( + MTP_string(code) + )).done([=](const MTPhelp_TermsOfService &result) { + const auto text = qs(result.c_help_termsOfService().vtext); + const auto match = QRegularExpression("\\[([^\\]]+)\\]").match(text); + const auto linked = [&] { + if (!match.hasMatch()) { + return text; + } + const auto from = match.capturedStart(0); + const auto till = from + match.capturedLength(0); + return text.mid(0, from) + + textcmdLink( + "http://telegram.org/privacy", + text.mid(from + 1, till - from - 2)) + + text.mid(till); + }(); + _termsLastCountryCode = code; + _termsLastLangPack = langPack; + _termsLastText = linked; + + showLastTerms(); + }).send(); +} + void Widget::showControls() { getStep()->show(); _next->show(); @@ -325,8 +507,14 @@ void Widget::showControls() { _connecting->setForceHidden(false); auto hasCover = getStep()->hasCover(); _settings->toggle(!hasCover, anim::type::instant); - if (_update) _update->toggle(!hasCover, anim::type::instant); - if (_changeLanguage) _changeLanguage->toggle(!_resetAccount, anim::type::instant); + if (_update) { + _update->toggle(!hasCover, anim::type::instant); + } + if (_changeLanguage) { + _changeLanguage->toggle( + !_resetAccount && !_terms, + anim::type::instant); + } _back->toggle(getStep()->hasBack(), anim::type::instant); } @@ -434,6 +622,9 @@ void Widget::updateControlsGeometry() { if (_resetAccount) { _resetAccount->moveToLeft((width() - _resetAccount->width()) / 2, height() - st::introResetBottom - _resetAccount->height()); } + if (_terms) { + _terms->moveToLeft((width() - _terms->width()) / 2, height() - st::introTermsBottom - _terms->height()); + } } @@ -833,6 +1024,17 @@ void Widget::Step::setShowResetCallback(base::lambda callback) { _showResetCallback = std::move(callback); } +void Widget::Step::setToggleTermsCallback( + base::lambda callback) { + _toggleTermsCallback = std::move(callback); +} + +void Widget::Step::setAcceptTermsCallback(base::lambda callback)> callback) { + _acceptTermsCallback = std::move(callback); +} + void Widget::Step::showFast() { show(); showFinished(); diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h index 0657ea546..23e6774af 100644 --- a/Telegram/SourceFiles/intro/introwidget.h +++ b/Telegram/SourceFiles/intro/introwidget.h @@ -94,8 +94,14 @@ public: setFocus(); } - void setGoCallback(base::lambda callback); + void setGoCallback( + base::lambda callback); void setShowResetCallback(base::lambda callback); + void setToggleTermsCallback( + base::lambda callback); + void setAcceptTermsCallback(base::lambda callback)> callback); void prepareShowAnimated(Step *after); void showAnimated(Direction direction); @@ -153,6 +159,16 @@ public: void showResetButton() { if (_showResetCallback) _showResetCallback(); } + void toggleTerms(const QString &countryCode) { + if (_toggleTermsCallback) _toggleTermsCallback(countryCode); + } + void acceptTerms( + const QString &countryCode, + base::lambda callback) { + if (_acceptTermsCallback) { + _acceptTermsCallback(countryCode, callback); + } + } private: struct CoverAnimation { @@ -187,6 +203,10 @@ public: bool _hasCover = false; base::lambda _goCallback; base::lambda _showResetCallback; + base::lambda _toggleTermsCallback; + base::lambda callback)> _acceptTermsCallback; object_ptr _title; base::lambda _titleTextFactory; @@ -224,6 +244,12 @@ private: void showResetButton(); void resetAccount(); + void toggleTerms(const QString &countryCode); + void acceptTerms( + const QString &countryCode, + base::lambda callback); + void hideAndDestroy(object_ptr> widget); + Step *getStep(int skip = 0) { Assert(_stepHistory.size() + skip > 0); return _stepHistory.at(_stepHistory.size() - skip - 1); @@ -233,6 +259,7 @@ private: void appendStep(Step *step); void getNearestDC(); + void showTerms(base::lambda callback = nullptr); Animation _a_show; bool _showBack = false; @@ -253,6 +280,11 @@ private: object_ptr _next; object_ptr> _changeLanguage = { nullptr }; object_ptr> _resetAccount = { nullptr }; + object_ptr> _terms = { nullptr }; + QString _termsCountryCode; + QString _termsLastCountryCode; + QString _termsLastLangPack; + QString _termsLastText; base::unique_qptr _connecting; diff --git a/Telegram/SourceFiles/ui/countryinput.cpp b/Telegram/SourceFiles/ui/countryinput.cpp index 3c31d8e01..dfdd10085 100644 --- a/Telegram/SourceFiles/ui/countryinput.cpp +++ b/Telegram/SourceFiles/ui/countryinput.cpp @@ -162,11 +162,12 @@ void CountryInput::leaveEventHook(QEvent *e) { void CountryInput::onChooseCode(const QString &code) { Ui::hideLayer(); + _chosenIso = QString(); if (code.length()) { CountriesByCode::const_iterator i = _countriesByCode.constFind(code); if (i != _countriesByCode.cend()) { const CountryInfo *info = *i; - lastValidISO = info->iso2; + _chosenIso = lastValidISO = info->iso2; setText(QString::fromUtf8(info->name)); } else { setText(lang(lng_bad_country_code)); @@ -183,8 +184,9 @@ bool CountryInput::onChooseCountry(const QString &iso) { CountriesByISO2::const_iterator i = _countriesByISO2.constFind(iso); const CountryInfo *info = (i == _countriesByISO2.cend()) ? 0 : (*i); + _chosenIso = QString(); if (info) { - lastValidISO = info->iso2; + _chosenIso = lastValidISO = info->iso2; setText(QString::fromUtf8(info->name)); emit codeChanged(info->code); update(); diff --git a/Telegram/SourceFiles/ui/countryinput.h b/Telegram/SourceFiles/ui/countryinput.h index acdc7601b..f62abac10 100644 --- a/Telegram/SourceFiles/ui/countryinput.h +++ b/Telegram/SourceFiles/ui/countryinput.h @@ -23,6 +23,10 @@ class CountryInput : public TWidget { public: CountryInput(QWidget *parent, const style::InputField &st); + QString iso() const { + return _chosenIso; + } + public slots: void onChooseCode(const QString &code); bool onChooseCountry(const QString &country); @@ -43,6 +47,7 @@ private: const style::InputField &_st; bool _active = false; QString _text; + QString _chosenIso; QPainterPath _placeholderPath; };