diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 51fdd65d39..27804c4cf0 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -945,6 +945,7 @@ void Updates::updateOnline(crl::time lastNonIdleTime, bool gotOtherOffline) { Data::PeerUpdate::Flag::OnlineStatus); if (!isOnline) { // Went offline, so we need to save message draft to the cloud. api().saveCurrentDraftToCloud(); + session().data().maybeStopWatchForOffline(self); } _lastSetOnline = ms; @@ -1856,6 +1857,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); + session().data().maybeStopWatchForOffline(user); } if (UserId(d.vuser_id()) == session().userId()) { if (d.vstatus().type() == mtpc_userStatusOffline diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index b7c397be02..e27fe368da 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -1843,6 +1843,7 @@ void ApiWrap::updatePrivacyLastSeens() { session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); + session().data().maybeStopWatchForOffline(user); }); if (_contactsStatusesRequestId) { @@ -1856,12 +1857,15 @@ void ApiWrap::updatePrivacyLastSeens() { auto &data = item.c_contactStatus(); if (auto user = _session->data().userLoaded(data.vuser_id())) { auto oldOnlineTill = user->onlineTill; - auto newOnlineTill = OnlineTillFromStatus(data.vstatus(), oldOnlineTill); + auto newOnlineTill = OnlineTillFromStatus( + data.vstatus(), + oldOnlineTill); if (oldOnlineTill != newOnlineTill) { user->onlineTill = newOnlineTill; session().changes().peerUpdated( user, Data::PeerUpdate::Flag::OnlineStatus); + session().data().maybeStopWatchForOffline(user); } } } diff --git a/Telegram/SourceFiles/data/data_peer_values.cpp b/Telegram/SourceFiles/data/data_peer_values.cpp index 8f8cf779e0..08feba690d 100644 --- a/Telegram/SourceFiles/data/data_peer_values.cpp +++ b/Telegram/SourceFiles/data/data_peer_values.cpp @@ -460,8 +460,11 @@ bool OnlineTextActive(not_null user, TimeId now) { return OnlineTextActive(user->onlineTill, now); } -bool IsUserOnline(not_null user) { - return OnlineTextActive(user, base::unixtime::now()); +bool IsUserOnline(not_null user, TimeId now) { + if (!now) { + now = base::unixtime::now(); + } + return OnlineTextActive(user, now); } bool ChannelHasActiveCall(not_null channel) { diff --git a/Telegram/SourceFiles/data/data_peer_values.h b/Telegram/SourceFiles/data/data_peer_values.h index e4d0b25f05..7a5911775d 100644 --- a/Telegram/SourceFiles/data/data_peer_values.h +++ b/Telegram/SourceFiles/data/data_peer_values.h @@ -122,7 +122,7 @@ inline auto PeerFullFlagValue( [[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); +[[nodiscard]] bool IsUserOnline(not_null user, TimeId now = 0); [[nodiscard]] bool ChannelHasActiveCall(not_null channel); [[nodiscard]] rpl::producer PeerUserpicImageValue( diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 7b7ffe9f5a..e0bc63e44a 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -246,6 +246,7 @@ Session::Session(not_null session) , _ttlCheckTimer([=] { checkTTLs(); }) , _selfDestructTimer([=] { checkSelfDestructItems(); }) , _pollsClosingTimer([=] { checkPollsClosings(); }) +, _watchForOfflineTimer([=] { checkLocalUsersWentOffline(); }) , _groups(this) , _chatsFilters(std::make_unique(this)) , _scheduledMessages(std::make_unique(this)) @@ -644,6 +645,7 @@ not_null Session::processUser(const MTPUser &data) { if (oldOnlineTill != newOnlineTill) { result->onlineTill = newOnlineTill; flags |= UpdateFlag::OnlineStatus; + session().data().maybeStopWatchForOffline(result); } } @@ -968,6 +970,64 @@ GroupCall *Session::groupCall(CallId callId) const { return (i != end(_groupCalls)) ? i->second.get() : nullptr; } +void Session::watchForOffline(not_null user, TimeId now) { + if (!now) { + now = base::unixtime::now(); + } + if (!Data::IsUserOnline(user, now)) { + return; + } + const auto till = user->onlineTill; + const auto [i, ok] = _watchingForOffline.emplace(user, till); + if (!ok) { + if (i->second == till) { + return; + } + i->second = till; + } + const auto timeout = Data::OnlineChangeTimeout(till, now); + const auto fires = _watchForOfflineTimer.isActive() + ? _watchForOfflineTimer.remainingTime() + : -1; + if (fires >= 0 && fires <= timeout) { + return; + } + _watchForOfflineTimer.callOnce(std::max(timeout, crl::time(1))); +} + +void Session::maybeStopWatchForOffline(not_null user) { + if (Data::IsUserOnline(user)) { + return; + } else if (_watchingForOffline.remove(user) + && _watchingForOffline.empty()) { + _watchForOfflineTimer.cancel(); + } +} + +void Session::checkLocalUsersWentOffline() { + _watchForOfflineTimer.cancel(); + + auto minimal = 86400 * crl::time(1000); + const auto now = base::unixtime::now(); + for (auto i = begin(_watchingForOffline) + ; i != end(_watchingForOffline);) { + const auto user = i->first; + if (!Data::IsUserOnline(user, now)) { + i = _watchingForOffline.erase(i); + session().changes().peerUpdated( + user, + PeerUpdate::Flag::OnlineStatus); + } else { + const auto timeout = Data::OnlineChangeTimeout(user, now); + accumulate_min(minimal, timeout); + ++i; + } + } + if (!_watchingForOffline.empty()) { + _watchForOfflineTimer.callOnce(std::max(minimal, crl::time(1))); + } +} + auto Session::invitedToCallUsers(CallId callId) const -> const base::flat_set> & { static const base::flat_set> kEmpty; diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index d491e897b3..85afbc2a56 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -192,6 +192,9 @@ public: void unregisterGroupCall(not_null call); GroupCall *groupCall(CallId callId) const; + void watchForOffline(not_null user, TimeId now = 0); + void maybeStopWatchForOffline(not_null user); + [[nodiscard]] auto invitedToCallUsers(CallId callId) const -> const base::flat_set> &; void registerInvitedToCallUser( @@ -717,6 +720,7 @@ private: void setupUserIsContactViewer(); void checkSelfDestructItems(); + void checkLocalUsersWentOffline(); void scheduleNextTTLs(); void checkTTLs(); @@ -976,6 +980,9 @@ private: std::vector _wallpapers; uint64 _wallpapersHash = 0; + base::flat_map, TimeId> _watchingForOffline; + base::Timer _watchForOfflineTimer; + rpl::event_stream _webViewResultSent; Groups _groups; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 08b613c5d2..784baa1073 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -193,7 +193,7 @@ InnerWidget::InnerWidget( session().data().sendActionManager().speakingAnimationUpdated( ) | rpl::start_with_next([=](not_null history) { - updateDialogRowCornerStatus(history); + repaintDialogRowCornerStatus(history); }, lifetime()); setupOnlineStatusCheck(); @@ -3181,15 +3181,15 @@ void InnerWidget::setupOnlineStatusCheck() { Data::PeerUpdate::Flag::OnlineStatus | Data::PeerUpdate::Flag::GroupCall ) | rpl::start_with_next([=](const Data::PeerUpdate &update) { - if (update.peer->isUser()) { - userOnlineUpdated(update.peer); + if (const auto user = update.peer->asUser()) { + userOnlineUpdated(user); } else { groupHasCallUpdated(update.peer); } }, lifetime()); } -void InnerWidget::updateDialogRowCornerStatus(not_null history) { +void InnerWidget::repaintDialogRowCornerStatus(not_null history) { const auto user = history->peer->isUser(); const auto size = user ? st::dialogsOnlineBadgeSize @@ -3217,16 +3217,15 @@ void InnerWidget::updateDialogRowCornerStatus(not_null history) { UpdateRowSection::Default | UpdateRowSection::Filtered); } -void InnerWidget::userOnlineUpdated(not_null peer) { - const auto user = peer->isSelf() ? nullptr : peer->asUser(); - if (!user) { +void InnerWidget::userOnlineUpdated(not_null user) { + if (!user->isSelf()) { return; } const auto history = session().data().historyLoaded(user); if (!history) { return; } - updateRowCornerStatusShown(history, Data::IsUserOnline(user)); + updateRowCornerStatusShown(history); } void InnerWidget::groupHasCallUpdated(not_null peer) { @@ -3238,14 +3237,12 @@ void InnerWidget::groupHasCallUpdated(not_null peer) { if (!history) { return; } - updateRowCornerStatusShown(history, Data::ChannelHasActiveCall(group)); + updateRowCornerStatusShown(history); } -void InnerWidget::updateRowCornerStatusShown( - not_null history, - bool shown) { +void InnerWidget::updateRowCornerStatusShown(not_null history) { const auto repaint = [=] { - updateDialogRowCornerStatus(history); + repaintDialogRowCornerStatus(history); }; repaint(); diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index dc842c77ae..a0a80c9317 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -248,13 +248,11 @@ private: int defaultRowTop(not_null row) const; void setupOnlineStatusCheck(); - void userOnlineUpdated(not_null peer); + void userOnlineUpdated(not_null user); void groupHasCallUpdated(not_null peer); - void updateRowCornerStatusShown( - not_null history, - bool shown); - void updateDialogRowCornerStatus(not_null history); + void updateRowCornerStatusShown(not_null history); + void repaintDialogRowCornerStatus(not_null history); void setupShortcuts(); RowDescriptor computeJump( diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index dd6da06d6a..5af607cf1e 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -15,10 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_video_userpic.h" #include "dialogs/ui/dialogs_layout.h" #include "data/data_folder.h" +#include "data/data_session.h" #include "data/data_peer_values.h" #include "history/history.h" #include "history/history_item.h" #include "lang/lang_keys.h" +#include "base/unixtime.h" #include "mainwidget.h" #include "styles/style_dialogs.h" @@ -196,15 +198,20 @@ void Row::setCornerBadgeShown( void Row::updateCornerBadgeShown( not_null peer, Fn updateCallback) const { + const auto user = peer->asUser(); + const auto now = user ? base::unixtime::now() : TimeId(); const auto shown = [&] { - if (const auto user = peer->asUser()) { - return Data::IsUserOnline(user); + if (user) { + return Data::IsUserOnline(user, now); } else if (const auto channel = peer->asChannel()) { return Data::ChannelHasActiveCall(channel); } return false; }(); setCornerBadgeShown(shown, std::move(updateCallback)); + if (shown && user) { + peer->owner().watchForOffline(user, now); + } } void Row::ensureCornerBadgeUserpic() const { 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 2b29389b89..62a3044eb4 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -1338,9 +1338,9 @@ void TopBarWidget::updateOnlineDisplayTimer() { } const auto now = base::unixtime::now(); - auto minTimeout = crl::time(86400); + auto minTimeout = 86400 * crl::time(1000); const auto handleUser = [&](not_null user) { - auto hisTimeout = Data::OnlineChangeTimeout(user, now); + const auto hisTimeout = Data::OnlineChangeTimeout(user, now); accumulate_min(minTimeout, hisTimeout); }; if (const auto user = peer->asUser()) {