Handle links to replies section.

This commit is contained in:
John Preston 2020-09-22 18:05:07 +03:00
parent 247b1f64ca
commit 889139f31f
11 changed files with 308 additions and 259 deletions

View File

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "window/window_session_controller.h"
#include "facades.h"
#include "app.h"
@ -112,7 +113,11 @@ void MentionClickHandler::onClick(ClickContext context) const {
const auto button = context.button;
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
if (const auto m = App::main()) { // multi good
m->openPeerByName(_tag.mid(1), ShowAtProfileMsgId);
using Info = Window::SessionNavigation::PeerByLinkInfo;
m->controller()->showPeerByLink(Info{
.usernameOrId = _tag.mid(1),
.messageId = ShowAtProfileMsgId
});
}
}
}

View File

@ -273,25 +273,30 @@ bool ResolveUsername(
}
const auto commentParam = params.value(qsl("comment"));
const auto commentId = commentParam.toInt();
const auto threadParam = params.value(qsl("thread"));
const auto threadId = threadParam.toInt();
const auto gameParam = params.value(qsl("game"));
if (!gameParam.isEmpty() && valid(gameParam)) {
startToken = gameParam;
post = ShowAtGameShareMsgId;
}
const auto clickFromMessageId = context.value<FullMsgId>();
if (commentId) {
controller->content()->openCommentByName(
domain,
post,
commentId,
clickFromMessageId);
} else {
controller->content()->openPeerByName(
domain,
post,
startToken,
clickFromMessageId);
}
using Navigation = Window::SessionNavigation;
controller->showPeerByLink(Navigation::PeerByLinkInfo{
.usernameOrId = domain,
.messageId = post,
.repliesInfo = commentId
? Navigation::RepliesByLinkInfo{
Navigation::CommentId{ commentId }
}
: threadId
? Navigation::RepliesByLinkInfo{
Navigation::ThreadId{ threadId }
}
: Navigation::RepliesByLinkInfo{ v::null },
.startToken = startToken,
.clickFromMessageId = clickFromMessageId,
});
return true;
}
@ -307,46 +312,29 @@ bool ResolvePrivatePost(
qthelp::UrlParamNameTransform::ToLower);
const auto channelId = params.value(qsl("channel")).toInt();
const auto msgId = params.value(qsl("post")).toInt();
const auto commentParam = params.value(qsl("comment"));
const auto commentId = commentParam.toInt();
const auto threadParam = params.value(qsl("thread"));
const auto threadId = threadParam.toInt();
if (!channelId || !IsServerMsgId(msgId)) {
return false;
}
const auto clickFromMessageId = context.value<FullMsgId>();
const auto done = crl::guard(controller, [=](not_null<PeerData*> peer) {
auto params = Window::SectionShow{
Window::SectionShow::Way::Forward
};
params.origin = Window::SectionShow::OriginMessage{
clickFromMessageId
};
controller->showPeerHistory(
peer->id,
Window::SectionShow::Way::Forward,
msgId);
});
const auto fail = [=] {
Ui::show(Box<InformBox>(tr::lng_error_post_link_invalid(tr::now)));
};
const auto session = &controller->session();
if (const auto channel = session->data().channelLoaded(channelId)) {
done(channel);
return true;
}
session->api().request(MTPchannels_GetChannels(
MTP_vector<MTPInputChannel>(
1,
MTP_inputChannel(MTP_int(channelId), MTP_long(0)))
)).done([=](const MTPmessages_Chats &result) {
result.match([&](const auto &data) {
const auto peer = session->data().processChats(data.vchats());
if (peer && peer->id == peerFromChannel(channelId)) {
done(peer);
} else {
fail();
using Navigation = Window::SessionNavigation;
controller->showPeerByLink(Navigation::PeerByLinkInfo{
.usernameOrId = channelId,
.messageId = msgId,
.repliesInfo = commentId
? Navigation::RepliesByLinkInfo{
Navigation::CommentId{ commentId }
}
});
}).fail([=](const RPCError &error) {
fail();
}).send();
: threadId
? Navigation::RepliesByLinkInfo{
Navigation::ThreadId{ threadId }
}
: Navigation::RepliesByLinkInfo{ v::null },
.clickFromMessageId = clickFromMessageId,
});
return true;
}

