From 6e31993777f61b795a09beea5041c6aa007e92a6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 15 Jan 2024 11:36:17 +0400 Subject: [PATCH] Update API scheme on layer 172. --- Telegram/CMakeLists.txt | 1 + Telegram/Resources/langs/lang.strings | 3 +- .../SourceFiles/api/api_send_progress.cpp | 11 +- Telegram/SourceFiles/api/api_updates.cpp | 12 +- Telegram/SourceFiles/apiwrap.cpp | 55 ++++---- .../boxes/peer_list_controllers.cpp | 4 +- .../boxes/peers/edit_participant_box.cpp | 2 +- .../SourceFiles/data/data_lastseen_status.h | 131 ++++++++++++++++++ .../SourceFiles/data/data_peer_values.cpp | 123 +++++++--------- Telegram/SourceFiles/data/data_peer_values.h | 8 +- Telegram/SourceFiles/data/data_session.cpp | 13 +- Telegram/SourceFiles/data/data_user.cpp | 71 +++++----- Telegram/SourceFiles/data/data_user.h | 23 ++- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 2 +- .../view/history_view_schedule_box.cpp | 10 +- .../view/history_view_top_bar_widget.cpp | 4 +- .../info/profile/info_profile_cover.cpp | 12 +- Telegram/SourceFiles/mtproto/scheme/api.tl | 7 +- .../touchbar/items/mac_pinned_chats_item.mm | 48 +++---- .../profile/profile_block_group_members.cpp | 20 +-- .../profile/profile_block_group_members.h | 3 +- .../SourceFiles/storage/serialize_peer.cpp | 25 ++-- 22 files changed, 343 insertions(+), 245 deletions(-) create mode 100644 Telegram/SourceFiles/data/data_lastseen_status.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index b7ecc7c8a..603fd3f75 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -515,6 +515,7 @@ PRIVATE data/data_groups.h data/data_histories.cpp data/data_histories.h + data/data_lastseen_status.h data/data_location.cpp data/data_location.h data/data_media_rotation.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9c6efb548..13a234755 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -141,8 +141,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_status_last_week" = "last seen within a week"; "lng_status_last_month" = "last seen within a month"; "lng_status_lastseen_now" = "last seen just now"; -"lng_status_lastseen_hidden" = "last seen hidden"; -"lng_status_lastseen_show" = "show"; +"lng_status_lastseen_when" = "when?"; "lng_status_lastseen_minutes#one" = "last seen {count} minute ago"; "lng_status_lastseen_minutes#other" = "last seen {count} minutes ago"; "lng_status_lastseen_hours#one" = "last seen {count} hour ago"; diff --git a/Telegram/SourceFiles/api/api_send_progress.cpp b/Telegram/SourceFiles/api/api_send_progress.cpp index d3e277261..93d287926 100644 --- a/Telegram/SourceFiles/api/api_send_progress.cpp +++ b/Telegram/SourceFiles/api/api_send_progress.cpp @@ -161,14 +161,13 @@ bool SendProgressManager::skipRequest(const Key &key) const { return true; } const auto recently = base::unixtime::now() - kSendTypingsToOfflineFor; - const auto online = user->onlineTill; - if (online == kOnlineRecently) { + const auto lastseen = user->lastseen(); + if (lastseen.isRecently()) { return false; - } else if (online < 0) { - return (-online < recently); - } else { - return (online < recently); + } else if (const auto value = lastseen.onlineTill()) { + return (value < recently); } + return true; } void SendProgressManager::done(mtpRequestId requestId) { diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 54e2a37b2..a6d27aa63 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -944,8 +944,9 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { } const auto self = session().user(); - self->onlineTill = base::unixtime::now() - + (isOnline ? (config.onlineUpdatePeriod / 1000) : -1); + const auto onlineFor = (config.onlineUpdatePeriod / 1000); + self->updateLastseen(Data::LastseenStatus::OnlineTill( + base::unixtime::now() + (isOnline ? onlineFor : -1))); session().changes().peerUpdated( self, Data::PeerUpdate::Flag::OnlineStatus); @@ -1852,14 +1853,11 @@ void Updates::feedUpdate(const MTPUpdate &update) { case mtpc_updateUserStatus: { auto &d = update.c_updateUserStatus(); if (const auto user = session().data().userLoaded(d.vuser_id())) { - const auto value = OnlineTillFromMTP( - d.vstatus(), - user->onlineTill); - if (user->onlineTill != value) { + const auto now = LastseenFromMTP(d.vstatus(), user->lastseen()); + if (user->updateLastseen(now)) { session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); } } if (UserId(d.vuser_id()) == session().userId()) { diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index b3940a5b0..73026cc88 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1922,25 +1922,28 @@ void ApiWrap::saveDraftToCloudDelayed(not_null thread) { void ApiWrap::updatePrivacyLastSeens() { const auto now = base::unixtime::now(); - _session->data().enumerateUsers([&](UserData *user) { - if (user->isSelf() || !user->isLoaded() || user->onlineTill <= 0) { - return; - } + if (!_session->premium()) { + _session->data().enumerateUsers([&](not_null user) { + if (user->isSelf() + || !user->isLoaded() + || user->lastseen().isHidden()) { + return; + } - if (user->onlineTill + 3 * 86400 >= now) { - user->onlineTill = kOnlineRecently; - } else if (user->onlineTill + 7 * 86400 >= now) { - user->onlineTill = kOnlineLastWeek; - } else if (user->onlineTill + 30 * 86400 >= now) { - user->onlineTill = kOnlineLastMonth; - } else { - user->onlineTill = kOnlineEmpty; - } - session().changes().peerUpdated( - user, - Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); - }); + const auto till = user->lastseen().onlineTill(); + user->updateLastseen((till + 3 * 86400 >= now) + ? Data::LastseenStatus::Recently(true) + : (till + 7 * 86400 >= now) + ? Data::LastseenStatus::WithinWeek(true) + : (till + 30 * 86400 >= now) + ? Data::LastseenStatus::WithinMonth(true) + : Data::LastseenStatus::LongAgo(true)); + session().changes().peerUpdated( + user, + Data::PeerUpdate::Flag::OnlineStatus); + session().data().maybeStopWatchForOffline(user); + }); + } if (_contactsStatusesRequestId) { request(_contactsStatusesRequestId).cancel(); @@ -1948,19 +1951,17 @@ void ApiWrap::updatePrivacyLastSeens() { _contactsStatusesRequestId = request(MTPcontacts_GetStatuses( )).done([=](const MTPVector &result) { _contactsStatusesRequestId = 0; - for (const auto &item : result.v) { - Assert(item.type() == mtpc_contactStatus); - auto &data = item.c_contactStatus(); - if (auto user = _session->data().userLoaded(data.vuser_id())) { - auto value = OnlineTillFromMTP( + for (const auto &status : result.v) { + const auto &data = status.data(); + const auto userId = UserId(data.vuser_id()); + if (const auto user = _session->data().userLoaded(userId)) { + const auto status = LastseenFromMTP( data.vstatus(), - user->onlineTill); - if (user->onlineTill != value) { - user->onlineTill = value; + user->lastseen()); + if (user->updateLastseen(status)) { session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); - session().data().maybeStopWatchForOffline(user); } } } diff --git a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp index bad9dbd89..6b4404d17 100644 --- a/Telegram/SourceFiles/boxes/peer_list_controllers.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_controllers.cpp @@ -598,7 +598,9 @@ void ContactsBoxController::sortByOnline() { const auto now = base::unixtime::now(); const auto key = [&](const PeerListRow &row) { const auto user = row.peer()->asUser(); - return user ? (std::min(user->onlineTill, now) + 1) : TimeId(); + return user + ? (std::min(user->lastseen().onlineTill(), now + 1) + 1) + : TimeId(); }; const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) { return key(a) > key(b); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp index d867c0607..51d969d0e 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participant_box.cpp @@ -143,7 +143,7 @@ void EditParticipantBox::Inner::paintEvent(QPaintEvent *e) { ? tr::lng_status_bot_reads_all : tr::lng_status_bot_not_reads_all)(tr::now); } - return Data::OnlineText(_user->onlineTill, base::unixtime::now()); + return Data::OnlineText(_user->lastseen(), base::unixtime::now()); }(); p.setFont(st::contactsStatusFont); p.setPen(st::contactsStatusFg); diff --git a/Telegram/SourceFiles/data/data_lastseen_status.h b/Telegram/SourceFiles/data/data_lastseen_status.h new file mode 100644 index 000000000..4603c5406 --- /dev/null +++ b/Telegram/SourceFiles/data/data_lastseen_status.h @@ -0,0 +1,131 @@ +/* +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 +*/ +#pragma once + +namespace Data { + +inline constexpr auto kLifeStartDate = 1375315200; // Let it be 01.08.2013. + +class LastseenStatus final { +public: + LastseenStatus() = default; + + [[nodiscard]] static LastseenStatus Recently(bool byMe = false) { + return LastseenStatus(kRecentlyValue, false, byMe); + } + [[nodiscard]] static LastseenStatus WithinWeek(bool byMe = false) { + return LastseenStatus(kWithinWeekValue, false, byMe); + } + [[nodiscard]] static LastseenStatus WithinMonth(bool byMe = false) { + return LastseenStatus(kWithinMonthValue, false, byMe); + } + [[nodiscard]] static LastseenStatus LongAgo(bool byMe = false) { + return LastseenStatus(kLongAgoValue, false, byMe); + } + [[nodiscard]] static LastseenStatus OnlineTill( + TimeId till, + bool local = false, + bool hiddenByMe = false) { + return (till >= kLifeStartDate + kSpecialValueSkip) + ? LastseenStatus(till - kLifeStartDate, !local, hiddenByMe) + : LongAgo(hiddenByMe); + } + + [[nodiscard]] bool isHidden() const { + return !_available; + } + [[nodiscard]] bool isRecently() const { + return !_available && (_value == kRecentlyValue); + } + [[nodiscard]] bool isWithinWeek() const { + return !_available && (_value == kWithinWeekValue); + } + [[nodiscard]] bool isWithinMonth() const { + return !_available && (_value == kWithinMonthValue); + } + [[nodiscard]] bool isLongAgo() const { + return !_available && (_value == kLongAgoValue); + } + [[nodiscard]] bool isHiddenByMe() const { + return _hiddenByMe; + } + + [[nodiscard]] bool isOnline(TimeId now) const { + return (_value >= kSpecialValueSkip) + && (kLifeStartDate + _value > now); + } + [[nodiscard]] bool isLocalOnlineValue() const { + return !_available && (_value >= kSpecialValueSkip); + } + [[nodiscard]] TimeId onlineTill() const { + return (_value >= kSpecialValueSkip) + ? (kLifeStartDate + _value) + : 0; + } + + [[nodiscard]] uint32 serialize() const { + return (_value & 0x3FFFFFFF) + | (_available << 30) + | (_hiddenByMe << 31); + } + [[nodiscard]] static LastseenStatus FromSerialized(uint32 value) { + auto result = LastseenStatus(); + result._value = value & 0x3FFFFFFF; + result._available = (value >> 30) & 1; + result._hiddenByMe = (value >> 31) & 1; + return result.valid() ? result : LastseenStatus(); + } + + [[nodiscard]] static LastseenStatus FromLegacy(int32 value) { + auto result = LastseenStatus(); + if (value == -2) { + return LastseenStatus::Recently(); + } else if (value == -3) { + return LastseenStatus::WithinWeek(); + } else if (value == -4) { + return LastseenStatus::WithinMonth(); + } else if (value < -30) { + return LastseenStatus::OnlineTill(-value, true); + } else if (value > 0) { + return LastseenStatus::OnlineTill(value); + } + return LastseenStatus(); + } + + friend inline constexpr auto operator<=>( + LastseenStatus, + LastseenStatus) = default; + friend inline constexpr bool operator==( + LastseenStatus a, + LastseenStatus b) = default; + +private: + static constexpr auto kLongAgoValue = uint32(0); + static constexpr auto kRecentlyValue = uint32(1); + static constexpr auto kWithinWeekValue = uint32(2); + static constexpr auto kWithinMonthValue = uint32(3); + static constexpr auto kSpecialValueSkip = uint32(4); + static constexpr auto kValidAfter = kLifeStartDate + kSpecialValueSkip; + + [[nodiscard]] bool valid() const { + return !_available || (_value >= kSpecialValueSkip); + } + + LastseenStatus(uint32 value, bool available, bool hiddenByMe) + : _value(value) + , _available(available ? 1 : 0) + , _hiddenByMe(hiddenByMe ? 1 : 0) { + } + + uint32 _value : 30 = 0; + uint32 _available : 1 = 0; + uint32 _hiddenByMe : 1 = 0; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 874fa0ae7..58b85fdbd 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -28,23 +28,20 @@ constexpr auto kMinOnlineChangeTimeout = crl::time(1000); constexpr auto kMaxOnlineChangeTimeout = 86400 * crl::time(1000); constexpr auto kSecondsInDay = 86400; -int OnlinePhraseChangeInSeconds(TimeId online, TimeId now) { - if (online <= 0) { - if (-online > now) { - return (-online - now); - } - return std::numeric_limits::max(); +int OnlinePhraseChangeInSeconds(LastseenStatus status, TimeId now) { + const auto till = status.onlineTill(); + if (till > now) { + return till - now; + } else if (status.isHidden()) { + return std::numeric_limits::max(); } - if (online > now) { - return online - now; - } - const auto minutes = (now - online) / 60; + const auto minutes = (now - till) / 60; if (minutes < 60) { - return (minutes + 1) * 60 - (now - online); + return (minutes + 1) * 60 - (now - till); } - const auto hours = (now - online) / 3600; + const auto hours = (now - till) / 3600; if (hours < 12) { - return (hours + 1) * 3600 - (now - online); + return (hours + 1) * 3600 - (now - till); } const auto nowFull = base::unixtime::parse(now); const auto tomorrow = nowFull.date().addDays(1).startOfDay(); @@ -64,20 +61,17 @@ std::optional OnlineTextSpecial(not_null user) { return std::nullopt; } -std::optional OnlineTextCommon(TimeId online, TimeId now) { - if (online <= 0) { - switch (online) { - case kOnlineEmpty: return tr::lng_status_offline(tr::now); - case kOnlineRecently: return tr::lng_status_recently(tr::now); - case kOnlineLastWeek: return tr::lng_status_last_week(tr::now); - case kOnlineLastMonth: return tr::lng_status_last_month(tr::now); - case kOnlineHidden: return tr::lng_status_lastseen_hidden(tr::now); - } - return IsRecentOnline(online, now) - ? tr::lng_status_online(tr::now) - : tr::lng_status_recently(tr::now); - } else if (online > now) { +std::optional OnlineTextCommon(LastseenStatus status, TimeId now) { + if (status.isOnline(now)) { return tr::lng_status_online(tr::now); + } else if (status.isLongAgo()) { + return tr::lng_status_offline(tr::now); + } else if (status.isRecently()) { + return tr::lng_status_recently(tr::now); + } else if (status.isWithinWeek()) { + return tr::lng_status_last_week(tr::now); + } else if (status.isWithinMonth()) { + return tr::lng_status_last_month(tr::now); } return std::nullopt; } @@ -395,31 +389,22 @@ TimeId SortByOnlineValue(not_null user, TimeId now) { if (user->isServiceUser() || user->isBot()) { return -1; } - const auto online = user->onlineTill; - if (online <= 0) { - switch (online) { - case 0: - case -1: return online; - - case -2: { - return now - 3 * kSecondsInDay; - } break; - - case -3: { - return now - 7 * kSecondsInDay; - } break; - - case -4: { - return now - 30 * kSecondsInDay; - } break; - } - return -online; + const auto lastseen = user->lastseen(); + if (const auto till = lastseen.onlineTill()) { + return till; + } else if (lastseen.isRecently()) { + return now - 3 * kSecondsInDay; + } else if (lastseen.isWithinWeek()) { + return now - 7 * kSecondsInDay; + } else if (lastseen.isWithinMonth()) { + return now - 30 * kSecondsInDay; + } else { + return 0; } - return online; } -crl::time OnlineChangeTimeout(TimeId online, TimeId now) { - const auto result = OnlinePhraseChangeInSeconds(online, now); +crl::time OnlineChangeTimeout(Data::LastseenStatus status, TimeId now) { + const auto result = OnlinePhraseChangeInSeconds(status, now); Assert(result >= 0); return std::clamp( result * crl::time(1000), @@ -431,24 +416,26 @@ crl::time OnlineChangeTimeout(not_null user, TimeId now) { if (user->isServiceUser() || user->isBot()) { return kMaxOnlineChangeTimeout; } - return OnlineChangeTimeout(user->onlineTill, now); + return OnlineChangeTimeout(user->lastseen(), now); } -QString OnlineText(TimeId online, TimeId now) { - if (const auto common = OnlineTextCommon(online, now)) { +QString OnlineText(Data::LastseenStatus status, TimeId now) { + if (const auto common = OnlineTextCommon(status, now)) { return *common; } - const auto minutes = (now - online) / 60; + const auto till = status.onlineTill(); + Assert(till > 0); + const auto minutes = (now - till) / 60; if (!minutes) { return tr::lng_status_lastseen_now(tr::now); } else if (minutes < 60) { return tr::lng_status_lastseen_minutes(tr::now, lt_count, minutes); } - const auto hours = (now - online) / 3600; + const auto hours = (now - till) / 3600; if (hours < 12) { return tr::lng_status_lastseen_hours(tr::now, lt_count, hours); } - const auto onlineFull = base::unixtime::parse(online); + const auto onlineFull = base::unixtime::parse(till); const auto nowFull = base::unixtime::parse(now); const auto locale = QLocale(); if (onlineFull.date() == nowFull.date()) { @@ -466,16 +453,17 @@ QString OnlineText(not_null user, TimeId now) { if (const auto special = OnlineTextSpecial(user)) { return *special; } - return OnlineText(user->onlineTill, now); + return OnlineText(user->lastseen(), now); } QString OnlineTextFull(not_null user, TimeId now) { if (const auto special = OnlineTextSpecial(user)) { return *special; - } else if (const auto common = OnlineTextCommon(user->onlineTill, now)) { + } else if (const auto common = OnlineTextCommon(user->lastseen(), now)) { return *common; } - const auto onlineFull = base::unixtime::parse(user->onlineTill); + const auto till = user->lastseen().onlineTill(); + const auto onlineFull = base::unixtime::parse(till); const auto nowFull = base::unixtime::parse(now); const auto locale = QLocale(); if (onlineFull.date() == nowFull.date()) { @@ -490,25 +478,10 @@ QString OnlineTextFull(not_null user, TimeId now) { return tr::lng_status_lastseen_date_time(tr::now, lt_date, date, lt_time, time); } -bool OnlineTextActive(TimeId online, TimeId now) { - if (online <= 0) { - switch (online) { - case 0: - case -1: - case -2: - case -3: - case -4: return false; - } - return (-online > now); - } - return (online > now); -} - bool OnlineTextActive(not_null user, TimeId now) { - if (user->isServiceUser() || user->isBot()) { - return false; - } - return OnlineTextActive(user->onlineTill, now); + return !user->isServiceUser() + && !user->isBot() + && user->lastseen().isOnline(now); } bool IsUserOnline(not_null user, TimeId now) { diff --git a/Telegram/SourceFiles/data/data_peer_values.h b/Telegram/SourceFiles/data/data_peer_values.h index ad20d8243..c3a1ac9ad 100644 --- a/Telegram/SourceFiles/data/data_peer_values.h +++ b/Telegram/SourceFiles/data/data_peer_values.h @@ -20,6 +20,7 @@ namespace Data { struct Reaction; class ForumTopic; +class LastseenStatus; template inline auto FlagsValueWithMask( @@ -151,14 +152,15 @@ inline auto PeerFullFlagValue( not_null session); [[nodiscard]] TimeId SortByOnlineValue(not_null user, TimeId now); -[[nodiscard]] crl::time OnlineChangeTimeout(TimeId online, TimeId now); +[[nodiscard]] crl::time OnlineChangeTimeout( + LastseenStatus status, + TimeId now); [[nodiscard]] crl::time OnlineChangeTimeout( not_null user, TimeId now); -[[nodiscard]] QString OnlineText(TimeId online, TimeId now); +[[nodiscard]] QString OnlineText(LastseenStatus status, TimeId now); [[nodiscard]] QString OnlineText(not_null user, TimeId now); [[nodiscard]] QString OnlineTextFull(not_null user, TimeId now); -[[nodiscard]] bool OnlineTextActive(TimeId online, TimeId now); [[nodiscard]] bool OnlineTextActive(not_null user, TimeId now); [[nodiscard]] bool IsUserOnline(not_null user, TimeId now = 0); [[nodiscard]] bool ChannelHasActiveCall(not_null channel); diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 9ec603386..42aa348c3 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -731,11 +731,9 @@ not_null Session::processUser(const MTPUser &data) { } if (status && !minimal) { - const auto value = OnlineTillFromMTP(*status, result->onlineTill); - if (result->onlineTill != value) { - result->onlineTill = value; + const auto lastseen = LastseenFromMTP(*status, result->lastseen()); + if (result->updateLastseen(lastseen)) { flags |= UpdateFlag::OnlineStatus; - session().data().maybeStopWatchForOffline(result); } } @@ -1106,7 +1104,8 @@ void Session::watchForOffline(not_null user, TimeId now) { if (!Data::IsUserOnline(user, now)) { return; } - const auto till = user->onlineTill; + const auto lastseen = user->lastseen(); + const auto till = lastseen.onlineTill(); const auto &[i, ok] = _watchingForOffline.emplace(user, till); if (!ok) { if (i->second == till) { @@ -1114,7 +1113,7 @@ void Session::watchForOffline(not_null user, TimeId now) { } i->second = till; } - const auto timeout = Data::OnlineChangeTimeout(till, now); + const auto timeout = Data::OnlineChangeTimeout(lastseen, now); const auto fires = _watchForOfflineTimer.isActive() ? _watchForOfflineTimer.remainingTime() : -1; @@ -4397,7 +4396,7 @@ void Session::serviceNotification( MTPstring(), // username MTP_string("42777"), MTP_userProfilePhotoEmpty(), - MTP_userStatusRecently(), + MTP_userStatusRecently(MTP_flags(0)), MTPint(), // bot_info_version MTPVector(), MTPstring(), // bot_inline_placeholder diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp index 4c0d4ce5a..fbf1e6e8f 100644 --- a/Telegram/SourceFiles/data/data_user.cpp +++ b/Telegram/SourceFiles/data/data_user.cpp @@ -34,37 +34,26 @@ using UpdateFlag = Data::PeerUpdate::Flag; BotInfo::BotInfo() = default; -int RecentOnlineAfter(TimeId when) { - return (when > 0) ? (-when - kSetOnlineAfterActivity) : 0; -} - -bool IsRecentOnlineValue(int value) { - return (value < -kSetOnlineAfterActivity); -} - -bool IsRecentOnline(int value, TimeId now) { - return IsRecentOnlineValue(value) && (now < -value); -} - -int OnlineTillFromMTP( +Data::LastseenStatus LastseenFromMTP( const MTPUserStatus &status, - int currentOnlineTill) { - return status.match([](const MTPDuserStatusEmpty &) { - return kOnlineEmpty; - }, [&](const MTPDuserStatusRecently&) { - return IsRecentOnlineValue(currentOnlineTill) - ? currentOnlineTill - : kOnlineRecently; - }, [](const MTPDuserStatusLastWeek &) { - return kOnlineLastWeek; - }, [](const MTPDuserStatusLastMonth &) { - return kOnlineLastMonth; - }, [](const MTPDuserStatusHidden &) { - return kOnlineHidden; + Data::LastseenStatus currentStatus) { + return status.match([](const MTPDuserStatusEmpty &data) { + return Data::LastseenStatus::LongAgo(); + }, [&](const MTPDuserStatusRecently &data) { + return currentStatus.isLocalOnlineValue() + ? Data::LastseenStatus::OnlineTill( + currentStatus.onlineTill(), + true, + data.is_by_me()) + : Data::LastseenStatus::Recently(data.is_by_me()); + }, [](const MTPDuserStatusLastWeek &data) { + return Data::LastseenStatus::WithinWeek(data.is_by_me()); + }, [](const MTPDuserStatusLastMonth &data) { + return Data::LastseenStatus::WithinMonth(data.is_by_me()); }, [](const MTPDuserStatusOnline& data) { - return data.vexpires().v; + return Data::LastseenStatus::OnlineTill(data.vexpires().v); }, [](const MTPDuserStatusOffline &data) { - return data.vwas_online().v; + return Data::LastseenStatus::OnlineTill(data.vwas_online().v); }); } @@ -88,6 +77,19 @@ void UserData::setIsContact(bool is) { } } +Data::LastseenStatus UserData::lastseen() const { + return _lastseen; +} + +bool UserData::updateLastseen(Data::LastseenStatus value) { + if (_lastseen == value) { + return false; + } + _lastseen = value; + owner().maybeStopWatchForOffline(this); + return true; +} + // see Serialize::readPeer as well void UserData::setPhoto(const MTPUserProfilePhoto &photo) { photo.match([&](const MTPDuserProfilePhoto &data) { @@ -305,11 +307,14 @@ void UserData::setNameOrPhone(const QString &newNameOrPhone) { void UserData::madeAction(TimeId when) { if (isBot() || isServiceUser() || when <= 0) { return; - } else if (onlineTill <= 0 && -onlineTill < when) { - onlineTill = -when - kSetOnlineAfterActivity; - session().changes().peerUpdated(this, UpdateFlag::OnlineStatus); - } else if (onlineTill > 0 && onlineTill < when + 1) { - onlineTill = when + kSetOnlineAfterActivity; + } + const auto till = lastseen().onlineTill(); + if (till < when + 1 + && updateLastseen( + Data::LastseenStatus::OnlineTill( + when + kSetOnlineAfterActivity, + !till || lastseen().isLocalOnlineValue(), + lastseen().isHiddenByMe()))) { session().changes().peerUpdated(this, UpdateFlag::OnlineStatus); } } diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index 2951cd9ef..ff3062b81 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_chat_participant_status.h" +#include "data/data_lastseen_status.h" #include "data/data_user_names.h" #include "dialogs/dialogs_key.h" @@ -43,7 +44,7 @@ struct BotInfo { ChatAdminRights channelAdminRights; }; -enum class UserDataFlag { +enum class UserDataFlag : uint32 { Contact = (1 << 0), MutualContact = (1 << 1), Deleted = (1 << 2), @@ -73,18 +74,9 @@ enum class UserDataFlag { inline constexpr bool is_flag_type(UserDataFlag) { return true; }; using UserDataFlags = base::flags; -inline constexpr auto kOnlineEmpty = 0; -inline constexpr auto kOnlineRecently = -2; -inline constexpr auto kOnlineLastWeek = -3; -inline constexpr auto kOnlineLastMonth = -4; -inline constexpr auto kOnlineHidden = -5; - -[[nodiscard]] int RecentOnlineAfter(TimeId when); -[[nodiscard]] bool IsRecentOnlineValue(int value); -[[nodiscard]] bool IsRecentOnline(int value, TimeId now); -[[nodiscard]] int OnlineTillFromMTP( - const MTPUserStatus& status, - int currentOnlineTill); +[[nodiscard]] Data::LastseenStatus LastseenFromMTP( + const MTPUserStatus &status, + Data::LastseenStatus currentStatus); class UserData final : public PeerData { public: @@ -160,7 +152,6 @@ public: [[nodiscard]] QString editableUsername() const; [[nodiscard]] const std::vector &usernames() const; QString nameOrPhone; - TimeId onlineTill = 0; enum class ContactStatus : char { Unknown, @@ -171,6 +162,9 @@ public: [[nodiscard]] bool isContact() const; void setIsContact(bool is); + [[nodiscard]] Data::LastseenStatus lastseen() const; + bool updateLastseen(Data::LastseenStatus value); + enum class CallsStatus : char { Unknown, Enabled, @@ -202,6 +196,7 @@ private: -> const std::vector & override; Flags _flags; + Data::LastseenStatus _lastseen; Data::UsernamesInfo _username; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 91362c73e..a625d8aca 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -57,7 +57,7 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_"; if (!history) { return false; } else if (const auto user = history->peer->asUser()) { - return (user->onlineTill > 0); + return !user->lastseen().isHidden(); } return !history->isForum(); } diff --git a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp index c5e60091d..136a3f73c 100644 --- a/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp +++ b/Telegram/SourceFiles/history/view/history_view_schedule_box.cpp @@ -59,10 +59,12 @@ TimeId DefaultScheduleTime() { } bool CanScheduleUntilOnline(not_null peer) { - return !peer->isSelf() - && peer->isUser() - && !peer->asUser()->isBot() - && (peer->asUser()->onlineTill > 0); + if (const auto user = peer->asUser()) { + return !user->isSelf() + && !user->isBot() + && !user->lastseen().isHidden(); + } + return false; } void ScheduleBox( diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index a670d8a91..c06eddf3f 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -1620,7 +1620,7 @@ void TopBarWidget::updateOnlineDisplay() { auto online = 0; auto onlyMe = true; for (const auto &user : chat->participants) { - if (user->onlineTill > now) { + if (user->lastseen().isOnline(now)) { ++online; if (onlyMe && user != self) onlyMe = false; } @@ -1648,7 +1648,7 @@ void TopBarWidget::updateOnlineDisplay() { auto online = 0; auto onlyMe = true; for (auto &participant : std::as_const(channel->mgInfo->lastParticipants)) { - if (participant->onlineTill > now) { + if (participant->lastseen().isOnline(now)) { ++online; if (onlyMe && participant != self) { onlyMe = false; diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index 7968c48d5..373a2723a 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -326,7 +326,7 @@ Cover::Cover( : nullptr) , _name(this, _st.name) , _status(this, _st.status) -, _showLastSeen(this, tr::lng_status_lastseen_show(), _st.showLastSeen) +, _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen) , _refreshStatusTimer([this] { refreshStatusText(); }) { _peer->updateFull(); @@ -376,7 +376,7 @@ void Cover::setupShowLastSeen() { && !user->isServiceUser() && user->session().premiumPossible()) { if (user->session().premium()) { - if (user->onlineTill == kOnlineHidden) { + if (user->lastseen().isHiddenByMe()) { user->updateFullForced(); } _showLastSeen->hide(); @@ -390,12 +390,12 @@ void Cover::setupShowLastSeen() { Data::AmPremiumValue(&user->session()) ) | rpl::start_with_next([=](auto, bool premium) { const auto wasShown = !_showLastSeen->isHidden(); - const auto onlineHidden = (user->onlineTill == kOnlineHidden); - const auto shown = onlineHidden + const auto hiddenByMe = user->lastseen().isHiddenByMe(); + const auto shown = hiddenByMe && !premium && user->session().premiumPossible(); _showLastSeen->setVisible(shown); - if (wasShown && premium && onlineHidden) { + if (wasShown && premium && hiddenByMe) { user->updateFullForced(); } }, _showLastSeen->lifetime()); @@ -405,7 +405,7 @@ void Cover::setupShowLastSeen() { ) | rpl::filter([=](Api::UserPrivacy::Rule rule) { return (rule.option == Api::UserPrivacy::Option::Everyone); }) | rpl::start_with_next([=] { - if (user->onlineTill == kOnlineHidden) { + if (user->lastseen().isHiddenByMe()) { user->updateFullForced(); } }, _showLastSeen->lifetime()); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 67e24c28d..5fac44c06 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -90,10 +90,9 @@ userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true p userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; userStatusOffline#8c703f was_online:int = UserStatus; -userStatusRecently#e26f42f1 = UserStatus; -userStatusLastWeek#7bf09fc = UserStatus; -userStatusLastMonth#77ebc742 = UserStatus; -userStatusHidden#cf7d64b1 = UserStatus; +userStatusRecently#7b197dc8 flags:# by_me:flags.0?true = UserStatus; +userStatusLastWeek#541a1d1a flags:# by_me:flags.0?true = UserStatus; +userStatusLastMonth#65899777 flags:# by_me:flags.0?true = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm index 63ceb76fb..3c6bfd25e 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm @@ -134,22 +134,13 @@ NSRect PeerRectByIndex(int index) { kCircleDiameter); } -TimeId CalculateOnlineTill(not_null peer) { - if (peer->isSelf() || peer->isRepliesChat()) { - return 0; - } +[[nodiscard]] Data::LastseenStatus CalculateLastseenStatus( + not_null peer) { if (const auto user = peer->asUser()) { - if (!user->isServiceUser() && !user->isBot()) { - const auto onlineTill = user->onlineTill; - return IsRecentOnlineValue(onlineTill) - ? -onlineTill - : (onlineTill <= 0) - ? 0 - : onlineTill; - } + return user->lastseen(); } - return 0; -}; + return Data::LastseenStatus(); +} } // namespace @@ -175,7 +166,7 @@ TimeId CalculateOnlineTill(not_null peer) { bool onTop = false; Ui::Animations::Simple onlineAnimation; - TimeId onlineTill = 0; + Data::LastseenStatus lastseen; }; rpl::lifetime _lifetime; Main::Session *_session; @@ -570,10 +561,10 @@ TimeId CalculateOnlineTill(not_null peer) { const auto callTimer = [=](const auto &pin) { onlineTimer->cancel(); - if (pin->onlineTill) { - const auto time = pin->onlineTill - base::unixtime::now(); - if (time > 0) { - onlineTimer->callOnce(std::min(86400, time) + if (const auto till = pin->lastseen.onlineTill()) { + const auto left = till - base::unixtime::now(); + if (left > 0) { + onlineTimer->callOnce(std::min(86400, left) * crl::time(1000)); } } @@ -595,7 +586,7 @@ TimeId CalculateOnlineTill(not_null peer) { return; } const auto &pin = *it; - pin->onlineTill = CalculateOnlineTill(pin->peer); + pin->lastseen = CalculateLastseenStatus(pin->peer); callTimer(pin); @@ -603,9 +594,8 @@ TimeId CalculateOnlineTill(not_null peer) { pin->onlineAnimation.stop(); return; } - const auto online = Data::OnlineTextActive( - pin->onlineTill, - base::unixtime::now()); + const auto now = base::unixtime::now(); + const auto online = pin->lastseen.isOnline(now); if (pin->onlineAnimation.animating()) { pin->onlineAnimation.change( online ? 1. : 0., @@ -638,13 +628,12 @@ TimeId CalculateOnlineTill(not_null peer) { const auto index = pair.second; auto peer = pair.first.history()->peer; auto view = peer->createUserpicView(); - const auto onlineTill = CalculateOnlineTill(peer); - Pin pin = { + return std::make_unique(Pin{ .peer = std::move(peer), .userpicView = std::move(view), .index = index, - .onlineTill = onlineTill }; - return std::make_unique(std::move(pin)); + .lastseen = CalculateLastseenStatus(peer), + }); }) | ranges::to_vector; _selfUnpinned = ranges::none_of(peers, &PeerData::isSelf); _repliesUnpinned = ranges::none_of(peers, &PeerData::isRepliesChat); @@ -837,9 +826,8 @@ TimeId CalculateOnlineTill(not_null peer) { CGImageRelease(image); return; } - const auto online = Data::OnlineTextActive( - pin->onlineTill, - base::unixtime::now()); + const auto now = base::unixtime::now(); + const auto online = pin->lastseen.isOnline(now); const auto value = pin->onlineAnimation.value(online ? 1. : 0.); if (value < 0.05) { return; diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.cpp b/Telegram/SourceFiles/profile/profile_block_group_members.cpp index 3780d8dd3..e155a19f9 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_group_members.cpp @@ -148,9 +148,8 @@ void GroupMembersWidget::refreshUserOnline(UserData *user) { _now = base::unixtime::now(); auto member = getMember(it->second); - member->statusHasOnlineColor = !user->isBot() - && Data::OnlineTextActive(user->onlineTill, _now); - member->onlineTill = user->onlineTill; + member->lastseen = user->lastseen(); + member->statusHasOnlineColor = Data::OnlineTextActive(user, _now); member->onlineForSort = user->isSelf() ? std::numeric_limits::max() : Data::SortByOnlineValue(user, _now); @@ -184,10 +183,10 @@ void GroupMembersWidget::updateItemStatusText(Item *item) { : tr::lng_status_bot_not_reads_all(tr::now); member->onlineTextTill = _now + 86400; } else { - member->statusHasOnlineColor = Data::OnlineTextActive(member->onlineTill, _now); - member->statusText = Data::OnlineText(member->onlineTill, _now); + member->statusHasOnlineColor = member->lastseen.isOnline(_now); + member->statusText = Data::OnlineText(member->lastseen, _now); const auto changeInMs = Data::OnlineChangeTimeout( - member->onlineTill, + member->lastseen, _now); member->onlineTextTill = _now + TimeId(changeInMs / 1000); } @@ -247,7 +246,8 @@ void GroupMembersWidget::updateOnlineCount() { for (const auto item : items()) { auto member = getMember(item); auto user = member->user(); - auto isOnline = !user->isBot() && Data::OnlineTextActive(member->onlineTill, _now); + auto isOnline = !user->isBot() + && member->lastseen.isOnline(_now); if (member->statusHasOnlineColor != isOnline) { member->statusHasOnlineColor = isOnline; member->statusText = QString(); @@ -439,9 +439,9 @@ auto GroupMembersWidget::computeMember(not_null user) if (it == _membersByUser.cend()) { auto member = new Member(user); it = _membersByUser.emplace(user, member).first; + member->lastseen = user->lastseen(); member->statusHasOnlineColor = !user->isBot() - && Data::OnlineTextActive(user->onlineTill, _now); - member->onlineTill = user->onlineTill; + && member->lastseen.isOnline(_now); member->onlineForSort = Data::SortByOnlineValue(user, _now); } return it->second; @@ -462,7 +462,7 @@ void GroupMembersWidget::updateOnlineDisplay() { } auto member = getMember(item); bool isOnline = !member->user()->isBot() - && Data::OnlineTextActive(member->onlineTill, _now); + && member->lastseen.isOnline(_now); if (!isOnline) { changed = true; } diff --git a/Telegram/SourceFiles/profile/profile_block_group_members.h b/Telegram/SourceFiles/profile/profile_block_group_members.h index 84884778b..89ae41159 100644 --- a/Telegram/SourceFiles/profile/profile_block_group_members.h +++ b/Telegram/SourceFiles/profile/profile_block_group_members.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "base/timer.h" +#include "data/data_lastseen_status.h" #include "profile/profile_block_peer_list.h" namespace Ui { @@ -61,7 +62,7 @@ private: not_null user() const; TimeId onlineTextTill = 0; - TimeId onlineTill = 0; + Data::LastseenStatus lastseen; TimeId onlineForSort = 0; }; Member *getMember(Item *item) { diff --git a/Telegram/SourceFiles/storage/serialize_peer.cpp b/Telegram/SourceFiles/storage/serialize_peer.cpp index a9a4ee640..861bebdc9 100644 --- a/Telegram/SourceFiles/storage/serialize_peer.cpp +++ b/Telegram/SourceFiles/storage/serialize_peer.cpp @@ -21,7 +21,7 @@ namespace { constexpr auto kModernImageLocationTag = std::numeric_limits::min(); constexpr auto kVersionTag = uint64(0x77FF'FFFF'FFFF'FFFFULL); -constexpr auto kVersion = 1; +constexpr auto kVersion = 2; } // namespace @@ -111,12 +111,12 @@ std::optional readImageLocation( } uint32 peerSize(not_null peer) { - uint32 result = sizeof(quint64) - + sizeof(quint64) - + sizeof(qint32) - + sizeof(quint64) + uint32 result = sizeof(quint64) // id + + sizeof(quint64) // version tag + + sizeof(qint32) // version + + sizeof(quint64) // userpic photo id + imageLocationSize(peer->userpicLocation()) - + sizeof(qint32); + + sizeof(qint32); // userpic has video if (const auto user = peer->asUser()) { result += stringSize(user->firstName) + stringSize(user->lastName) @@ -124,7 +124,7 @@ uint32 peerSize(not_null peer) { + stringSize(user->username()) + sizeof(quint64) // access + sizeof(qint32) // flags - + sizeof(qint32) // onlineTill + + sizeof(quint32) // lastseen + sizeof(qint32) // contact + sizeof(qint32); // botInfoVersion } else if (const auto chat = peer->asChat()) { @@ -168,7 +168,7 @@ void writePeer(QDataStream &stream, not_null peer) { << quint64(user->accessHash()) << qint32(user->flags()) << botInlinePlaceholder - << qint32(user->onlineTill) + << quint32(user->lastseen().serialize()) << qint32(user->isContact() ? 1 : 0) << qint32(user->isBot() ? user->botInfo->version : -1); } else if (const auto chat = peer->asChat()) { @@ -233,7 +233,8 @@ PeerData *readPeer( if (const auto user = result->asUser()) { QString first, last, phone, username, inlinePlaceholder; quint64 access; - qint32 flags = 0, onlineTill, contact, botInfoVersion; + qint32 flags = 0, contact, botInfoVersion; + quint32 lastseen; stream >> first >> last >> phone >> username >> access; if (streamAppVersion >= 9012) { stream >> flags; @@ -241,7 +242,7 @@ PeerData *readPeer( if (streamAppVersion >= 9016) { stream >> inlinePlaceholder; } - stream >> onlineTill >> contact >> botInfoVersion; + stream >> lastseen >> contact >> botInfoVersion; userpicAccessHash = access; @@ -285,7 +286,9 @@ PeerData *readPeer( user->setFlags((user->flags() & ~flagsMask) | flagsSet); } user->setAccessHash(access); - user->onlineTill = onlineTill; + user->updateLastseen((version > 1) + ? Data::LastseenStatus::FromSerialized(lastseen) + : Data::LastseenStatus::FromLegacy(lastseen)); user->setIsContact(contact == 1); user->setBotInfoVersion(botInfoVersion); if (!inlinePlaceholder.isEmpty() && user->isBot()) {