Support default General topic in forums.

This commit is contained in:
John Preston 2022-09-26 17:37:32 +04:00
parent 2201159da5
commit 73e56b0340
23 changed files with 385 additions and 183 deletions

View File

@ -248,6 +248,7 @@ public:
not_null<Window::SessionNavigation*> navigation,
not_null<Ui::BoxContent*> box,
not_null<PeerData*> peer);
~Controller();
[[nodiscard]] object_ptr<Ui::VerticalLayout> createContent();
void setFocus();
@ -388,6 +389,8 @@ Controller::Controller(
_peer->updateFull();
}
Controller::~Controller() = default;
void Controller::subscribeToMigration() {
SubscribeToMigration(
_peer,
@ -815,7 +818,7 @@ void Controller::fillForumButton() {
AddButtonWithText(
_controls.buttonsLayout,
rpl::single(u"Forum"_q), // #TODO forum
rpl::single(u"Forum"_q), // #TODO forum lang
rpl::single(QString()),
[] {},
{ &st::settingsIconGroup, Settings::kIconPurple }

View File

@ -60,13 +60,9 @@ Data::ChatBotCommands::Changed MegagroupInfo::setBotCommands(
return _botCommands.update(list);
}
void MegagroupInfo::setIsForum(not_null<ChannelData*> that, bool is) {
if (is == (_forum != nullptr)) {
return;
} else if (is) {
void MegagroupInfo::ensureForum(not_null<ChannelData*> that) {
if (!_forum) {
_forum = std::make_unique<Data::Forum>(that->owner().history(that));
} else {
_forum = nullptr;
}
}
@ -74,35 +70,15 @@ Data::Forum *MegagroupInfo::forum() const {
return _forum.get();
}
std::unique_ptr<Data::Forum> MegagroupInfo::takeForumData() {
return std::move(_forum);
}
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
: PeerData(owner, id)
, inputChannel(
MTP_inputChannel(MTP_long(peerToChannel(id).bare), MTP_long(0)))
, _ptsWaiter(&owner->session().updates()) {
_flags.changes(
) | rpl::start_with_next([=](const Flags::Change &change) {
if (change.diff
& (Flag::Left | Flag::Forbidden)) {
if (const auto chat = getMigrateFromChat()) {
session().changes().peerUpdated(chat, UpdateFlag::Migration);
session().changes().peerUpdated(this, UpdateFlag::Migration);
}
}
if (change.diff & Flag::Megagroup) {
if (change.value & Flag::Megagroup) {
if (!mgInfo) {
mgInfo = std::make_unique<MegagroupInfo>();
}
} else if (mgInfo) {
mgInfo = nullptr;
}
}
if (change.diff & Flag::CallNotEmpty) {
if (const auto history = this->owner().historyLoaded(this)) {
history->updateChatListEntry();
}
}
}, _lifetime);
}
void ChannelData::setPhoto(const MTPChatPhoto &photo) {
@ -132,6 +108,44 @@ void ChannelData::setAccessHash(uint64 accessHash) {
MTP_long(accessHash));
}
void ChannelData::setFlags(ChannelDataFlags which) {
const auto diff = flags() ^ which;
if ((which & Flag::Megagroup) && !mgInfo) {
mgInfo = std::make_unique<MegagroupInfo>();
}
// Let Data::Forum live till the end of _flags.set.
// That way the data can be used in changes handler.
// Example: render frame for forum auto-closing animation.
const auto taken = ((diff & Flag::Forum) && !(which & Flag::Forum))
? mgInfo->takeForumData()
: nullptr;
if ((diff & Flag::Forum) && (which & Flag::Forum)) {
mgInfo->ensureForum(this);
}
_flags.set(which);
if (diff & (Flag::Left | Flag::Forbidden)) {
if (const auto chat = getMigrateFromChat()) {
session().changes().peerUpdated(chat, UpdateFlag::Migration);
session().changes().peerUpdated(this, UpdateFlag::Migration);
}
}
if (diff & Flag::CallNotEmpty) {
if (const auto history = this->owner().historyLoaded(this)) {
history->updateChatListEntry();
}
}
}
void ChannelData::addFlags(ChannelDataFlags which) {
setFlags(flags() | which);
}
void ChannelData::removeFlags(ChannelDataFlags which) {
setFlags(flags() & ~which);
}
void ChannelData::setInviteLink(const QString &newInviteLink) {
_inviteLink = newInviteLink;
}

View File

@ -96,8 +96,9 @@ public:
return _botCommands;
}
void setIsForum(not_null<ChannelData*> that, bool is);
void ensureForum(not_null<ChannelData*> that);
[[nodiscard]] Data::Forum *forum() const;
[[nodiscard]] std::unique_ptr<Data::Forum> takeForumData();
std::deque<not_null<UserData*>> lastParticipants;
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
@ -148,15 +149,9 @@ public:
void setPhoto(const MTPChatPhoto &photo);
void setAccessHash(uint64 accessHash);
void setFlags(ChannelDataFlags which) {
_flags.set(which);
}
void addFlags(ChannelDataFlags which) {
_flags.add(which);
}
void removeFlags(ChannelDataFlags which) {
_flags.remove(which);
}
void setFlags(ChannelDataFlags which);
void addFlags(ChannelDataFlags which);
void removeFlags(ChannelDataFlags which);
[[nodiscard]] auto flags() const {
return _flags.current();
}
@ -490,8 +485,6 @@ private:
int _slowmodeSeconds = 0;
TimeId _slowmodeLastMessage = 0;
rpl::lifetime _lifetime;
};
namespace Data {

View File

@ -29,12 +29,25 @@ constexpr auto kTopicsPerPage = 500;
} // namespace
Forum::Forum(not_null<History*> forum)
: _forum(forum)
, _topicsList(&forum->session(), FilterId(0), rpl::single(1)) {
Forum::Forum(not_null<History*> history)
: _history(history)
, _topicsList(&_history->session(), FilterId(0), rpl::single(1)) {
Expects(_history->peer->isChannel());
}
Forum::~Forum() = default;
Forum::~Forum() {
if (_requestId) {
_history->session().api().request(_requestId).cancel();
}
}
not_null<History*> Forum::history() const {
return _history;
}
not_null<ChannelData*> Forum::channel() const {
return _history->peer->asChannel();
}
not_null<Dialogs::MainList*> Forum::topicsList() {
return &_topicsList;
@ -44,28 +57,24 @@ void Forum::requestTopics() {
if (_allLoaded || _requestId) {
return;
}
const auto forum = _forum;
const auto firstLoad = !_offsetDate;
const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage;
const auto api = &forum->session().api();
const auto api = &_history->session().api();
_requestId = api->request(MTPchannels_GetForumTopics(
MTP_flags(0),
forum->peer->asChannel()->inputChannel,
channel()->inputChannel,
MTPstring(), // q
MTP_int(_offsetDate),
MTP_int(_offsetId),
MTP_int(_offsetTopicId),
MTP_int(loadCount)
)).done([=](const MTPmessages_ForumTopics &result) {
if (!forum->peer->isForum()) {
return;
}
const auto &data = result.data();
const auto owner = &forum->owner();
const auto owner = &channel()->owner();
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
owner->processMessages(data.vmessages(), NewMessageType::Existing);
forum->peer->asChannel()->ptsReceived(data.vpts().v);
channel()->ptsReceived(data.vpts().v);
const auto &list = data.vtopics().v;
for (const auto &topic : list) {
const auto rootId = MsgId(topic.data().vid().v);
@ -74,7 +83,7 @@ void Forum::requestTopics() {
const auto raw = creating
? _topics.emplace(
rootId,
std::make_unique<ForumTopic>(forum, rootId)
std::make_unique<ForumTopic>(_history, rootId)
).first->second.get()
: i->second.get();
raw->applyTopic(topic);
@ -107,7 +116,7 @@ void Forum::applyTopicAdded(MsgId rootId, const QString &title) {
} else {
const auto raw = _topics.emplace(
rootId,
std::make_unique<ForumTopic>(_forum, rootId)
std::make_unique<ForumTopic>(_history, rootId)
).first->second.get();
raw->applyTitle(title);
raw->addToChatList(FilterId(), topicsList());
@ -122,10 +131,18 @@ void Forum::applyTopicRemoved(MsgId rootId) {
}
ForumTopic *Forum::topicFor(not_null<HistoryItem*> item) {
if (const auto rootId = item->replyToTop()) {
return topicFor(item->topicRootId());
}
ForumTopic *Forum::topicFor(MsgId rootId) {
if (rootId != ForumTopic::kGeneralId) {
if (const auto i = _topics.find(rootId); i != end(_topics)) {
return i->second.get();
}
} else {
// #TODO forum lang
applyTopicAdded(rootId, "General! Created.");
return _topics.find(rootId)->second.get();
}
return nullptr;
}
@ -148,13 +165,13 @@ void ShowAddForumTopic(
object_ptr<Ui::InputField>(
box,
st::defaultInputField,
rpl::single(u"Topic Title"_q))); // #TODO forum
rpl::single(u"Topic Title"_q))); // #TODO forum lang
const auto message = box->addRow(
object_ptr<Ui::InputField>(
box,
st::newGroupDescription,
Ui::InputField::Mode::MultiLine,
rpl::single(u"Message"_q))); // #TODO forum
rpl::single(u"Message"_q))); // #TODO forum lang
box->setFocusCallback([=] {
title->setFocusFast();
});

View File

@ -20,9 +20,11 @@ namespace Data {
class Forum final {
public:
explicit Forum(not_null<History*> forum);
explicit Forum(not_null<History*> history);
~Forum();
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] not_null<ChannelData*> channel() const;
[[nodiscard]] not_null<Dialogs::MainList*> topicsList();
void requestTopics();
@ -32,9 +34,10 @@ public:
void applyTopicAdded(MsgId rootId, const QString &title);
void applyTopicRemoved(MsgId rootId);
[[nodiscard]] ForumTopic *topicFor(not_null<HistoryItem*> item);
[[nodiscard]] ForumTopic *topicFor(MsgId rootId);
private:
const not_null<History*> _forum;
const not_null<History*> _history;
base::flat_map<MsgId, std::unique_ptr<ForumTopic>> _topics;
Dialogs::MainList _topicsList;

View File

@ -198,6 +198,9 @@ void ForumTopic::requestChatListMessage() {
}
TimeId ForumTopic::adjustedChatListTimeId() const {
if (isGeneral()) {
return TimeId(1);
}
const auto result = chatListTimeId();
#if 0 // #TODO forum
if (const auto draft = cloudDraft()) {
@ -236,10 +239,10 @@ bool ForumTopic::lastServerMessageKnown() const {
}
void ForumTopic::applyTitle(const QString &title) {
if (_title == title) {
if (_title == title || (isGeneral() && !_title.isEmpty())) {
return;
}
_title = title;
_title = isGeneral() ? "General! Topic." : title; // #TODO forum lang
++_titleVersion;
indexTitleParts();
updateChatListEntry();

View File

@ -26,6 +26,8 @@ class Session;
class ForumTopic final : public Dialogs::Entry {
public:
static constexpr auto kGeneralId = 1;
ForumTopic(not_null<History*> forum, MsgId rootId);
ForumTopic(const ForumTopic &) = delete;
@ -33,6 +35,9 @@ public:
[[nodiscard]] not_null<History*> forum() const;
[[nodiscard]] MsgId rootId() const;
[[nodiscard]] bool isGeneral() const {
return (_rootId == kGeneralId);
}
void applyTopic(const MTPForumTopic &topic);
@ -88,7 +93,6 @@ private:
int unreadCount,
MsgId maxInboxRead,
MsgId maxOutboxRead);
void applyChatListMessage(HistoryItem *item);
void setLastMessage(HistoryItem *item);
void setLastServerMessage(HistoryItem *item);

View File

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_messages.h"
#include "data/data_forum_topic.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
@ -125,7 +126,10 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) {
}
slice.ids.reserve(messages.size());
for (const auto &item : messages) {
if (item->replyToTop() != _rootId) {
const auto checkId = (_rootId == ForumTopic::kGeneralId)
? item->topicRootId()
: item->replyToTop();
if (!item->inThread(_rootId)) {
continue;
}
slice.ids.push_back(item->fullId());
@ -143,7 +147,7 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) {
dates.push_back(message->date());
}
for (const auto &item : messages) {
if (item->replyToTop() != _rootId) {
if (!item->inThread(_rootId)) {
continue;
}
const auto date = item->date();
@ -341,12 +345,15 @@ bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
auto nearestToAround = std::optional<MsgId>();
slice->ids.reserve(useAfter + useBefore);
for (auto j = i - useAfter, e = i + useBefore; j != e; ++j) {
if (!nearestToAround && *j < around) {
const auto id = *j;
if (id == _rootId) {
continue;
} else if (!nearestToAround && id < around) {
nearestToAround = (j == i - useAfter)
? *j
? id
: *(j - 1);
}
slice->ids.emplace_back(peerId, *j);
slice->ids.emplace_back(peerId, id);
}
slice->nearestToAround = FullMsgId(
peerId,
@ -380,7 +387,7 @@ bool RepliesList::applyUpdate(
}
}
}
if (update.item->replyToTop() != _rootId) {
if (!update.item->inThread(_rootId)) {
return false;
}
const auto id = update.item->id;
@ -613,7 +620,7 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
auto skipped = 0;
for (const auto &message : list) {
if (const auto item = owner.addNewMessage(message, localFlags, type)) {
if (item->replyToTop() == _rootId) {
if (item->inThread(_rootId)) {
if (toFront && item->id > _list.front()) {
refreshed.push_back(item->id);
} else if (_list.empty() || item->id < _list.back()) {

View File

@ -807,12 +807,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
| ((data.is_forum() && data.is_megagroup())
? Flag::Forum
: Flag());
const auto wasForum = channel->isForum();
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
if (const auto nowForum = channel->isForum(); nowForum != wasForum) {
Assert(channel->mgInfo != nullptr);
channel->mgInfo->setIsForum(channel, nowForum);
}
channel->setName(
qs(data.vtitle()),

View File

@ -404,12 +404,13 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
}
void InnerWidget::changeOpenedForum(ChannelData *forum) {
if (_openedForum == forum) {
const auto now = _openedForum ? _openedForum->channel().get() : nullptr;
if (now == forum) {
return;
}
stopReorderPinned();
clearSelection();
_openedForum = forum;
_openedForum = forum ? forum->forum() : nullptr;
_openedForumLifetime.destroy();
if (forum) {
@ -1791,7 +1792,7 @@ not_null<IndexedList*> InnerWidget::shownDialogs() const {
return _filterId
? session().data().chatsFilters().chatsList(_filterId)->indexed()
: _openedForum
? _openedForum->forum()->topicsList()->indexed()
? _openedForum->topicsList()->indexed()
: session().data().chatsList(_openedFolder)->indexed();
}
@ -2296,7 +2297,7 @@ Data::Folder *InnerWidget::shownFolder() const {
return _openedFolder;
}
ChannelData *InnerWidget::shownForum() const {
Data::Forum *InnerWidget::shownForum() const {
return _openedForum;
}
@ -2410,7 +2411,7 @@ void InnerWidget::refreshEmptyLabel() {
} else if (_emptyState == EmptyState::EmptyFolder) {
editOpenedFilter();
} else if (_emptyState == EmptyState::EmptyForum) {
Data::ShowAddForumTopic(_controller, _openedForum);
Data::ShowAddForumTopic(_controller, _openedForum->channel());
}
});
_empty->setVisible(_state == WidgetState::Default);
@ -3286,7 +3287,9 @@ void InnerWidget::setupShortcuts() {
});
request->check(Command::ChatSelf) && request->handle([=] {
if (_openedForum) {
Data::ShowAddForumTopic(_controller, _openedForum);
Data::ShowAddForumTopic(
_controller,
_openedForum->channel());
} else {
_controller->content()->choosePeer(
session().userPeerId(),

View File

@ -36,6 +36,8 @@ class SessionController;
namespace Data {
class CloudImageView;
class Folder;
class Forum;
} // namespace Data
namespace Dialogs::Ui {
@ -110,7 +112,7 @@ public:
void scrollToEntry(const RowDescriptor &entry);
Data::Folder *shownFolder() const;
ChannelData *shownForum() const;
Data::Forum *shownForum() const;
int32 lastSearchDate() const;
PeerData *lastSearchPeer() const;
MsgId lastSearchId() const;
@ -351,7 +353,7 @@ private:
Qt::MouseButton _pressButton = Qt::LeftButton;
Data::Folder *_openedFolder = nullptr;
ChannelData *_openedForum = nullptr;
Data::Forum *_openedForum = nullptr;
rpl::lifetime _openedForumLifetime;
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;

View File

@ -260,50 +260,7 @@ Widget::Widget(
}, lifetime());
_inner->chosenRow(
) | rpl::start_with_next([=](const ChosenRow &row) {
const auto openSearchResult = !controller->selectingPeer()
&& row.filteredRow;
const auto history = row.key.history();
if (const auto topic = row.key.topic()) {
controller->showRepliesForMessage(
topic->forum(),
topic->rootId());
} else if (history && history->peer->isForum()) {
controller->openForum(history->peer->asChannel());
} else if (history) {
const auto peer = history->peer;
const auto showAtMsgId = controller->uniqueChatsInSearchResults()
? ShowAtUnreadMsgId
: row.message.fullId.msg;
if (row.newWindow && controller->canShowSeparateWindow(peer)) {
const auto active = controller->activeChatCurrent();
const auto fromActive = active.history()
? (active.history()->peer == peer)
: false;
const auto toSeparate = [=] {
Core::App().ensureSeparateWindowForPeer(
peer,
showAtMsgId);
};
if (fromActive) {
controller->window().preventOrInvoke([=] {
controller->content()->ui_showPeerHistory(
0,
Window::SectionShow::Way::ClearStack,
0);
toSeparate();
});
} else {
toSeparate();
}
} else {
controller->content()->choosePeer(peer->id, showAtMsgId);
}
} else if (const auto folder = row.key.folder()) {
controller->openFolder(folder);
}
if (openSearchResult && !session().supportMode()) {
escape();
}
chosenRow(row);
}, lifetime());
_scroll->geometryChanged(
@ -422,6 +379,53 @@ Widget::Widget(
setupDownloadBar();
}
void Widget::chosenRow(const ChosenRow &row) {
const auto openSearchResult = !controller()->selectingPeer()
&& row.filteredRow;
const auto history = row.key.history();
if (const auto topic = row.key.topic()) {
controller()->showRepliesForMessage(
topic->forum(),
topic->rootId());
} else if (history && history->peer->isForum()) {
controller()->openForum(history->peer->asChannel());
} else if (history) {
const auto peer = history->peer;
const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
? ShowAtUnreadMsgId
: row.message.fullId.msg;
if (row.newWindow && controller()->canShowSeparateWindow(peer)) {
const auto active = controller()->activeChatCurrent();
const auto fromActive = active.history()
? (active.history()->peer == peer)
: false;
const auto toSeparate = [=] {
Core::App().ensureSeparateWindowForPeer(
peer,
showAtMsgId);
};
if (fromActive) {
controller()->window().preventOrInvoke([=] {
controller()->content()->ui_showPeerHistory(
0,
Window::SectionShow::Way::ClearStack,
0);
toSeparate();
});
} else {
toSeparate();
}
} else {
controller()->content()->choosePeer(peer->id, showAtMsgId);
}
} else if (const auto folder = row.key.folder()) {
controller()->openFolder(folder);
}
if (openSearchResult && !session().supportMode()) {
escape();
}
}
void Widget::setGeometryWithTopMoved(
const QRect &newGeometry,
int topDelta) {

View File

@ -116,6 +116,7 @@ private:
Internal,
};
void chosenRow(const ChosenRow &row);
void listScrollUpdated();
void cancelSearchInChat();
void filterCursorMoved(int from = -1, int to = -1);

View File

@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_messages.h"
#include "data/data_media_types.h"
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
@ -1058,6 +1059,21 @@ MsgId HistoryItem::replyToTop() const {
return 0;
}
MsgId HistoryItem::topicRootId() const {
if (const auto reply = Get<HistoryMessageReply>()
; reply && reply->topicPost) {
return reply->replyToTop();
}
return Data::ForumTopic::kGeneralId;
}
MsgId HistoryItem::inThread(MsgId rootId) const {
const auto checkId = (rootId == Data::ForumTopic::kGeneralId)
? topicRootId()
: replyToTop();
return (checkId == rootId);
}
not_null<PeerData*> HistoryItem::author() const {
return (isPost() && !isSponsored()) ? history()->peer : from();
}

View File

@ -410,6 +410,8 @@ public:
[[nodiscard]] MsgId replyToId() const;
[[nodiscard]] MsgId replyToTop() const;
[[nodiscard]] MsgId topicRootId() const;
[[nodiscard]] MsgId inThread(MsgId rootId) const;
[[nodiscard]] not_null<PeerData*> author() const;

View File

@ -259,7 +259,7 @@ bool HistoryMessageReply::updateData(
} else {
holder->history()->owner().registerDependentMessage(
holder,
replyToMsg);
replyToMsg.get());
}
}
}
@ -298,7 +298,7 @@ bool HistoryMessageReply::updateData(
void HistoryMessageReply::setReplyToLinkFrom(
not_null<HistoryMessage*> holder) {
replyToLnk = replyToMsg
? goToMessageClickHandler(replyToMsg, holder->fullId())
? goToMessageClickHandler(replyToMsg.get(), holder->fullId())
: nullptr;
}
@ -307,7 +307,7 @@ void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
if (replyToMsg) {
holder->history()->owner().unregisterDependentMessage(
holder,
replyToMsg);
replyToMsg.get());
replyToMsg = nullptr;
}
replyToMsgId = 0;
@ -399,7 +399,7 @@ void HistoryMessageReply::resize(int width) const {
void HistoryMessageReply::itemRemoved(
HistoryMessage *holder,
HistoryItem *removed) {
if (replyToMsg == removed) {
if (replyToMsg.get() == removed) {
clearData(holder);
holder->history()->owner().requestItemResize(holder);
}

View File

@ -132,29 +132,55 @@ struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored
bool recommended = false;
};
struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, HistoryItem> {
class ReplyToMessagePointer final {
public:
ReplyToMessagePointer(HistoryItem *item = nullptr) : _data(item) {
}
ReplyToMessagePointer(ReplyToMessagePointer &&other)
: _data(base::take(other._data)) {
}
ReplyToMessagePointer &operator=(ReplyToMessagePointer &&other) {
_data = base::take(other._data);
return *this;
}
ReplyToMessagePointer &operator=(HistoryItem *item) {
_data = item;
return *this;
}
[[nodiscard]] bool empty() const {
return !_data;
}
[[nodiscard]] HistoryItem *get() const {
return _data;
}
explicit operator bool() const {
return !empty();
}
[[nodiscard]] HistoryItem *operator->() const {
return _data;
}
[[nodiscard]] HistoryItem &operator*() const {
return *_data;
}
private:
HistoryItem *_data = nullptr;
};
struct HistoryMessageReply
: public RuntimeComponent<HistoryMessageReply, HistoryItem> {
HistoryMessageReply() = default;
HistoryMessageReply(const HistoryMessageReply &other) = delete;
HistoryMessageReply(HistoryMessageReply &&other) = delete;
HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
replyToPeerId = other.replyToPeerId;
replyToMsgId = other.replyToMsgId;
replyToMsgTop = other.replyToMsgTop;
replyToDocumentId = other.replyToDocumentId;
replyToWebPageId = other.replyToWebPageId;
std::swap(replyToMsg, other.replyToMsg);
replyToLnk = std::move(other.replyToLnk);
replyToName = std::move(other.replyToName);
replyToText = std::move(other.replyToText);
replyToVersion = other.replyToVersion;
maxReplyWidth = other.maxReplyWidth;
replyToVia = std::move(other.replyToVia);
return *this;
}
HistoryMessageReply &operator=(
const HistoryMessageReply &other) = delete;
HistoryMessageReply &operator=(HistoryMessageReply &&other) = default;
~HistoryMessageReply() {
// clearData() should be called by holder.
Expects(replyToMsg == nullptr);
Expects(replyToMsg.empty());
Expects(replyToVia == nullptr);
}
@ -205,15 +231,16 @@ struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, Histor
PeerId replyToPeerId = 0;
MsgId replyToMsgId = 0;
MsgId replyToMsgTop = 0;
HistoryItem *replyToMsg = nullptr;
DocumentId replyToDocumentId = 0;
WebPageId replyToWebPageId = 0;
ReplyToMessagePointer replyToMsg;
std::unique_ptr<HistoryMessageVia> replyToVia;
ClickHandlerPtr replyToLnk;
mutable Ui::Text::String replyToName, replyToText;
mutable int replyToVersion = 0;
mutable int maxReplyWidth = 0;
std::unique_ptr<HistoryMessageVia> replyToVia;
int toWidth = 0;
bool topicPost = false;
};

View File

@ -52,7 +52,7 @@ TextForMimeData WrapAsItem(
not_null<HistoryItem*> item,
TextForMimeData &&result) {
if (const auto reply = item->Get<HistoryMessageReply>()) {
if (const auto message = reply->replyToMsg) {
if (const auto message = reply->replyToMsg.get()) {
result = WrapAsReply(std::move(result), message);
}
}

View File

@ -247,6 +247,7 @@ struct HistoryMessage::CreateConfig {
PeerId replyToPeer = 0;
MsgId replyTo = 0;
MsgId replyToTop = 0;
bool replyIsTopicPost = false;
UserId viaBotId = 0;
int viewsCount = -1;
QString author;
@ -963,6 +964,7 @@ void HistoryMessage::createComponents(CreateConfig &&config) {
reply->replyToPeerId = config.replyToPeer;
reply->replyToMsgId = config.replyTo;
reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
reply->topicPost = config.replyIsTopicPost;
if (!reply->updateData(this)) {
RequestDependentMessageData(
this,

View File

@ -57,6 +57,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_replies_list.h"
#include "data/data_peer_values.h"
#include "data/data_changes.h"
@ -160,6 +162,7 @@ RepliesWidget::RepliesWidget(
, _history(history)
, _rootId(rootId)
, _root(lookupRoot())
, _topic(lookupTopic())
, _areComments(computeAreComments())
, _sendAction(history->owner().sendActionManager().repliesPainter(
history,
@ -332,6 +335,7 @@ RepliesWidget::~RepliesWidget() {
if (_readRequestTimer.isActive()) {
sendReadTillRequest();
}
_history->session().api().request(_resolveTopicRequestId).cancel();
base::take(_sendAction);
_history->owner().sendActionManager().repliesPainterRemoved(
_history,
@ -437,6 +441,58 @@ HistoryItem *RepliesWidget::lookupRoot() const {
return _history->owner().message(_history->peer, _rootId);
}
Data::ForumTopic *RepliesWidget::lookupTopic() {
if (const auto forum = _history->peer->forum()) {
const auto result = forum->topicFor(_rootId);
if (!result && !_resolveTopicRequestId) {
const auto api = &_history->session().api();
_resolveTopicRequestId = api->request(
MTPchannels_GetForumTopicsByID(
forum->channel()->inputChannel,
MTP_vector<MTPint>(1, MTP_int(_rootId.bare)))
).done([=](const MTPmessages_ForumTopics &result) {
const auto &data = result.data();
const auto owner = &_history->owner();
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
owner->processMessages(data.vmessages(), NewMessageType::Existing);
channel()->ptsReceived(data.vpts().v);
const auto &list = data.vtopics().v;
for (const auto &topic : list) {
const auto rootId = MsgId(topic.data().vid().v);
const auto i = _topics.find(rootId);
const auto creating = (i == end(_topics));
const auto raw = creating
? _topics.emplace(
rootId,
std::make_unique<ForumTopic>(_history, rootId)
).first->second.get()
: i->second.get();
raw->applyTopic(topic);
if (creating) {
raw->addToChatList(FilterId(), topicsList());
}
if (const auto last = raw->lastServerMessage()) {
_offsetDate = last->date();
_offsetId = last->id;
}
_offsetTopicId = rootId;
}
if (list.isEmpty() || list.size() == data.vcount().v) {
_allLoaded = true;
}
_requestId = 0;
_chatsListChanges.fire({});
if (_allLoaded) {
_chatsListLoadedEvents.fire({});
}
}).send();
}
return result;
}
return nullptr;
}
bool RepliesWidget::computeAreComments() const {
return _root && _root->isDiscussionPost();
}
@ -1294,7 +1350,11 @@ void RepliesWidget::refreshTopBarActiveChat() {
MsgId RepliesWidget::replyToId() const {
const auto custom = _composeControls->replyingToMessage().msg;
return custom ? custom : _rootId;
return custom
? custom
: (_rootId == Data::ForumTopic::kGeneralId)
? MsgId(0)
: _rootId;
}
void RepliesWidget::setupScrollDownButton() {
@ -1578,23 +1638,34 @@ bool RepliesWidget::showMessage(
}
const auto id = FullMsgId(_history->peer->id, messageId);
const auto message = _history->owner().message(id);
if (!message || message->replyToTop() != _rootId) {
if (!message) {
return false;
}
const auto originItem = [&]() -> HistoryItem* {
auto originFound = false;
const auto general = (_rootId == Data::ForumTopic::kGeneralId);
const auto originMessage = [&]() -> HistoryItem* {
using OriginMessage = Window::SectionShow::OriginMessage;
if (const auto origin = std::get_if<OriginMessage>(&params.origin)) {
if (const auto returnTo = session().data().message(origin->id)) {
if (returnTo->history() == _history
&& returnTo->replyToTop() == _rootId
&& _replyReturn != returnTo) {
if (returnTo->history() != _history) {
return nullptr;
} else if (general
&& _inner->viewByPosition(returnTo->position())
&& returnTo->replyToId() == messageId) {
return returnTo;
} else if (!general && (returnTo->replyToTop() == _rootId)) {
return returnTo;
}
}
}
return nullptr;
}();
if (!originMessage) {
return false;
}
const auto originItem = (!originMessage || _replyReturn == originMessage)
? nullptr
: originMessage;
showAtPosition(message->position(), originItem);
return true;
}
@ -1756,8 +1827,8 @@ void RepliesWidget::updateInnerVisibleArea() {
void RepliesWidget::updatePinnedVisibility() {
if (!_loaded) {
return;
} else if (!_root) {
setPinnedVisibility(true);
} else if (!_root || _root->isEmpty()) {
setPinnedVisibility(!_root);
return;
}
const auto item = [&] {

View File

@ -49,6 +49,7 @@ class Result;
namespace Data {
class RepliesList;
class ForumTopic;
} // namespace Data
namespace HistoryView {
@ -208,6 +209,7 @@ private:
[[nodiscard]] SendMenu::Type sendMenuType() const;
[[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] Data::ForumTopic *lookupTopic();
[[nodiscard]] bool computeAreComments() const;
[[nodiscard]] std::optional<int> computeUnreadCount() const;
void orderWidgets();
@ -270,6 +272,8 @@ private:
const MsgId _rootId = 0;
std::shared_ptr<Ui::ChatTheme> _theme;
HistoryItem *_root = nullptr;
Data::ForumTopic *_topic = nullptr;
std::shared_ptr<Data::RepliesList> _replies;
rpl::variable<bool> _areComments = false;
std::shared_ptr<SendActionPainter> _sendAction;
@ -301,6 +305,8 @@ private:
bool _readRequestPending = false;
mtpRequestId _readRequestId = 0;
mtpRequestId _resolveTopicRequestId = 0;
mtpRequestId _reloadUnreadCountRequestId = 0;
bool _loaded = false;

View File

@ -676,19 +676,6 @@ SessionController::SessionController(
closeFolder();
}, lifetime());
_openedForum.changes(
) | rpl::filter([](ChannelData *forum) {
return (forum != nullptr);
}) | rpl::map([](ChannelData *forum) {
return forum->flagsValue(
) | rpl::filter([](ChannelData::Flags::Change change) {
return (change.diff & ChannelData::Flag::Forum)
&& !(change.value & ChannelData::Flag::Forum);
});
}) | rpl::flatten_latest() | rpl::start_with_next([=] {
closeForum();
}, lifetime());
session->data().chatsFilters().changed(
) | rpl::start_with_next([=] {
checkOpenedFilter();
@ -842,7 +829,9 @@ void SessionController::checkOpenedFilter() {
const auto &list = session().data().chatsFilters().list();
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
if (i == end(list)) {
setActiveChatsFilter(0);
setActiveChatsFilter(
0,
{ anim::type::normal, anim::activation::background });
}
}
}
@ -874,18 +863,35 @@ void SessionController::closeFolder() {
_openedFolder = nullptr;
}
void SessionController::openForum(not_null<ChannelData*> forum) {
void SessionController::openForum(
not_null<ChannelData*> forum,
const SectionShow &params) {
Expects(forum->isForum());
_openedForumLifetime.destroy();
if (_openedForum.current() != forum) {
resetFakeUnreadWhileOpened();
}
setActiveChatsFilter(0);
setActiveChatsFilter(0, params);
closeFolder();
_openedForum = forum.get();
if (_openedForum.current() == forum) {
forum->flagsValue(
) | rpl::filter([=](const ChannelData::Flags::Change &update) {
using Flag = ChannelData::Flag;
return (update.diff & Flag::Forum)
&& !(update.value & Flag::Forum);
}) | rpl::start_with_next([=] {
closeForum();
showPeerHistory(
forum,
{ anim::type::normal, anim::activation::background });
}, _openedForumLifetime);
}
}
void SessionController::closeForum() {
_openedForumLifetime.destroy();
_openedForum = nullptr;
}
@ -926,12 +932,27 @@ void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) {
const auto was = _activeChatEntry.current().key.history();
const auto now = row.key.history();
if (was && was != now) {
_activeHistoryLifetime.destroy();
was->setFakeUnreadWhileOpened(false);
_invitePeekTimer.cancel();
}
_activeChatEntry = row;
if (now) {
now->setFakeUnreadWhileOpened(true);
if (const auto channel = now->peer->asChannel()
; channel && !channel->isForum()) {
channel->flagsValue(
) | rpl::filter([=](const ChannelData::Flags::Change &update) {
using Flag = ChannelData::Flag;
return (update.diff & Flag::Forum)
&& (update.value & Flag::Forum);
}) | rpl::start_with_next([=] {
clearSectionStack(
{ anim::type::normal, anim::activation::background });
openForum(channel,
{ anim::type::normal, anim::activation::background });
}, _openedForumLifetime);
}
}
if (session().supportMode()) {
pushToChatEntryHistory(row);
@ -1601,7 +1622,9 @@ FilterId SessionController::activeChatsFilterCurrent() const {
return _activeChatsFilter.current();
}
void SessionController::setActiveChatsFilter(FilterId id) {
void SessionController::setActiveChatsFilter(
FilterId id,
const SectionShow &params) {
if (activeChatsFilterCurrent() != id) {
resetFakeUnreadWhileOpened();
}
@ -1610,7 +1633,7 @@ void SessionController::setActiveChatsFilter(FilterId id) {
closeFolder();
}
if (adaptive().isOneColumn()) {
Ui::showChatsList(&session());
clearSectionStack(params);
}
}

View File

@ -345,7 +345,9 @@ public:
void closeFolder();
const rpl::variable<Data::Folder*> &openedFolder() const;
void openForum(not_null<ChannelData*> forum);
void openForum(
not_null<ChannelData*> forum,
const SectionShow &params = SectionShow::Way::ClearStack);
void closeForum();
const rpl::variable<ChannelData*> &openedForum() const;
@ -474,7 +476,9 @@ public:
[[nodiscard]] int filtersWidth() const;
[[nodiscard]] rpl::producer<FilterId> activeChatsFilter() const;
[[nodiscard]] FilterId activeChatsFilterCurrent() const;
void setActiveChatsFilter(FilterId id);
void setActiveChatsFilter(
FilterId id,
const SectionShow &params = SectionShow::Way::ClearStack);
void toggleFiltersMenu(bool enabled);
[[nodiscard]] rpl::producer<> filtersMenuChanged() const;
@ -582,6 +586,7 @@ private:
const std::unique_ptr<ChatHelpers::TabbedSelector> _tabbedSelector;
rpl::variable<Dialogs::RowDescriptor> _activeChatEntry;
rpl::lifetime _activeHistoryLifetime;
base::Variable<bool> _dialogsListFocused = { false };
base::Variable<bool> _dialogsListDisplayForced = { false };
std::deque<Dialogs::RowDescriptor> _chatEntryHistory;
@ -600,6 +605,7 @@ private:
PeerData *_showEditPeer = nullptr;
rpl::variable<Data::Folder*> _openedFolder;
rpl::variable<ChannelData*> _openedForum;
rpl::lifetime _openedForumLifetime;
rpl::event_stream<> _filtersMenuChanged;