From 21fab77c4b16f355f9614db97e683c644adac0e8 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 20 Nov 2018 14:03:44 +0400 Subject: [PATCH] Add support for incomplete login attempts. --- Telegram/Resources/langs/lang.strings | 3 + Telegram/SourceFiles/boxes/abstract_box.cpp | 4 +- Telegram/SourceFiles/boxes/boxes.style | 2 +- Telegram/SourceFiles/boxes/sessions_box.cpp | 745 ++++++++++++-------- Telegram/SourceFiles/boxes/sessions_box.h | 90 +-- Telegram/SourceFiles/config.h | 1 - Telegram/SourceFiles/data/data_session.cpp | 8 + Telegram/SourceFiles/data/data_session.h | 4 + Telegram/SourceFiles/info/info.style | 4 + Telegram/SourceFiles/mainwidget.cpp | 2 +- Telegram/SourceFiles/mainwindow.h | 1 - 11 files changed, 501 insertions(+), 363 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c082ccbe9a..a5bbddb4e2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -549,6 +549,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_sessions_other_header" = "Active sessions"; "lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized."; "lng_sessions_terminate_all" = "Terminate all other sessions"; +"lng_sessions_terminate_all_about" = "Logs out all devices except for this one."; +"lng_sessions_incomplete" = "Incomplete login attempts"; +"lng_sessions_incomplete_about" = "The devices above have no access to your messages. The code was entered correctly, but no correct password was given."; "lng_blocked_list_title" = "Blocked users"; "lng_blocked_list_unknown_phone" = "unknown phone number"; diff --git a/Telegram/SourceFiles/boxes/abstract_box.cpp b/Telegram/SourceFiles/boxes/abstract_box.cpp index 5e141167c2..92d71134d8 100644 --- a/Telegram/SourceFiles/boxes/abstract_box.cpp +++ b/Telegram/SourceFiles/boxes/abstract_box.cpp @@ -77,7 +77,9 @@ void BoxContent::finishPrepare() { void BoxContent::finishScrollCreate() { Expects(_scroll != nullptr); - _scroll->show(); + if (!_scroll->isHidden()) { + _scroll->show(); + } updateScrollAreaGeometry(); connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(_scroll, SIGNAL(innerResized()), this, SLOT(onInnerResize())); diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 9293400c8e..e71804f69d 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -443,7 +443,7 @@ sessionsHeight: 350px; sessionHeight: 70px; sessionCurrentPadding: margins(0px, 7px, 0px, 4px); sessionCurrentHeight: 118px; -sessionPadding: margins(21px, 10px, 21px, 0px); +sessionPadding: margins(23px, 10px, 23px, 0px); sessionNameFont: msgNameFont; sessionNameFg: boxTextFg; sessionWhenFont: msgDateFont; diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index 04bfd1b861..35dcef99a3 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -12,34 +12,100 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "mainwindow.h" #include "countries.h" +#include "auth_session.h" +#include "data/data_session.h" #include "boxes/confirm_box.h" +#include "info/profile/info_profile_button.h" +#include "settings/settings_common.h" #include "ui/widgets/buttons.h" #include "ui/widgets/scroll_area.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" #include "styles/style_boxes.h" +#include "styles/style_info.h" +#include "styles/style_settings.h" + +namespace { + +constexpr auto kSessionsShortPollTimeout = 60 * TimeMs(1000); + +} // namespace + +class SessionsBox::List : public Ui::RpWidget { +public: + List(QWidget *parent); + + void showData(gsl::span items); + rpl::producer itemsCount() const; + rpl::producer terminate() const; + + void terminating(uint64 hash, bool terminating); + +protected: + void paintEvent(QPaintEvent *e) override; + + int resizeGetHeight(int newWidth) override; + +private: + std::vector _items; + std::map> _terminateButtons; + rpl::event_stream _terminate; + rpl::event_stream _itemsCount; + +}; + +class SessionsBox::Inner : public Ui::RpWidget { +public: + Inner(QWidget *parent); + + void showData(const Full &data); + rpl::producer terminateOne() const; + rpl::producer<> terminateAll() const; + + void terminatingOne(uint64 hash, bool terminating); + +private: + void setupContent(); + + QPointer _current; + QPointer _terminateAll; + QPointer _incomplete; + QPointer _list; + +}; SessionsBox::SessionsBox(QWidget*) -: _shortPollTimer(this) { +: _shortPollTimer([=] { shortPollSessions(); }) { } void SessionsBox::prepare() { setTitle(langFactory(lng_sessions_other_header)); - addButton(langFactory(lng_close), [this] { closeBox(); }); + addButton(langFactory(lng_close), [=] { closeBox(); }); setDimensions(st::boxWideWidth, st::sessionsHeight); - _inner = setInnerWidget(object_ptr(this, &_list, &_current), st::sessionsScroll); + _inner = setInnerWidget(object_ptr(this), st::sessionsScroll); _inner->resize(width(), st::noContactsHeight); - connect(_inner, SIGNAL(oneTerminated()), this, SLOT(onOneTerminated())); - connect(_inner, SIGNAL(allTerminated()), this, SLOT(onAllTerminated())); - connect(_inner, SIGNAL(terminateAll()), this, SLOT(onTerminateAll())); - connect(App::wnd(), SIGNAL(checkNewAuthorization()), this, SLOT(onCheckNewAuthorization())); - connect(_shortPollTimer, SIGNAL(timeout()), this, SLOT(onShortPollAuthorizations())); + _inner->terminateOne( + ) | rpl::start_with_next([=](uint64 hash) { + terminateOne(hash); + }, lifetime()); + + _inner->terminateAll( + ) | rpl::start_with_next([=] { + terminateAll(); + }, lifetime()); + + Auth().data().newAuthorizationChecks( + ) | rpl::start_with_next([=] { + shortPollSessions(); + }, lifetime()); setLoading(true); - - MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); + shortPollSessions(); } void SessionsBox::setLoading(bool loading) { @@ -63,331 +129,420 @@ void SessionsBox::paintEvent(QPaintEvent *e) { if (_loading) { p.setFont(st::noContactsFont); p.setPen(st::noContactsColor); - p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); + p.drawText( + QRect(0, 0, width(), st::noContactsHeight), + lang(lng_contacts_loading), + style::al_center); } } -void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { +void SessionsBox::got(const MTPaccount_Authorizations &result) { _shortPollRequest = 0; setLoading(false); + _data = Full(); - const auto availCurrent = st::boxWideWidth - st::sessionPadding.left() - st::sessionTerminateSkip; - const auto availOther = availCurrent - st::sessionTerminate.iconPosition.x(); - const auto availInfo = availCurrent - st::sessionTerminate.width; - - _list.clear(); - if (result.type() != mtpc_account_authorizations) { - return; - } - auto &v = result.c_account_authorizations().vauthorizations.v; - _list.reserve(v.size()); - - const CountriesByISO2 &countries(countriesByISO2()); - - for_const (auto &auth, v) { - if (auth.type() != mtpc_authorization) { - continue; - } - auto &d = auth.c_authorization(); - Data data; - data.hash = d.vhash.v; - - QString appName, appVer = qs(d.vapp_version), systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model); - if (d.vapi_id.v == 2040 || d.vapi_id.v == 17349) { - appName = (d.vapi_id.v == 2040) ? qstr("Telegram Desktop") : qstr("Telegram Desktop (GitHub)"); - // if (systemVer == qstr("windows")) { - // deviceModel = qsl("Windows"); - // } else if (systemVer == qstr("os x")) { - // deviceModel = qsl("OS X"); - // } else if (systemVer == qstr("linux")) { - // deviceModel = qsl("Linux"); - // } - if (appVer == QString::number(appVer.toInt())) { - int32 ver = appVer.toInt(); - appVer = QString("%1.%2").arg(ver / 1000000).arg((ver % 1000000) / 1000) + ((ver % 1000) ? ('.' + QString::number(ver % 1000)) : QString()); - //} else { - // appVer = QString(); - } - } else { - appName = qs(d.vapp_name);// +qsl(" for ") + qs(d.vplatform); - if (appVer.indexOf('(') >= 0) appVer = appVer.mid(appVer.indexOf('(')); - } - data.name = appName; - if (!appVer.isEmpty()) data.name += ' ' + appVer; - data.nameWidth = st::sessionNameFont->width(data.name); - - QString country = qs(d.vcountry), platform = qs(d.vplatform); - //CountriesByISO2::const_iterator j = countries.constFind(country); - //if (j != countries.cend()) country = QString::fromUtf8(j.value()->name); - - const auto active = data.activeTime = d.vdate_active.v - ? d.vdate_active.v - : d.vdate_created.v; - - data.info = qs(d.vdevice_model) + qstr(", ") + (platform.isEmpty() ? QString() : platform + ' ') + qs(d.vsystem_version); - data.ip = qs(d.vip) + (country.isEmpty() ? QString() : QString::fromUtf8(" \xe2\x80\x93 ") + country); - if (!data.hash || (d.vflags.v & 1)) { - data.active = lang(lng_sessions_header); - data.activeWidth = st::sessionWhenFont->width(lang(lng_sessions_header)); - int32 availForName = availCurrent - st::sessionPadding.right() - data.activeWidth; - if (data.nameWidth > availForName) { - data.name = st::sessionNameFont->elided(data.name, availForName); - data.nameWidth = st::sessionNameFont->width(data.name); - } - data.infoWidth = st::sessionInfoFont->width(data.info); - if (data.infoWidth > availCurrent) { - data.info = st::sessionInfoFont->elided(data.info, availCurrent); - data.infoWidth = st::sessionInfoFont->width(data.info); - } - data.ipWidth = st::sessionInfoFont->width(data.ip); - if (data.ipWidth > availCurrent) { - data.ip = st::sessionInfoFont->elided(data.ip, availCurrent); - data.ipWidth = st::sessionInfoFont->width(data.ip); - } - _current = data; - } else { - const auto now = QDateTime::currentDateTime(); - const auto lastTime = ParseDateTime(active); - const auto nowDate = now.date(); - const auto lastDate = lastTime.date(); - QString dt; - if (lastDate == nowDate) { - data.active = lastTime.toString(cTimeFormat()); - } else if (lastDate.year() == nowDate.year() - && lastDate.weekNumber() == nowDate.weekNumber()) { - data.active = langDayOfWeek(lastDate); - } else { - data.active = lastDate.toString(qsl("d.MM.yy")); - } - data.activeWidth = st::sessionWhenFont->width(data.active); - int32 availForName = availOther - st::sessionPadding.right() - data.activeWidth; - if (data.nameWidth > availForName) { - data.name = st::sessionNameFont->elided(data.name, availForName); - data.nameWidth = st::sessionNameFont->width(data.name); - } - data.infoWidth = st::sessionInfoFont->width(data.info); - if (data.infoWidth > availInfo) { - data.info = st::sessionInfoFont->elided(data.info, availInfo); - data.infoWidth = st::sessionInfoFont->width(data.info); - } - data.ipWidth = st::sessionInfoFont->width(data.ip); - if (data.ipWidth > availOther) { - data.ip = st::sessionInfoFont->elided(data.ip, availOther); - data.ipWidth = st::sessionInfoFont->width(data.ip); - } - - _list.push_back(data); - for (int32 i = _list.size(); i > 1;) { - --i; - if (_list.at(i).activeTime > _list.at(i - 1).activeTime) { - qSwap(_list[i], _list[i - 1]); + result.match([&](const MTPDaccount_authorizations &data) { + const auto &list = data.vauthorizations.v; + for (const auto &auth : list) { + auth.match([&](const MTPDauthorization &data) { + auto entry = ParseEntry(data); + if (!entry.hash) { + _data.current = std::move(entry); + } else if (entry.incomplete) { + _data.incomplete.push_back(std::move(entry)); + } else { + _data.list.push_back(std::move(entry)); } - } + }); + } + }); + + const auto getActiveTime = [](const Entry &entry) { + return entry.activeTime; + }; + ranges::sort(_data.list, std::greater<>(), getActiveTime); + ranges::sort(_data.incomplete, std::greater<>(), getActiveTime); + + _inner->showData(_data); + + _shortPollTimer.callOnce(kSessionsShortPollTimeout); +} + +SessionsBox::Entry SessionsBox::ParseEntry(const MTPDauthorization &data) { + auto result = Entry(); + + result.hash = data.is_current() ? 0 : data.vhash.v; + result.incomplete = data.is_password_pending(); + + auto appName = QString(); + auto appVer = qs(data.vapp_version); + const auto systemVer = qs(data.vsystem_version); + const auto deviceModel = qs(data.vdevice_model); + const auto apiId = data.vapi_id.v; + if (apiId == 2040 || apiId == 17349) { + appName = (apiId == 2040) + ? qstr("Telegram Desktop") + : qstr("Telegram Desktop (GitHub)"); + //if (systemVer == qstr("windows")) { + // deviceModel = qsl("Windows"); + //} else if (systemVer == qstr("os x")) { + // deviceModel = qsl("OS X"); + //} else if (systemVer == qstr("linux")) { + // deviceModel = qsl("Linux"); + //} + if (appVer == QString::number(appVer.toInt())) { + const auto ver = appVer.toInt(); + appVer = QString("%1.%2" + ).arg(ver / 1000000 + ).arg((ver % 1000000) / 1000) + + ((ver % 1000) + ? ('.' + QString::number(ver % 1000)) + : QString()); + //} else { + // appVer = QString(); + } + } else { + appName = qs(data.vapp_name);// +qsl(" for ") + qs(d.vplatform); + if (appVer.indexOf('(') >= 0) { + appVer = appVer.mid(appVer.indexOf('(')); } } - _inner->listUpdated(); - - update(); - - _shortPollTimer->start(SessionsShortPollTimeout); -} - -void SessionsBox::onOneTerminated() { - update(); -} - -void SessionsBox::onShortPollAuthorizations() { - if (!_shortPollRequest) { - _shortPollRequest = MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); - update(); + result.name = appName; + if (!appVer.isEmpty()) { + result.name += ' ' + appVer; } + + const auto country = qs(data.vcountry); + const auto platform = qs(data.vplatform); + //const auto &countries = countriesByISO2(); + //const auto j = countries.constFind(country); + //if (j != countries.cend()) { + // country = QString::fromUtf8(j.value()->name); + //} + + result.activeTime = data.vdate_active.v + ? data.vdate_active.v + : data.vdate_created.v; + result.info = qs(data.vdevice_model) + qstr(", ") + (platform.isEmpty() ? QString() : platform + ' ') + qs(data.vsystem_version); + result.ip = qs(data.vip) + (country.isEmpty() ? QString() : QString::fromUtf8(" \xe2\x80\x93 ") + country); + if (!result.hash) { + result.active = lang(lng_status_online); + result.activeWidth = st::sessionWhenFont->width(lang(lng_status_online)); + } else { + const auto now = QDateTime::currentDateTime(); + const auto lastTime = ParseDateTime(result.activeTime); + const auto nowDate = now.date(); + const auto lastDate = lastTime.date(); + if (lastDate == nowDate) { + result.active = lastTime.toString(cTimeFormat()); + } else if (lastDate.year() == nowDate.year() + && lastDate.weekNumber() == nowDate.weekNumber()) { + result.active = langDayOfWeek(lastDate); + } else { + result.active = lastDate.toString(qsl("d.MM.yy")); + } + result.activeWidth = st::sessionWhenFont->width(result.active); + } + + ResizeEntry(result); + + return result; } -void SessionsBox::onCheckNewAuthorization() { - onShortPollAuthorizations(); -// _shortPollTimer.start(1000); +void SessionsBox::ResizeEntry(Entry &entry) { + const auto available = st::boxWideWidth + - st::sessionPadding.left() + - st::sessionTerminateSkip; + const auto availableInList = available + - st::sessionTerminate.iconPosition.x(); + const auto availableListInfo = available - st::sessionTerminate.width; + + const auto resize = []( + const style::font &font, + QString &string, + int &stringWidth, + int available) { + stringWidth = font->width(string); + if (stringWidth > available) { + string = font->elided(string, available); + stringWidth = font->width(string); + } + }; + const auto forName = entry.hash ? availableInList : available; + const auto forInfo = entry.hash ? availableListInfo : available; + resize(st::sessionNameFont, entry.name, entry.nameWidth, forName); + resize(st::sessionInfoFont, entry.info, entry.infoWidth, forInfo); + resize(st::sessionInfoFont, entry.ip, entry.ipWidth, available); } -void SessionsBox::onAllTerminated() { - MTP::send(MTPaccount_GetAuthorizations(), rpcDone(&SessionsBox::gotAuthorizations)); +void SessionsBox::shortPollSessions() { if (_shortPollRequest) { - MTP::cancel(_shortPollRequest); - _shortPollRequest = 0; - } -} - -void SessionsBox::onTerminateAll() { - setLoading(true); -} - -SessionsBox::Inner::Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current) : TWidget(parent) -, _list(list) -, _current(current) -, _terminateAll(this, lang(lng_sessions_terminate_all), st::sessionTerminateAllButton) { - connect(_terminateAll, SIGNAL(clicked()), this, SLOT(onTerminateAll())); - _terminateAll->hide(); - setAttribute(Qt::WA_OpaquePaintEvent); -} - -void SessionsBox::Inner::paintEvent(QPaintEvent *e) { - QRect r(e->rect()); - Painter p(this); - - p.fillRect(r, st::boxBg); - int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPosition.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; - int32 w = width(); - - if (_current->active.isEmpty() && _list->isEmpty()) { - p.setFont(st::noContactsFont->f); - p.setPen(st::noContactsColor->p); - p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang(lng_contacts_loading), style::al_center); return; } - - p.translate(0, st::sessionCurrentPadding.top()); - if (r.y() <= st::sessionCurrentHeight) { - p.setFont(st::sessionNameFont); - p.setPen(st::sessionNameFg); - p.drawTextLeft(x, st::sessionPadding.top(), w, _current->name, _current->nameWidth); - - p.setFont(st::sessionWhenFont); - p.setPen(st::sessionWhenFg); - p.drawTextRight(x, st::sessionPadding.top(), w, _current->active, _current->activeWidth); - - p.setFont(st::sessionInfoFont); - p.setPen(st::boxTextFg); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current->info, _current->infoWidth); - p.setPen(st::sessionInfoFg); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current->ip, _current->ipWidth); - } - p.translate(0, st::sessionCurrentHeight - st::sessionCurrentPadding.top()); - if (_list->isEmpty()) { - p.setFont(st::sessionInfoFont); - p.setPen(st::sessionInfoFg); - p.drawText(QRect(st::sessionPadding.left(), 0, width() - st::sessionPadding.left() - st::sessionPadding.right(), st::noContactsHeight), lang(lng_sessions_other_desc), style::al_topleft); - return; - } - - p.setFont(st::linkFont->f); - int32 count = _list->size(); - int32 from = floorclamp(r.y() - st::sessionCurrentHeight, st::sessionHeight, 0, count); - int32 to = ceilclamp(r.y() + r.height() - st::sessionCurrentHeight, st::sessionHeight, 0, count); - p.translate(0, from * st::sessionHeight); - for (int32 i = from; i < to; ++i) { - const SessionsBox::Data &auth(_list->at(i)); - - p.setFont(st::sessionNameFont); - p.setPen(st::sessionNameFg); - p.drawTextLeft(x, st::sessionPadding.top(), w, auth.name, auth.nameWidth); - - p.setFont(st::sessionWhenFont); - p.setPen(st::sessionWhenFg); - p.drawTextRight(xact, st::sessionPadding.top(), w, auth.active, auth.activeWidth); - - p.setFont(st::sessionInfoFont); - p.setPen(st::boxTextFg); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth); - p.setPen(st::sessionInfoFg); - p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth); - - p.translate(0, st::sessionHeight); - } + _shortPollRequest = request(MTPaccount_GetAuthorizations( + )).done([=](const MTPaccount_Authorizations &result) { + got(result); + }).send(); + update(); } -void SessionsBox::Inner::onTerminate() { - for (auto i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { - if (i.value()->isOver()) { - if (_terminateBox) _terminateBox->deleteLater(); - _terminateBox = Ui::show(Box(lang(lng_settings_reset_one_sure), lang(lng_settings_reset_button), st::attentionBoxButton, crl::guard(this, [this, terminating = i.key()] { - if (_terminateBox) { - _terminateBox->closeBox(); - _terminateBox = nullptr; - } - MTP::send(MTPaccount_ResetAuthorization(MTP_long(terminating)), rpcDone(&Inner::terminateDone, terminating), rpcFail(&Inner::terminateFail, terminating)); - auto i = _terminateButtons.find(terminating); - if (i != _terminateButtons.cend()) { - i.value()->clearState(); - i.value()->hide(); - } - })), LayerOption::KeepOther); - } - } -} - -void SessionsBox::Inner::onTerminateAll() { +void SessionsBox::terminateOne(uint64 hash) { if (_terminateBox) _terminateBox->deleteLater(); - _terminateBox = Ui::show(Box(lang(lng_settings_reset_sure), lang(lng_settings_reset_button), st::attentionBoxButton, crl::guard(this, [this] { + const auto callback = crl::guard(this, [=] { if (_terminateBox) { _terminateBox->closeBox(); _terminateBox = nullptr; } - MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); - emit terminateAll(); - })), LayerOption::KeepOther); + request(MTPaccount_ResetAuthorization( + MTP_long(hash) + )).done([=](const MTPBool &result) { + _inner->terminatingOne(hash, false); + const auto getHash = [](const Entry &entry) { + return entry.hash; + }; + const auto removeByHash = [&](std::vector &list) { + list.erase( + ranges::remove(list, hash, getHash), + end(list)); + }; + removeByHash(_data.incomplete); + removeByHash(_data.list); + _inner->showData(_data); + }).fail([=](const RPCError &error) { + _inner->terminatingOne(hash, false); + }).send(); + _inner->terminatingOne(hash, true); + }); + _terminateBox = Ui::show( + Box( + lang(lng_settings_reset_one_sure), + lang(lng_settings_reset_button), + st::attentionBoxButton, + callback), + LayerOption::KeepOther); } -void SessionsBox::Inner::terminateDone(uint64 hash, const MTPBool &result) { - for (int32 i = 0, l = _list->size(); i < l; ++i) { - if (_list->at(i).hash == hash) { - _list->removeAt(i); - break; +void SessionsBox::terminateAll() { + if (_terminateBox) _terminateBox->deleteLater(); + const auto callback = crl::guard(this, [=] { + if (_terminateBox) { + _terminateBox->closeBox(); + _terminateBox = nullptr; } - } - listUpdated(); - emit oneTerminated(); + request(MTPauth_ResetAuthorizations( + )).done([=](const MTPBool &result) { + request(base::take(_shortPollRequest)).cancel(); + shortPollSessions(); + }).fail([=](const RPCError &result) { + request(base::take(_shortPollRequest)).cancel(); + shortPollSessions(); + }).send(); + setLoading(true); + }); + _terminateBox = Ui::show( + Box( + lang(lng_settings_reset_sure), + lang(lng_settings_reset_button), + st::attentionBoxButton, + callback), + LayerOption::KeepOther); } -bool SessionsBox::Inner::terminateFail(uint64 hash, const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - - TerminateButtons::iterator i = _terminateButtons.find(hash); - if (i != _terminateButtons.end()) { - i.value()->show(); - return true; - } - return false; +SessionsBox::Inner::Inner(QWidget *parent) +: RpWidget(parent) { + setupContent(); } -void SessionsBox::Inner::terminateAllDone(const MTPBool &result) { - emit allTerminated(); +void SessionsBox::Inner::setupContent() { + using namespace Settings; + using namespace rpl::mappers; + + const auto content = Ui::CreateChild(this); + + AddSubsectionTitle(content, lng_sessions_header); + _current = content->add(object_ptr(content)); + const auto terminateWrap = content->add( + object_ptr>( + content, + object_ptr(content)))->setDuration(0); + const auto terminateInner = terminateWrap->entity(); + _terminateAll = terminateInner->add( + object_ptr( + terminateInner, + Lang::Viewer(lng_sessions_terminate_all), + st::terminateSessionsButton)); + AddSkip(terminateInner); + AddDividerText( + terminateInner, + Lang::Viewer(lng_sessions_terminate_all_about)); + + const auto incompleteWrap = content->add( + object_ptr>( + content, + object_ptr(content)))->setDuration(0); + const auto incompleteInner = incompleteWrap->entity(); + AddSkip(incompleteInner); + AddSubsectionTitle(incompleteInner, lng_sessions_incomplete); + _incomplete = incompleteInner->add(object_ptr(incompleteInner)); + AddSkip(incompleteInner); + AddDividerText( + incompleteInner, + Lang::Viewer(lng_sessions_incomplete_about)); + + const auto listWrap = content->add( + object_ptr>( + content, + object_ptr(content)))->setDuration(0); + const auto listInner = listWrap->entity(); + AddSkip(listInner); + AddSubsectionTitle(listInner, lng_sessions_other_header); + _list = listInner->add(object_ptr(listInner)); + AddSkip(listInner); + + const auto placeholder = content->add( + object_ptr>( + content, + object_ptr( + content, + Lang::Viewer(lng_sessions_other_desc), + st::boxDividerLabel), + st::settingsDividerLabelPadding))->setDuration(0); + + terminateWrap->toggleOn( + rpl::combine( + _incomplete->itemsCount(), + _list->itemsCount(), + (_1 + _2) > 0)); + incompleteWrap->toggleOn(_incomplete->itemsCount() | rpl::map(_1 > 0)); + listWrap->toggleOn(_list->itemsCount() | rpl::map(_1 > 0)); + placeholder->toggleOn(_list->itemsCount() | rpl::map(_1 == 0)); + + Ui::ResizeFitChild(this, content); } -bool SessionsBox::Inner::terminateAllFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - emit allTerminated(); - return true; +void SessionsBox::Inner::showData(const Full &data) { + _current->showData({ &data.current, &data.current + 1 }); + _list->showData(data.list); + _incomplete->showData(data.incomplete); } -void SessionsBox::Inner::resizeEvent(QResizeEvent *e) { - _terminateAll->moveToLeft(st::sessionPadding.left(), st::sessionCurrentPadding.top() + st::sessionHeight + st::sessionCurrentPadding.bottom()); +rpl::producer<> SessionsBox::Inner::terminateAll() const { + return _terminateAll->clicks() | rpl::map([] { + return rpl::empty_value(); + }); } -void SessionsBox::Inner::listUpdated() { - if (_list->isEmpty()) { - _terminateAll->hide(); - } else { - _terminateAll->show(); - } - for (TerminateButtons::iterator i = _terminateButtons.begin(), e = _terminateButtons.end(); i != e; ++i) { - i.value()->move(0, -1); - } - for (int32 i = 0, l = _list->size(); i < l; ++i) { - TerminateButtons::iterator j = _terminateButtons.find(_list->at(i).hash); - if (j == _terminateButtons.cend()) { - j = _terminateButtons.insert(_list->at(i).hash, new Ui::IconButton(this, st::sessionTerminate)); - connect(j.value(), SIGNAL(clicked()), this, SLOT(onTerminate())); +rpl::producer SessionsBox::Inner::terminateOne() const { + return rpl::merge( + _incomplete->terminate(), + _list->terminate()); +} + +void SessionsBox::Inner::terminatingOne(uint64 hash, bool terminating) { + _incomplete->terminating(hash, terminating); + _list->terminating(hash, terminating); +} + +SessionsBox::List::List(QWidget *parent) : RpWidget(parent) { + setAttribute(Qt::WA_OpaquePaintEvent); +} + +void SessionsBox::List::showData(gsl::span items) { + auto buttons = base::take(_terminateButtons); + _items.clear(); + _items.insert(begin(_items), items.begin(), items.end()); + for (const auto &entry : _items) { + const auto hash = entry.hash; + if (!hash) { + continue; } - j.value()->moveToRight(st::sessionTerminateSkip, st::sessionCurrentHeight + i * st::sessionHeight + st::sessionTerminateTop, width()); - j.value()->show(); + const auto button = [&] { + const auto i = buttons.find(hash); + return _terminateButtons.emplace( + hash, + (i != end(buttons) + ? std::move(i->second) + : std::make_unique( + this, + st::sessionTerminate))).first->second.get(); + }(); + button->setClickedCallback([=] { + _terminate.fire_copy(hash); + }); + button->show(); + button->moveToRight( + st::sessionTerminateSkip, + ((_terminateButtons.size() - 1) * st::sessionHeight + + st::sessionTerminateTop)); } - for (TerminateButtons::iterator i = _terminateButtons.begin(); i != _terminateButtons.cend();) { - if (i.value()->y() >= 0) { - ++i; + resizeToWidth(width()); + _itemsCount.fire(_items.size()); +} + +rpl::producer SessionsBox::List::itemsCount() const { + return _itemsCount.events_starting_with(_items.size()); +} + +rpl::producer SessionsBox::List::terminate() const { + return _terminate.events(); +} + +void SessionsBox::List::terminating(uint64 hash, bool terminating) { + const auto i = _terminateButtons.find(hash); + if (i != _terminateButtons.cend()) { + if (terminating) { + i->second->clearState(); + i->second->hide(); } else { - delete i.value(); - i = _terminateButtons.erase(i); + i->second->show(); } } - resize(width(), _list->isEmpty() ? (st::sessionCurrentHeight + st::noContactsHeight) : (st::sessionCurrentHeight + _list->size() * st::sessionHeight)); - update(); +} + +int SessionsBox::List::resizeGetHeight(int newWidth) { + return _items.size() * st::sessionHeight; +} + +void SessionsBox::List::paintEvent(QPaintEvent *e) { + QRect r(e->rect()); + Painter p(this); + + p.fillRect(r, st::boxBg); + p.setFont(st::linkFont); + const auto count = int(_items.size()); + const auto from = floorclamp(r.y(), st::sessionHeight, 0, count); + const auto till = ceilclamp( + r.y() + r.height(), + st::sessionHeight, + 0, + count); + + const auto x = st::sessionPadding.left(); + const auto y = st::sessionPadding.top(); + const auto w = width(); + const auto xact = st::sessionTerminateSkip + + st::sessionTerminate.iconPosition.x(); + p.translate(0, from * st::sessionHeight); + for (auto i = from; i != till; ++i) { + const auto &entry = _items[i]; + + p.setFont(st::sessionNameFont); + p.setPen(st::sessionNameFg); + p.drawTextLeft(x, y, w, entry.name, entry.nameWidth); + + p.setFont(st::sessionWhenFont); + p.setPen(st::sessionWhenFg); + p.drawTextRight(xact, y, w, entry.active, entry.activeWidth); + + const auto name = st::sessionNameFont->height; + p.setFont(st::sessionInfoFont); + p.setPen(st::boxTextFg); + p.drawTextLeft(x, y + name, w, entry.info, entry.infoWidth); + + const auto info = st::sessionInfoFont->height; + p.setPen(st::sessionInfoFg); + p.drawTextLeft(x, y + name + info, w, entry.ip, entry.ipWidth); + + p.translate(0, st::sessionHeight); + } } diff --git a/Telegram/SourceFiles/boxes/sessions_box.h b/Telegram/SourceFiles/boxes/sessions_box.h index 0343aa66e2..ddb5cd9900 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.h +++ b/Telegram/SourceFiles/boxes/sessions_box.h @@ -8,7 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "boxes/abstract_box.h" -#include "core/single_timer.h" +#include "mtproto/sender.h" +#include "base/timer.h" class ConfirmBox; @@ -17,9 +18,7 @@ class IconButton; class LinkButton; } // namespace Ui -class SessionsBox : public BoxContent, public RPCSender { - Q_OBJECT - +class SessionsBox : public BoxContent, private MTP::Sender { public: SessionsBox(QWidget*); @@ -29,75 +28,40 @@ protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; -private slots: - void onOneTerminated(); - void onAllTerminated(); - void onTerminateAll(); - void onShortPollAuthorizations(); - void onCheckNewAuthorization(); - private: - void setLoading(bool loading); - struct Data { - uint64 hash; + struct Entry { + uint64 hash = 0; - int32 activeTime; - int32 nameWidth, activeWidth, infoWidth, ipWidth; + bool incomplete = false; + TimeId activeTime = 0; + int nameWidth, activeWidth, infoWidth, ipWidth; QString name, active, info, ip; }; - using List = QList; + struct Full { + Entry current; + std::vector incomplete; + std::vector list; + }; + class Inner; + class List; - void gotAuthorizations(const MTPaccount_Authorizations &result); + static Entry ParseEntry(const MTPDauthorization &data); + static void ResizeEntry(Entry &entry); + void setLoading(bool loading); + void shortPollSessions(); + + void got(const MTPaccount_Authorizations &result); + + void terminateOne(uint64 hash); + void terminateAll(); bool _loading = false; + Full _data; - Data _current; - List _list; - - class Inner; QPointer _inner; + QPointer _terminateBox; - object_ptr _shortPollTimer; + base::Timer _shortPollTimer; mtpRequestId _shortPollRequest = 0; }; - -// This class is hold in header because it requires Qt preprocessing. -class SessionsBox::Inner : public TWidget, public RPCSender { - Q_OBJECT - -public: - Inner(QWidget *parent, SessionsBox::List *list, SessionsBox::Data *current); - - void listUpdated(); - -protected: - void paintEvent(QPaintEvent *e) override; - void resizeEvent(QResizeEvent *e) override; - -signals: - void oneTerminated(); - void allTerminated(); - void terminateAll(); - -public slots: - void onTerminate(); - void onTerminateAll(); - -private: - void terminateDone(uint64 hash, const MTPBool &result); - bool terminateFail(uint64 hash, const RPCError &error); - - void terminateAllDone(const MTPBool &res); - bool terminateAllFail(const RPCError &error); - - SessionsBox::List *_list; - SessionsBox::Data *_current; - - typedef QMap TerminateButtons; - TerminateButtons _terminateButtons; - - object_ptr _terminateAll; - QPointer _terminateBox; - -}; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index e2a02f4be0..14d9b28e6d 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -89,7 +89,6 @@ enum { UpdateDelayRandPart = 8 * 3600, // 8 hour max - min time between update check requests WrongPasscodeTimeout = 1500, - SessionsShortPollTimeout = 60000, ChoosePeerByDragTimeout = 1000, // 1 second mouse not moved to choose dialog when dragging a file }; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index faab5b0e13..700efedab9 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -2001,6 +2001,14 @@ void Session::serviceNotification( } } +void Session::checkNewAuthorization() { + _newAuthorizationChecks.fire({}); +} + +rpl::producer<> Session::newAuthorizationChecks() const { + return _newAuthorizationChecks.events(); +} + void Session::insertCheckedServiceNotification( const TextWithEntities &message, const MTPMessageMedia &media, diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 5086548114..83f71bf31e 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -423,6 +423,8 @@ public: void serviceNotification( const TextWithEntities &message, const MTPMessageMedia &media = MTP_messageMediaEmpty()); + void checkNewAuthorization(); + rpl::producer<> newAuthorizationChecks() const; void setMimeForwardIds(MessageIdsList &&list); MessageIdsList takeMimeForwardIds(); @@ -656,6 +658,8 @@ private: int>; std::unique_ptr _passportCredentials; + rpl::event_stream<> _newAuthorizationChecks; + rpl::lifetime _lifetime; }; diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style index 344e25ad25..39f2100e52 100644 --- a/Telegram/SourceFiles/info/info.style +++ b/Telegram/SourceFiles/info/info.style @@ -588,6 +588,10 @@ managePeerButtonLabel: FlatLabel(defaultFlatLabel) { } managePeerButtonLabelPosition: point(25px, 10px); +terminateSessionsButton: InfoProfileButton(infoBlockButton) { + padding: margins(23px, 12px, 23px, 10px); +} + infoEmptyFg: windowSubTextFg; infoEmptyPhoto: icon {{ "info_media_photo_empty", infoEmptyFg }}; infoEmptyVideo: icon {{ "info_media_video_empty", infoEmptyFg }}; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index a69600364f..8257da284f 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -4578,7 +4578,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { Ui::show(Box(text)); } else { Auth().data().serviceNotification(text, d.vmedia); - emit App::wnd()->checkNewAuthorization(); + Auth().data().checkNewAuthorization(); } } break; diff --git a/Telegram/SourceFiles/mainwindow.h b/Telegram/SourceFiles/mainwindow.h index 3fe9471ae9..0f76cce9ab 100644 --- a/Telegram/SourceFiles/mainwindow.h +++ b/Telegram/SourceFiles/mainwindow.h @@ -148,7 +148,6 @@ public slots: signals: void tempDirCleared(int task); void tempDirClearFailed(int task); - void checkNewAuthorization(); private: [[nodiscard]] bool skipTrayClick() const;