mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Proof-of-concept read time in private chats.
This commit is contained in:
parent
e63d573414
commit
474f1118b6
4 changed files with 128 additions and 20 deletions
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "api/api_who_reacted.h"
|
#include "api/api_who_reacted.h"
|
||||||
|
|
||||||
|
#include "api/api_global_privacy.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "data/stickers/data_custom_emoji.h"
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
@ -19,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
#include "data/data_message_reaction_id.h"
|
#include "data/data_message_reaction_id.h"
|
||||||
|
#include "data/data_peer_values.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -36,10 +38,11 @@ namespace {
|
||||||
constexpr auto kContextReactionsLimit = 50;
|
constexpr auto kContextReactionsLimit = 50;
|
||||||
|
|
||||||
using Data::ReactionId;
|
using Data::ReactionId;
|
||||||
|
using WhoReadState = Ui::WhoReadState;
|
||||||
|
|
||||||
struct Peers {
|
struct Peers {
|
||||||
std::vector<WhoReadPeer> list;
|
std::vector<WhoReadPeer> list;
|
||||||
bool unknown = false;
|
WhoReadState state = WhoReadState::Empty;
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const Peers &a,
|
const Peers &a,
|
||||||
|
@ -59,7 +62,7 @@ struct PeersWithReactions {
|
||||||
std::vector<PeerWithReaction> list;
|
std::vector<PeerWithReaction> list;
|
||||||
std::vector<WhoReadPeer> read;
|
std::vector<WhoReadPeer> read;
|
||||||
int fullReactionsCount = 0;
|
int fullReactionsCount = 0;
|
||||||
bool unknown = false;
|
WhoReadState state = WhoReadState::Empty;
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const PeersWithReactions &a,
|
const PeersWithReactions &a,
|
||||||
|
@ -68,7 +71,7 @@ struct PeersWithReactions {
|
||||||
|
|
||||||
struct CachedRead {
|
struct CachedRead {
|
||||||
CachedRead()
|
CachedRead()
|
||||||
: data(Peers{ .unknown = true }) {
|
: data(Peers{ .state = WhoReadState::Unknown }) {
|
||||||
}
|
}
|
||||||
rpl::variable<Peers> data;
|
rpl::variable<Peers> data;
|
||||||
mtpRequestId requestId = 0;
|
mtpRequestId requestId = 0;
|
||||||
|
@ -76,7 +79,7 @@ struct CachedRead {
|
||||||
|
|
||||||
struct CachedReacted {
|
struct CachedReacted {
|
||||||
CachedReacted()
|
CachedReacted()
|
||||||
: data(PeersWithReactions{ .unknown = true }) {
|
: data(PeersWithReactions{ .state = WhoReadState::Unknown }) {
|
||||||
}
|
}
|
||||||
rpl::variable<PeersWithReactions> data;
|
rpl::variable<PeersWithReactions> data;
|
||||||
mtpRequestId requestId = 0;
|
mtpRequestId requestId = 0;
|
||||||
|
@ -186,6 +189,27 @@ struct State {
|
||||||
context->cachedReacted.erase(j);
|
context->cachedReacted.erase(j);
|
||||||
}
|
}
|
||||||
}, context->subscriptions[session]);
|
}, context->subscriptions[session]);
|
||||||
|
Data::AmPremiumValue(
|
||||||
|
session
|
||||||
|
) | rpl::skip(1) | rpl::filter(
|
||||||
|
rpl::mappers::_1
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
for (auto &[item, cache] : context->cachedRead) {
|
||||||
|
if (cache.data.current().state == Ui::WhoReadState::MyHidden) {
|
||||||
|
cache.data = Peers{ .state = Ui::WhoReadState::Unknown };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, context->subscriptions[session]);
|
||||||
|
session->api().globalPrivacy().hideReadTime(
|
||||||
|
) | rpl::skip(1) | rpl::filter(
|
||||||
|
!rpl::mappers::_1
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
for (auto &[item, cache] : context->cachedRead) {
|
||||||
|
if (cache.data.current().state == Ui::WhoReadState::MyHidden) {
|
||||||
|
cache.data = Peers{ .state = Ui::WhoReadState::Unknown };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, context->subscriptions[session]);
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +246,38 @@ struct State {
|
||||||
}
|
}
|
||||||
const auto context = PreparedContextAt(weak.data(), session);
|
const auto context = PreparedContextAt(weak.data(), session);
|
||||||
auto &entry = context->cacheRead(item);
|
auto &entry = context->cacheRead(item);
|
||||||
if (!entry.requestId) {
|
if (entry.requestId) {
|
||||||
|
} else if (const auto user = item->history()->peer->asUser()) {
|
||||||
|
entry.requestId = session->api().request(
|
||||||
|
MTPmessages_GetOutboxReadDate(
|
||||||
|
user->input,
|
||||||
|
MTP_int(item->id)
|
||||||
|
)
|
||||||
|
).done([=](const MTPOutboxReadDate &result) {
|
||||||
|
const auto &data = result.data();
|
||||||
|
auto &entry = context->cacheRead(item);
|
||||||
|
entry.requestId = 0;
|
||||||
|
auto parsed = Peers();
|
||||||
|
parsed.list.push_back({
|
||||||
|
.peer = user->id,
|
||||||
|
.date = data.vdate().v,
|
||||||
|
});
|
||||||
|
entry.data = std::move(parsed);
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
auto &entry = context->cacheRead(item);
|
||||||
|
entry.requestId = 0;
|
||||||
|
if (entry.data.current().state == WhoReadState::Unknown) {
|
||||||
|
const auto &text = error.type();
|
||||||
|
entry.data = (text == u"YOUR_PRIVACY_RESTRICTED"_q)
|
||||||
|
? Peers{ .state = WhoReadState::MyHidden }
|
||||||
|
: (text == u"USER_PRIVACY_RESTRICTED"_q)
|
||||||
|
? Peers{ .state = WhoReadState::HisHidden }
|
||||||
|
: (text == u"MESSAGE_TOO_OLD"_q)
|
||||||
|
? Peers{ .state = WhoReadState::TooOld }
|
||||||
|
: Peers{ .state = WhoReadState::Empty };
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
} else {
|
||||||
entry.requestId = session->api().request(
|
entry.requestId = session->api().request(
|
||||||
MTPmessages_GetMessageReadParticipants(
|
MTPmessages_GetMessageReadParticipants(
|
||||||
item->history()->peer->input,
|
item->history()->peer->input,
|
||||||
|
@ -243,8 +298,8 @@ struct State {
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
auto &entry = context->cacheRead(item);
|
auto &entry = context->cacheRead(item);
|
||||||
entry.requestId = 0;
|
entry.requestId = 0;
|
||||||
if (entry.data.current().unknown) {
|
if (entry.data.current().state == WhoReadState::Unknown) {
|
||||||
entry.data = Peers();
|
entry.data = Peers{ .state = WhoReadState::Empty };
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
@ -258,7 +313,7 @@ struct State {
|
||||||
.list = peers.list | ranges::views::transform([](WhoReadPeer peer) {
|
.list = peers.list | ranges::views::transform([](WhoReadPeer peer) {
|
||||||
return PeerWithReaction{ .peerWithDate = peer };
|
return PeerWithReaction{ .peerWithDate = peer };
|
||||||
}) | ranges::to_vector,
|
}) | ranges::to_vector,
|
||||||
.unknown = peers.unknown,
|
.state = peers.state,
|
||||||
};
|
};
|
||||||
result.read = std::move(peers.list);
|
result.read = std::move(peers.list);
|
||||||
return result;
|
return result;
|
||||||
|
@ -319,8 +374,10 @@ struct State {
|
||||||
}).fail([=] {
|
}).fail([=] {
|
||||||
auto &entry = context->cacheReacted(item, reaction);
|
auto &entry = context->cacheReacted(item, reaction);
|
||||||
entry.requestId = 0;
|
entry.requestId = 0;
|
||||||
if (entry.data.current().unknown) {
|
if (entry.data.current().state == WhoReadState::Unknown) {
|
||||||
entry.data = PeersWithReactions();
|
entry.data = PeersWithReactions{
|
||||||
|
.state = WhoReadState::Empty,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
@ -336,8 +393,9 @@ struct State {
|
||||||
WhoReactedIds(item, {}, context),
|
WhoReactedIds(item, {}, context),
|
||||||
WhoReadIds(item, context)
|
WhoReadIds(item, context)
|
||||||
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
) | rpl::map([=](PeersWithReactions &&reacted, Peers &&read) {
|
||||||
if (reacted.unknown || read.unknown) {
|
if (reacted.state == WhoReadState::Unknown
|
||||||
return PeersWithReactions{ .unknown = true };
|
|| read.state == WhoReadState::Unknown) {
|
||||||
|
return PeersWithReactions{ .state = WhoReadState::Unknown};
|
||||||
}
|
}
|
||||||
auto &list = reacted.list;
|
auto &list = reacted.list;
|
||||||
for (const auto &peerWithDate : read.list) {
|
for (const auto &peerWithDate : read.list) {
|
||||||
|
@ -531,16 +589,17 @@ rpl::producer<Ui::WhoReadContent> WhoReacted(
|
||||||
std::move(
|
std::move(
|
||||||
idsWithReactions
|
idsWithReactions
|
||||||
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
|
) | rpl::start_with_next([=](PeersWithReactions &&peers) {
|
||||||
if (peers.unknown) {
|
if (peers.state == WhoReadState::Unknown) {
|
||||||
state->userpics.clear();
|
state->userpics.clear();
|
||||||
consumer.put_next(Ui::WhoReadContent{
|
consumer.put_next(Ui::WhoReadContent{
|
||||||
.type = state->current.type,
|
.type = state->current.type,
|
||||||
.fullReactionsCount = state->current.fullReactionsCount,
|
.fullReactionsCount = state->current.fullReactionsCount,
|
||||||
.fullReadCount = state->current.fullReadCount,
|
.fullReadCount = state->current.fullReadCount,
|
||||||
.unknown = true,
|
.state = WhoReadState::Unknown,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
state->current.state = peers.state;
|
||||||
state->current.fullReadCount = int(peers.read.size());
|
state->current.fullReadCount = int(peers.read.size());
|
||||||
state->current.fullReactionsCount = peers.fullReactionsCount;
|
state->current.fullReactionsCount = peers.fullReactionsCount;
|
||||||
if (whoReadIds) {
|
if (whoReadIds) {
|
||||||
|
@ -631,6 +690,22 @@ bool WhoReadExists(not_null<HistoryItem*> item) {
|
||||||
}
|
}
|
||||||
const auto history = item->history();
|
const auto history = item->history();
|
||||||
const auto peer = history->peer;
|
const auto peer = history->peer;
|
||||||
|
if (const auto user = peer->asUser()) {
|
||||||
|
if (user->isSelf()
|
||||||
|
|| user->isBot()
|
||||||
|
|| user->isServiceUser()
|
||||||
|
|| user->readDatesPrivate()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto &appConfig = peer->session().account().appConfig();
|
||||||
|
const auto expirePeriod = appConfig.get<int>(
|
||||||
|
"pm_read_date_expire_period",
|
||||||
|
7 * 86400);
|
||||||
|
if (item->date() + int64(expirePeriod) <= int64(base::unixtime::now())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const auto chat = peer->asChat();
|
const auto chat = peer->asChat();
|
||||||
const auto megagroup = peer->asMegagroup();
|
const auto megagroup = peer->asMegagroup();
|
||||||
if ((!chat && !megagroup)
|
if ((!chat && !megagroup)
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "api/api_attached_stickers.h"
|
#include "api/api_attached_stickers.h"
|
||||||
#include "api/api_editing.h"
|
#include "api/api_editing.h"
|
||||||
|
#include "api/api_global_privacy.h"
|
||||||
#include "api/api_polls.h"
|
#include "api/api_polls.h"
|
||||||
#include "api/api_report.h"
|
#include "api/api_report.h"
|
||||||
#include "api/api_ringtones.h"
|
#include "api/api_ringtones.h"
|
||||||
|
@ -41,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "boxes/delete_messages_box.h"
|
#include "boxes/delete_messages_box.h"
|
||||||
#include "boxes/report_messages_box.h"
|
#include "boxes/report_messages_box.h"
|
||||||
|
#include "boxes/premium_preview_box.h"
|
||||||
#include "boxes/sticker_set_box.h"
|
#include "boxes/sticker_set_box.h"
|
||||||
#include "boxes/stickers_box.h"
|
#include "boxes/stickers_box.h"
|
||||||
#include "boxes/translate_box.h"
|
#include "boxes/translate_box.h"
|
||||||
|
@ -62,6 +64,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
|
#include "settings/settings_premium.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
#include "window/window_controller.h"
|
#include "window/window_controller.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
@ -1265,11 +1268,23 @@ void AddWhoReactedAction(
|
||||||
not_null<Window::SessionController*> controller) {
|
not_null<Window::SessionController*> controller) {
|
||||||
const auto whoReadIds = std::make_shared<Api::WhoReadList>();
|
const auto whoReadIds = std::make_shared<Api::WhoReadList>();
|
||||||
const auto weak = Ui::MakeWeak(menu.get());
|
const auto weak = Ui::MakeWeak(menu.get());
|
||||||
|
const auto user = item->history()->peer;
|
||||||
const auto participantChosen = [=](uint64 id) {
|
const auto participantChosen = [=](uint64 id) {
|
||||||
if (const auto strong = weak.data()) {
|
if (const auto strong = weak.data()) {
|
||||||
strong->hideMenu();
|
strong->hideMenu();
|
||||||
}
|
}
|
||||||
controller->showPeerInfo(PeerId(id));
|
if (id) {
|
||||||
|
controller->showPeerInfo(PeerId(id));
|
||||||
|
} else {
|
||||||
|
const auto type = ShowOrPremium::ReadTime;
|
||||||
|
auto box = Box(ShowOrPremiumBox, type, user->shortName(), [=] {
|
||||||
|
const auto api = &controller->session().api();
|
||||||
|
api->globalPrivacy().updateHideReadTime({});
|
||||||
|
}, [=] {
|
||||||
|
Settings::ShowPremium(controller, u"revtime_hidden"_q);
|
||||||
|
});
|
||||||
|
controller->show(std::move(box));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const auto showAllChosen = [=, itemId = item->fullId()]{
|
const auto showAllChosen = [=, itemId = item->fullId()]{
|
||||||
// Pressing on an item that has a submenu doesn't hide it :(
|
// Pressing on an item that has a submenu doesn't hide it :(
|
||||||
|
@ -1396,7 +1411,7 @@ void ShowWhoReactedMenu(
|
||||||
context,
|
context,
|
||||||
st::defaultWhoRead
|
st::defaultWhoRead
|
||||||
) | rpl::filter([=](const Ui::WhoReadContent &content) {
|
) | rpl::filter([=](const Ui::WhoReadContent &content) {
|
||||||
return !content.unknown;
|
return content.state != Ui::WhoReadState::Unknown;
|
||||||
}) | rpl::start_with_next([=, &lifetime](Ui::WhoReadContent &&content) {
|
}) | rpl::start_with_next([=, &lifetime](Ui::WhoReadContent &&content) {
|
||||||
const auto creating = !*menu;
|
const auto creating = !*menu;
|
||||||
const auto refillTop = [=] {
|
const auto refillTop = [=] {
|
||||||
|
|
|
@ -178,7 +178,7 @@ Action::Action(
|
||||||
) | rpl::start_with_next([=](WhoReadContent &&content) {
|
) | rpl::start_with_next([=](WhoReadContent &&content) {
|
||||||
checkAppeared();
|
checkAppeared();
|
||||||
const auto changed = (_content.participants != content.participants)
|
const auto changed = (_content.participants != content.participants)
|
||||||
|| (_content.unknown != content.unknown);
|
|| (_content.state != content.state);
|
||||||
_content = content;
|
_content = content;
|
||||||
if (changed) {
|
if (changed) {
|
||||||
PostponeCall(this, [=] { populateSubmenu(); });
|
PostponeCall(this, [=] { populateSubmenu(); });
|
||||||
|
@ -219,6 +219,10 @@ Action::Action(
|
||||||
if (const auto onstack = _showAllChosen) {
|
if (const auto onstack = _showAllChosen) {
|
||||||
onstack();
|
onstack();
|
||||||
}
|
}
|
||||||
|
} else if (_content.state == WhoReadState::MyHidden) {
|
||||||
|
if (const auto onstack = _participantChosen) {
|
||||||
|
onstack(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
|
@ -379,8 +383,13 @@ void Action::refreshText() {
|
||||||
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
const auto count = std::max(_content.fullReactionsCount, usersCount);
|
||||||
_text.setMarkedText(
|
_text.setMarkedText(
|
||||||
_st.itemStyle,
|
_st.itemStyle,
|
||||||
{ (_content.unknown
|
{ ((_content.state == WhoReadState::Unknown)
|
||||||
? tr::lng_context_seen_loading(tr::now)
|
? tr::lng_context_seen_loading(tr::now)
|
||||||
|
: (_content.state == WhoReadState::MyHidden)
|
||||||
|
? tr::lng_context_read_show(tr::now)
|
||||||
|
: (_content.state == WhoReadState::HisHidden
|
||||||
|
|| _content.state == WhoReadState::TooOld)
|
||||||
|
? tr::lng_context_read_hidden(tr::now)
|
||||||
: (usersCount == 1)
|
: (usersCount == 1)
|
||||||
? _content.participants.front().name
|
? _content.participants.front().name
|
||||||
: (_content.fullReactionsCount > 0
|
: (_content.fullReactionsCount > 0
|
||||||
|
@ -431,7 +440,8 @@ void Action::refreshDimensions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Action::isEnabled() const {
|
bool Action::isEnabled() const {
|
||||||
return !_content.participants.empty();
|
return !_content.participants.empty()
|
||||||
|
|| (_content.state == WhoReadState::MyHidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<QAction*> Action::action() const {
|
not_null<QAction*> Action::action() const {
|
||||||
|
|
|
@ -38,13 +38,21 @@ enum class WhoReadType {
|
||||||
Reacted,
|
Reacted,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class WhoReadState : uchar {
|
||||||
|
Empty,
|
||||||
|
Unknown,
|
||||||
|
MyHidden,
|
||||||
|
HisHidden,
|
||||||
|
TooOld,
|
||||||
|
};
|
||||||
|
|
||||||
struct WhoReadContent {
|
struct WhoReadContent {
|
||||||
std::vector<WhoReadParticipant> participants;
|
std::vector<WhoReadParticipant> participants;
|
||||||
WhoReadType type = WhoReadType::Seen;
|
WhoReadType type = WhoReadType::Seen;
|
||||||
QString singleCustomEntityData;
|
QString singleCustomEntityData;
|
||||||
int fullReactionsCount = 0;
|
int fullReactionsCount = 0;
|
||||||
int fullReadCount = 0;
|
int fullReadCount = 0;
|
||||||
bool unknown = false;
|
WhoReadState state = WhoReadState::Empty;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
[[nodiscard]] base::unique_qptr<Menu::ItemBase> WhoReactedContextAction(
|
||||||
|
|
Loading…
Add table
Reference in a new issue