diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 016a4ed1c8..ea9c3e095f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -723,6 +723,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_sessions_terminate" = "Terminate"; +"lng_sessions_application" = "Application"; +"lng_sessions_system" = "System Version"; +"lng_sessions_ip" = "IP Address"; +"lng_sessions_location" = "Location"; +"lng_sessions_location_about" = "This location estimate is based on the IP address and may not always be accurate."; "lng_blocked_list_title" = "Blocked users"; "lng_blocked_list_unknown_phone" = "unknown phone number"; diff --git a/Telegram/SourceFiles/api/api_authorizations.cpp b/Telegram/SourceFiles/api/api_authorizations.cpp index ce609661fa..9764f5d5a5 100644 --- a/Telegram/SourceFiles/api/api_authorizations.cpp +++ b/Telegram/SourceFiles/api/api_authorizations.cpp @@ -58,7 +58,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { //if (j != countries.cend()) { // country = QString::fromUtf8(j.value()->name); //} - + result.system = qs(data.vsystem_version()); result.activeTime = data.vdate_active().v ? data.vdate_active().v : data.vdate_created().v; @@ -82,10 +82,7 @@ Authorizations::Entry ParseEntry(const MTPDauthorization &data) { result.active = lastDate.toString(cDateFormat()); } } - result.location = (country.isEmpty() ? result.ip : country) - + (result.hash - ? (QString::fromUtf8(" \xe2\x80\x93 ") + result.active) - : QString()); + result.location = country; return result; } diff --git a/Telegram/SourceFiles/api/api_authorizations.h b/Telegram/SourceFiles/api/api_authorizations.h index 9110c0650c..4b28e9c174 100644 --- a/Telegram/SourceFiles/api/api_authorizations.h +++ b/Telegram/SourceFiles/api/api_authorizations.h @@ -22,7 +22,7 @@ public: bool incomplete = false; TimeId activeTime = 0; - QString name, active, info, ip, location; + QString name, active, info, ip, location, system; }; using List = std::vector; diff --git a/Telegram/SourceFiles/boxes/sessions_box.cpp b/Telegram/SourceFiles/boxes/sessions_box.cpp index c4868bd89e..d398dac537 100644 --- a/Telegram/SourceFiles/boxes/sessions_box.cpp +++ b/Telegram/SourceFiles/boxes/sessions_box.cpp @@ -37,6 +37,8 @@ namespace { constexpr auto kSessionsShortPollTimeout = 60 * crl::time(1000); constexpr auto kMaxDeviceModelLength = 32; +using EntryData = Api::Authorizations::Entry; + void RenameBox(not_null box) { box->setTitle(tr::lng_settings_rename_device_title()); @@ -74,6 +76,63 @@ void RenameBox(not_null box) { box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +void SessionInfoBox( + not_null box, + const EntryData &data, + Fn terminate) { + box->setTitle(rpl::single(data.name)); + box->setWidth(st::boxWidth); + + const auto skips = style::margins(0, 0, 0, st::settingsSectionSkip); + const auto date = base::unixtime::parse(data.activeTime); + box->addRow( + object_ptr( + box, + rpl::single(langDateTimeFull(date)), + st::boxDividerLabel), + st::boxRowPadding + skips); + + const auto add = [&](rpl::producer label, QString value) { + if (value.isEmpty()) { + return; + } + Settings::AddSubsectionTitle( + box->verticalLayout(), + std::move(label)); + box->addRow( + object_ptr( + box, + rpl::single(value), + st::boxDividerLabel), + st::boxRowPadding + skips); + }; + add(tr::lng_sessions_application(), data.info); + add(tr::lng_sessions_system(), data.system); + add(tr::lng_sessions_ip(), data.ip); + add(tr::lng_sessions_location(), data.location); + if (!data.location.isEmpty()) { + Settings::AddDividerText( + box->verticalLayout(), + tr::lng_sessions_location_about()); + } + + box->addButton(tr::lng_about_done(), [=] { box->closeBox(); }); + box->addLeftButton(tr::lng_sessions_terminate(), [=, hash = data.hash] { + const auto weak = Ui::MakeWeak(box.get()); + terminate(hash); + if (weak) { + box->closeBox(); + } + }, st::attentionBoxButton); +} + +[[nodiscard]] QString LocationAndDate(const EntryData &entry) { + return (entry.location.isEmpty() ? entry.ip : entry.location) + + (entry.hash + ? (QString::fromUtf8(" \xe2\x80\x93 ") + entry.active) + : QString()); +} + } // namespace class SessionsContent : public Ui::RpWidget { @@ -91,20 +150,20 @@ protected: private: struct Entry { Entry() = default; - Entry(const Api::Authorizations::Entry &entry) - : hash(entry.hash) + Entry(const EntryData &entry) + : data(entry) , incomplete(entry.incomplete) , activeTime(entry.activeTime) , name(st::sessionNameStyle, entry.name) , info(st::sessionInfoStyle, entry.info) - , ip(st::sessionInfoStyle, entry.location) { + , location(st::sessionInfoStyle, LocationAndDate(entry)) { }; - uint64 hash = 0; + EntryData data; bool incomplete = false; TimeId activeTime = 0; - Ui::Text::String name, info, ip; + Ui::Text::String name, info, location; }; struct Full { Entry current; @@ -121,6 +180,7 @@ private: void terminateOne(uint64 hash); void terminateAll(); + const not_null _controller; const not_null _authorizations; rpl::variable _loading = false; @@ -139,7 +199,8 @@ public: void showData(gsl::span items); rpl::producer itemsCount() const; - rpl::producer terminate() const; + rpl::producer terminateRequests() const; + [[nodiscard]] rpl::producer showRequests() const; void terminating(uint64 hash, bool terminating); @@ -147,6 +208,9 @@ protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + int resizeGetHeight(int newWidth) override; private: @@ -161,8 +225,11 @@ private: RowWidth _rowWidth; std::vector _items; std::map> _terminateButtons; - rpl::event_stream _terminate; + rpl::event_stream _terminateRequests; rpl::event_stream _itemsCount; + rpl::event_stream _showRequests; + + int _pressed = -1; }; @@ -174,9 +241,10 @@ public: rpl::producer ttlDays); void showData(const Full &data); - rpl::producer terminateOne() const; - rpl::producer<> terminateAll() const; - rpl::producer<> renameCurrentRequests() const; + [[nodiscard]] rpl::producer showRequests() const; + [[nodiscard]] rpl::producer terminateOne() const; + [[nodiscard]] rpl::producer<> terminateAll() const; + [[nodiscard]] rpl::producer<> renameCurrentRequests() const; void terminatingOne(uint64 hash, bool terminating); @@ -195,7 +263,8 @@ private: SessionsContent::SessionsContent( QWidget*, not_null controller) -: _authorizations(&controller->session().api().authorizations()) +: _controller(controller) +, _authorizations(&controller->session().api().authorizations()) , _inner(this, controller, _authorizations->ttlDays()) , _shortPollTimer([=] { shortPollSessions(); }) { } @@ -209,6 +278,14 @@ void SessionsContent::setupContent() { resize(width(), height); }, _inner->lifetime()); + _inner->showRequests( + ) | rpl::start_with_next([=](const EntryData &data) { + _controller->show(Box( + SessionInfoBox, + data, + [=](uint64 hash) { terminateOne(hash); })); + }, lifetime()); + _inner->terminateOne( ) | rpl::start_with_next([=](uint64 hash) { terminateOne(hash); @@ -240,7 +317,7 @@ void SessionsContent::parse(const Api::Authorizations::List &list) { _data = Full(); for (const auto &auth : list) { auto entry = Entry(auth); - if (!entry.hash) { + if (!entry.data.hash) { _data.current = std::move(entry); } else if (entry.incomplete) { _data.incomplete.push_back(std::move(entry)); @@ -323,7 +400,10 @@ void SessionsContent::terminateOne(uint64 hash) { _inner->terminatingOne(hash, false); const auto removeByHash = [&](std::vector &list) { list.erase( - ranges::remove(list, hash, &Entry::hash), + ranges::remove( + list, + hash, + [](const Entry &entry) { return entry.data.hash; }), end(list)); }; removeByHash(_data.incomplete); @@ -489,8 +569,14 @@ rpl::producer<> SessionsContent::Inner::terminateAll() const { rpl::producer SessionsContent::Inner::terminateOne() const { return rpl::merge( - _incomplete->terminate(), - _list->terminate()); + _incomplete->terminateRequests(), + _list->terminateRequests()); +} + +rpl::producer SessionsContent::Inner::showRequests() const { + return rpl::merge( + _incomplete->showRequests(), + _list->showRequests()); } void SessionsContent::Inner::terminatingOne(uint64 hash, bool terminating) { @@ -512,7 +598,7 @@ void SessionsContent::List::subscribeToCustomDeviceModel() { Core::App().settings().deviceModelChanges( ) | rpl::start_with_next([=](const QString &model) { for (auto &entry : _items) { - if (!entry.hash) { + if (!entry.data.hash) { entry.name.setText(st::sessionNameStyle, model); } } @@ -527,7 +613,7 @@ void SessionsContent::List::showData(gsl::span items) { _items.clear(); _items.insert(begin(_items), items.begin(), items.end()); for (const auto &entry : _items) { - const auto hash = entry.hash; + const auto hash = entry.data.hash; if (!hash) { continue; } @@ -542,7 +628,7 @@ void SessionsContent::List::showData(gsl::span items) { st::sessionTerminate))).first->second.get(); }(); button->setClickedCallback([=] { - _terminate.fire_copy(hash); + _terminateRequests.fire_copy(hash); }); button->show(); const auto number = _terminateButtons.size() - 1; @@ -557,12 +643,16 @@ void SessionsContent::List::showData(gsl::span items) { _itemsCount.fire(_items.size()); } +rpl::producer SessionsContent::List::showRequests() const { + return _showRequests.events(); +} + rpl::producer SessionsContent::List::itemsCount() const { return _itemsCount.events_starting_with(_items.size()); } -rpl::producer SessionsContent::List::terminate() const { - return _terminate.events(); +rpl::producer SessionsContent::List::terminateRequests() const { + return _terminateRequests.events(); } void SessionsContent::List::terminating(uint64 hash, bool terminating) { @@ -617,7 +707,7 @@ void SessionsContent::List::paintEvent(QPaintEvent *e) { const auto nameW = _rowWidth.info; const auto nameH = entry.name.style()->font->height; - const auto infoW = entry.hash ? _rowWidth.info : available; + const auto infoW = entry.data.hash ? _rowWidth.info : available; const auto infoH = entry.info.style()->font->height; p.setPen(st::sessionNameFg); @@ -627,12 +717,26 @@ void SessionsContent::List::paintEvent(QPaintEvent *e) { entry.info.drawLeftElided(p, x, y + nameH, infoW, w); p.setPen(st::sessionInfoFg); - entry.ip.drawLeftElided(p, x, y + nameH + infoH, available, w); + entry.location.drawLeftElided(p, x, y + nameH + infoH, available, w); p.translate(0, st::sessionHeight); } } +void SessionsContent::List::mousePressEvent(QMouseEvent *e) { + const auto index = e->pos().y() / st::sessionHeight; + _pressed = (index >= 0 && index < _items.size()) ? index : -1; +} + +void SessionsContent::List::mouseReleaseEvent(QMouseEvent *e) { + const auto index = e->pos().y() / st::sessionHeight; + const auto released = (index >= 0 && index < _items.size()) ? index : -1; + if (released == _pressed && released >= 0) { + _showRequests.fire_copy(_items[released].data); + } + _pressed = -1; +} + SessionsBox::SessionsBox( QWidget*, not_null controller)