View File

@ -67,27 +67,28 @@ struct PeerUpdate {
OnlineStatus = (1 << 12),
BotCommands = (1 << 13),
BotCanBeInvited = (1 << 14),
CommonChats = (1 << 15),
HasCalls = (1 << 16),
SupportInfo = (1 << 17),
IsBot = (1 << 18),
BotStartToken = (1 << 15),
CommonChats = (1 << 16),
HasCalls = (1 << 17),
SupportInfo = (1 << 18),
IsBot = (1 << 19),
// For chats and channels
InviteLink = (1 << 19),
Members = (1 << 20),
Admins = (1 << 21),
BannedUsers = (1 << 22),
Rights = (1 << 23),
InviteLink = (1 << 20),
Members = (1 << 21),
Admins = (1 << 22),
BannedUsers = (1 << 23),
Rights = (1 << 24),
// For channels
ChannelAmIn = (1 << 24),
StickersSet = (1 << 25),
ChannelLinkedChat = (1 << 26),
ChannelLocation = (1 << 27),
Slowmode = (1 << 28),
ChannelAmIn = (1 << 25),
StickersSet = (1 << 26),
ChannelLinkedChat = (1 << 27),
ChannelLocation = (1 << 28),
Slowmode = (1 << 29),
// For iteration
LastUsedBit = (1 << 28),
LastUsedBit = (1 << 29),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@ -570,6 +570,7 @@ HistoryWidget::HistoryWidget(
| UpdateFlag::ChannelAmIn
| UpdateFlag::ChannelLinkedChat
| UpdateFlag::Slowmode
| UpdateFlag::BotStartToken
) | rpl::filter([=](const Data::PeerUpdate &update) {
return (update.peer.get() == _peer);
}) | rpl::map([](const Data::PeerUpdate &update) {
@ -594,6 +595,10 @@ HistoryWidget::HistoryWidget(
return;
}
}
if (flags & UpdateFlag::BotStartToken) {
updateControlsVisibility();
updateControlsGeometry();
}
if (flags & UpdateFlag::PinnedMessage) {
if (pinnedMsgVisibilityUpdated()) {
updateHistoryGeometry();

View File

@ -1210,7 +1210,8 @@ bool RepliesWidget::showInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) {
if (auto logMemento = dynamic_cast<RepliesMemento*>(memento.get())) {
if (logMemento->getHistory() == history()) {
if (logMemento->getHistory() == history()
&& logMemento->getRootId() == _rootId) {
restoreState(logMemento);
return true;
}

View File

@ -133,6 +133,13 @@ void AbstractController::showBackFromStack(
return parentController()->showBackFromStack(params);
}
void AbstractController::showPeerHistory(
PeerId peerId,
const Window::SectionShow &params,
MsgId msgId) {
return parentController()->showPeerHistory(peerId, params, msgId);
}
Controller::Controller(
not_null<WrapWidget*> widget,
not_null<Window::SessionController*> window,

View File

@ -140,6 +140,12 @@ public:
const Window::SectionShow &params = Window::SectionShow()) override;
void showBackFromStack(
const Window::SectionShow &params = Window::SectionShow()) override;
void showPeerHistory(
PeerId peerId,
const Window::SectionShow &params = Window::SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId) override;
not_null<Window::SessionController*> parentController() override {
return _parent;
}

View File

@ -2597,136 +2597,12 @@ void MainWidget::searchInChat(Dialogs::Key chat) {
}
}
void MainWidget::openPeerResolved(
not_null<PeerData*> peer,
MsgId msgId,
const QString &startToken,
FullMsgId clickFromMessageId) {
if (msgId == ShowAtGameShareMsgId) {
if (peer->isUser() && peer->asUser()->isBot() && !startToken.isEmpty()) {
peer->asUser()->botInfo->shareGameShortName = startToken;
AddBotToGroupBoxController::Start(
_controller,
peer->asUser());
} else {
InvokeQueued(this, [this, peer] {
_controller->showPeerHistory(
peer->id,
SectionShow::Way::Forward);
});
}
} else if (msgId == ShowAtProfileMsgId && !peer->isChannel()) {
if (peer->isUser() && peer->asUser()->isBot() && !peer->asUser()->botInfo->cantJoinGroups && !startToken.isEmpty()) {
peer->asUser()->botInfo->startGroupToken = startToken;
AddBotToGroupBoxController::Start(
_controller,
peer->asUser());
} else if (peer->isUser() && peer->asUser()->isBot()) {
// Always open bot chats, even from mention links.
InvokeQueued(this, [this, peer] {
_controller->showPeerHistory(
peer->id,
SectionShow::Way::Forward);
});
} else {
_controller->showPeerInfo(peer);
}
} else {
if (msgId == ShowAtProfileMsgId || !peer->isChannel()) { // show specific posts only in channels / supergroups
msgId = ShowAtUnreadMsgId;
}
if (peer->isUser() && peer->asUser()->isBot()) {
peer->asUser()->botInfo->startToken = startToken;
if (peer == _history->peer()) {
_history->updateControlsVisibility();
_history->updateControlsGeometry();
}
}
InvokeQueued(this, [=] {
auto params = SectionShow{
SectionShow::Way::Forward
};
params.origin = SectionShow::OriginMessage{
clickFromMessageId
};
_controller->showPeerHistory(peer->id, params, msgId);
});
}
}
void MainWidget::openPeerByName(
const QString &username,
MsgId msgId,
const QString &startToken,
FullMsgId clickFromMessageId) {
Core::App().hideMediaView();
resolveUsername(username, [=](not_null<PeerData*> peer) {
openPeerResolved(peer, msgId, startToken, clickFromMessageId);
});
}
void MainWidget::openCommentByName(
const QString &username,
MsgId msgId,
MsgId commentId,
FullMsgId clickFromMessageId) {
Core::App().hideMediaView();
resolveUsername(username, [=](not_null<PeerData*> peer) {
InvokeQueued(this, [=] {
auto params = SectionShow{
SectionShow::Way::Forward
};
params.origin = SectionShow::OriginMessage{
clickFromMessageId
};
_controller->showRepliesForMessage(
session().data().history(peer),
msgId,
commentId,
params);
});
});
Core::App().hideMediaView();
}
bool MainWidget::contentOverlapped(const QRect &globalRect) {
return (_history->contentOverlapped(globalRect)
|| _playerPlaylist->overlaps(globalRect)
|| (_playerVolume && _playerVolume->overlaps(globalRect)));
}
void MainWidget::resolveUsername(
const QString &username,
Fn<void(not_null<PeerData*>)> done) {
if (const auto peer = session().data().peerByUsername(username)) {
done(peer);
return;
}
_api.request(base::take(_resolveRequestId)).cancel();
_resolveRequestId = _api.request(MTPcontacts_ResolveUsername(
MTP_string(username)
)).done([=](const MTPcontacts_ResolvedPeer &result) {
_resolveRequestId = 0;
Ui::hideLayer();
if (result.type() != mtpc_contacts_resolvedPeer) {
return;
}
const auto &d(result.c_contacts_resolvedPeer());
session().data().processUsers(d.vusers());
session().data().processChats(d.vchats());
if (const auto peerId = peerFromMTP(d.vpeer())) {
done(session().data().peer(peerId));
}
}).fail([=](const RPCError &error) {
_resolveRequestId = 0;
if (error.code() == 400) {
Ui::show(Box<InformBox>(
tr::lng_username_not_found(tr::now, lt_user, username)));
}
}).send();
}
void MainWidget::activate() {
if (_a_show.animating()) {
return;

View File

@ -122,17 +122,6 @@ public:
void showAnimated(const QPixmap &bgAnimCache, bool back = false);
void openPeerByName(
const QString &name,
MsgId msgId = ShowAtUnreadMsgId,
const QString &startToken = QString(),
FullMsgId clickFromMessageId = FullMsgId());
void openCommentByName(
const QString &name,
MsgId msgId,
MsgId commentId,
FullMsgId clickFromMessageId = FullMsgId());
void activate();
void windowShown();
@ -294,15 +283,6 @@ private:
void saveSectionInStack();
void resolveUsername(
const QString &username,
Fn<void(not_null<PeerData*>)> done);
void openPeerResolved(
not_null<PeerData*> peer,
MsgId msgId,
const QString &startToken,
FullMsgId clickFromMessageId);
int getMainSectionTop() const;
int getThirdSectionTop() const;
@ -406,7 +386,6 @@ private:
base::flat_map<not_null<PeerData*>, mtpRequestId> _viewsIncrementRequests;
base::flat_map<mtpRequestId, not_null<PeerData*>> _viewsIncrementByRequest;
base::Timer _viewsIncrementTimer;
mtpRequestId _resolveRequestId = 0;
struct SettingBackground;
std::unique_ptr<SettingBackground> _background;

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_session_controller.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/peer_list_controllers.h"
#include "window/window_controller.h"
#include "window/main_window.h"
#include "window/window_filters_menu.h"
@ -24,6 +25,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_folder.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_changes.h"
#include "data/data_chat_filters.h"
#include "data/data_photo.h" // requestAttachedStickerSets.
#include "passport/passport_form_controller.h"
@ -85,12 +88,155 @@ SessionNavigation::SessionNavigation(not_null<Main::Session*> session)
SessionNavigation::~SessionNavigation() {
_session->api().request(base::take(_showingRepliesRequestId)).cancel();
_session->api().request(base::take(_resolveRequestId)).cancel();
}
Main::Session &SessionNavigation::session() const {
return *_session;
}
void SessionNavigation::showPeerByLink(const PeerByLinkInfo &info) {
Core::App().hideMediaView();
if (const auto username = std::get_if<QString>(&info.usernameOrId)) {
resolveUsername(*username, [=](not_null<PeerData*> peer) {
showPeerByLinkResolved(peer, info);
});
} else if (const auto id = std::get_if<ChannelId>(&info.usernameOrId)) {
resolveChannelById(*id, [=](not_null<ChannelData*> channel) {
showPeerByLinkResolved(channel, info);
});
}
}
void SessionNavigation::resolveUsername(
const QString &username,
Fn<void(not_null<PeerData*>)> done) {
if (const auto peer = _session->data().peerByUsername(username)) {
done(peer);
return;
}
_session->api().request(base::take(_resolveRequestId)).cancel();
_resolveRequestId = _session->api().request(MTPcontacts_ResolveUsername(
MTP_string(username)
)).done([=](const MTPcontacts_ResolvedPeer &result) {
_resolveRequestId = 0;
Ui::hideLayer();
if (result.type() != mtpc_contacts_resolvedPeer) {
return;
}
const auto &d(result.c_contacts_resolvedPeer());
_session->data().processUsers(d.vusers());
_session->data().processChats(d.vchats());
if (const auto peerId = peerFromMTP(d.vpeer())) {
done(_session->data().peer(peerId));
}
}).fail([=](const RPCError &error) {
_resolveRequestId = 0;
if (error.code() == 400) {
Ui::show(Box<InformBox>(
tr::lng_username_not_found(tr::now, lt_user, username)));
}
}).send();
}
void SessionNavigation::resolveChannelById(
ChannelId channelId,
Fn<void(not_null<ChannelData*>)> done) {
if (const auto channel = _session->data().channelLoaded(channelId)) {
done(channel);
return;
}
const auto fail = [=] {
Ui::show(Box<InformBox>(tr::lng_error_post_link_invalid(tr::now)));
};
_session->api().request(base::take(_resolveRequestId)).cancel();
_resolveRequestId = _session->api().request(MTPchannels_GetChannels(
MTP_vector<MTPInputChannel>(
1,
MTP_inputChannel(MTP_int(channelId), MTP_long(0)))
)).done([=](const MTPmessages_Chats &result) {
result.match([&](const auto &data) {
const auto peer = _session->data().processChats(data.vchats());
if (peer && peer->id == peerFromChannel(channelId)) {
done(peer->asChannel());
} else {
fail();
}
});
}).fail([=](const RPCError &error) {
fail();
}).send();
}
void SessionNavigation::showPeerByLinkResolved(
not_null<PeerData*> peer,
const PeerByLinkInfo &info) {
auto params = SectionShow{
SectionShow::Way::Forward
};
params.origin = SectionShow::OriginMessage{
info.clickFromMessageId
};
const auto &replies = info.repliesInfo;
if (const auto threadId = std::get_if<ThreadId>(&replies)) {
showRepliesForMessage(
session().data().history(peer),
threadId->id,
info.messageId,
params);
} else if (const auto commentId = std::get_if<CommentId>(&replies)) {
showRepliesForMessage(
session().data().history(peer),
info.messageId,
commentId->id,
params);
} else if (info.messageId == ShowAtGameShareMsgId) {
const auto user = peer->asUser();
if (user && user->isBot() && !info.startToken.isEmpty()) {
user->botInfo->shareGameShortName = info.startToken;
AddBotToGroupBoxController::Start(this, user);
} else {
crl::on_main(this, [=] {
showPeerHistory(peer->id, params);
});
}
} else if (info.messageId == ShowAtProfileMsgId && !peer->isChannel()) {
const auto user = peer->asUser();
if (user
&& user->isBot()
&& !user->botInfo->cantJoinGroups
&& !info.startToken.isEmpty()) {
user->botInfo->startGroupToken = info.startToken;
AddBotToGroupBoxController::Start(this, user);
} else if (user && user->isBot()) {
// Always open bot chats, even from mention links.
crl::on_main(this, [=] {
showPeerHistory(peer->id, params);
});
} else {
showPeerInfo(peer, params);
}
} else {
const auto user = peer->asUser();
auto msgId = info.messageId;
if (msgId == ShowAtProfileMsgId || !peer->isChannel()) {
// Show specific posts only in channels / supergroups.
msgId = ShowAtUnreadMsgId;
}
if (user && user->isBot()) {
user->botInfo->startToken = info.startToken;
user->session().changes().peerUpdated(
user,
Data::PeerUpdate::Flag::BotStartToken);
}
crl::on_main(this, [=] {
showPeerHistory(peer->id, params, msgId);
});
}
}
void SessionNavigation::showRepliesForMessage(
not_null<History*> history,
MsgId rootId,
@ -104,17 +250,17 @@ void SessionNavigation::showRepliesForMessage(
_session->api().request(base::take(_showingRepliesRequestId)).cancel();
const auto channelId = history->channelId();
const auto item = _session->data().message(channelId, rootId);
if (!commentId && (!item || !item->repliesAreComments())) {
showSection(HistoryView::RepliesMemento(history, rootId));
return;
} else if (const auto id = item ? item->commentsItemId() : FullMsgId()) {
if (const auto commentsItem = _session->data().message(id)) {
showSection(
HistoryView::RepliesMemento(commentsItem));
return;
}
}
//const auto item = _session->data().message(channelId, rootId);
//if (!commentId && (!item || !item->repliesAreComments())) {
// showSection(HistoryView::RepliesMemento(history, rootId));
// return;
//} else if (const auto id = item ? item->commentsItemId() : FullMsgId()) {
// if (const auto commentsItem = _session->data().message(id)) {
// showSection(
// HistoryView::RepliesMemento(commentsItem));
// return;
// }
//}
_showingRepliesHistory = history;
_showingRepliesRootId = rootId;
_showingRepliesRequestId = _session->api().request(
@ -155,7 +301,7 @@ void SessionNavigation::showRepliesForMessage(
item->setRepliesOutboxReadTill(readTill->v);
}
const auto post = _session->data().message(channelId, rootId);
if (post) {
if (post && item->history()->channelId() != channelId) {
post->setCommentsItemId(item->fullId());
if (const auto maxId = data.vmax_id()) {
post->setRepliesMaxId(maxId->v);
@ -199,6 +345,26 @@ void SessionNavigation::showPeerInfo(
showPeerInfo(history->peer->id, params);
}
void SessionNavigation::showPeerHistory(
not_null<PeerData*> peer,
const SectionShow &params,
MsgId msgId) {
showPeerHistory(
peer->id,
params,
msgId);
}
void SessionNavigation::showPeerHistory(
not_null<History*> history,
const SectionShow &params,
MsgId msgId) {
showPeerHistory(
history->peer->id,
params,
msgId);
}
void SessionNavigation::showSettings(
Settings::Type type,
const SectionShow &params) {
@ -905,26 +1071,6 @@ void SessionController::showPeerHistory(
msgId);
}
void SessionController::showPeerHistory(
not_null<PeerData*> peer,
const SectionShow &params,
MsgId msgId) {
showPeerHistory(
peer->id,
params,
msgId);
}
void SessionController::showPeerHistory(
not_null<History*> history,
const SectionShow &params,
MsgId msgId) {
showPeerHistory(
history->peer->id,
params,
msgId);
}
void SessionController::showSection(
SectionMemento &&memento,
const SectionShow &params) {

View File

@ -138,6 +138,22 @@ public:
const SectionShow &params = SectionShow()) = 0;
virtual not_null<SessionController*> parentController() = 0;
struct CommentId {
MsgId id = 0;
};
struct ThreadId {
MsgId id = 0;
};
using RepliesByLinkInfo = std::variant<v::null_t, CommentId, ThreadId>;
struct PeerByLinkInfo {
std::variant<QString, ChannelId> usernameOrId;
MsgId messageId = ShowAtUnreadMsgId;
RepliesByLinkInfo repliesInfo;
QString startToken;
FullMsgId clickFromMessageId;
};
void showPeerByLink(const PeerByLinkInfo &info);
void showRepliesForMessage(
not_null<History*> history,
MsgId rootId,
@ -154,6 +170,27 @@ public:
not_null<History*> history,
const SectionShow &params = SectionShow());
virtual void showPeerHistory(
PeerId peerId,
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId) = 0;
void showPeerHistory(
not_null<PeerData*> peer,
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId);
void showPeerHistory(
not_null<History*> history,
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId);
void clearSectionStack(
const SectionShow &params = SectionShow::Way::ClearStack) {
showPeerHistory(
PeerId(0),
params,
ShowAtUnreadMsgId);
}
void showSettings(
Settings::Type type,
const SectionShow &params = SectionShow());
@ -166,8 +203,21 @@ public:
private:
void resolveUsername(
const QString &username,
Fn<void(not_null<PeerData*>)> done);
void resolveChannelById(
ChannelId channelId,
Fn<void(not_null<ChannelData*>)> done);
void showPeerByLinkResolved(
not_null<PeerData*> peer,
const PeerByLinkInfo &info);
const not_null<Main::Session*> _session;
mtpRequestId _resolveRequestId = 0;
History *_showingRepliesHistory = nullptr;
MsgId _showingRepliesRootId = 0;
mtpRequestId _showingRepliesRequestId = 0;
@ -251,26 +301,11 @@ public:
void showBackFromStack(
const SectionShow &params = SectionShow()) override;
using SessionNavigation::showPeerHistory;
void showPeerHistory(
PeerId peerId,
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId);
void showPeerHistory(
not_null<PeerData*> peer,
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId);
void showPeerHistory(
not_null<History*> history,
const SectionShow &params = SectionShow::Way::ClearStack,
MsgId msgId = ShowAtUnreadMsgId);
void clearSectionStack(
const SectionShow &params = SectionShow::Way::ClearStack) {
showPeerHistory(
PeerId(0),
params,
ShowAtUnreadMsgId);
}
MsgId msgId = ShowAtUnreadMsgId) override;
void showSpecialLayer(
object_ptr<Ui::LayerWidget> &&layer,