Support default General topic in forums.
This commit is contained in:
parent
2201159da5
commit
73e56b0340
|
@ -248,6 +248,7 @@ public:
|
||||||
not_null<Window::SessionNavigation*> navigation,
|
not_null<Window::SessionNavigation*> navigation,
|
||||||
not_null<Ui::BoxContent*> box,
|
not_null<Ui::BoxContent*> box,
|
||||||
not_null<PeerData*> peer);
|
not_null<PeerData*> peer);
|
||||||
|
~Controller();
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::VerticalLayout> createContent();
|
[[nodiscard]] object_ptr<Ui::VerticalLayout> createContent();
|
||||||
void setFocus();
|
void setFocus();
|
||||||
|
@ -388,6 +389,8 @@ Controller::Controller(
|
||||||
_peer->updateFull();
|
_peer->updateFull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Controller::~Controller() = default;
|
||||||
|
|
||||||
void Controller::subscribeToMigration() {
|
void Controller::subscribeToMigration() {
|
||||||
SubscribeToMigration(
|
SubscribeToMigration(
|
||||||
_peer,
|
_peer,
|
||||||
|
@ -815,7 +818,7 @@ void Controller::fillForumButton() {
|
||||||
|
|
||||||
AddButtonWithText(
|
AddButtonWithText(
|
||||||
_controls.buttonsLayout,
|
_controls.buttonsLayout,
|
||||||
rpl::single(u"Forum"_q), // #TODO forum
|
rpl::single(u"Forum"_q), // #TODO forum lang
|
||||||
rpl::single(QString()),
|
rpl::single(QString()),
|
||||||
[] {},
|
[] {},
|
||||||
{ &st::settingsIconGroup, Settings::kIconPurple }
|
{ &st::settingsIconGroup, Settings::kIconPurple }
|
||||||
|
|
|
@ -60,13 +60,9 @@ Data::ChatBotCommands::Changed MegagroupInfo::setBotCommands(
|
||||||
return _botCommands.update(list);
|
return _botCommands.update(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MegagroupInfo::setIsForum(not_null<ChannelData*> that, bool is) {
|
void MegagroupInfo::ensureForum(not_null<ChannelData*> that) {
|
||||||
if (is == (_forum != nullptr)) {
|
if (!_forum) {
|
||||||
return;
|
|
||||||
} else if (is) {
|
|
||||||
_forum = std::make_unique<Data::Forum>(that->owner().history(that));
|
_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();
|
return _forum.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Data::Forum> MegagroupInfo::takeForumData() {
|
||||||
|
return std::move(_forum);
|
||||||
|
}
|
||||||
|
|
||||||
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
|
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
|
||||||
: PeerData(owner, id)
|
: PeerData(owner, id)
|
||||||
, inputChannel(
|
, inputChannel(
|
||||||
MTP_inputChannel(MTP_long(peerToChannel(id).bare), MTP_long(0)))
|
MTP_inputChannel(MTP_long(peerToChannel(id).bare), MTP_long(0)))
|
||||||
, _ptsWaiter(&owner->session().updates()) {
|
, _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) {
|
void ChannelData::setPhoto(const MTPChatPhoto &photo) {
|
||||||
|
@ -132,6 +108,44 @@ void ChannelData::setAccessHash(uint64 accessHash) {
|
||||||
MTP_long(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) {
|
void ChannelData::setInviteLink(const QString &newInviteLink) {
|
||||||
_inviteLink = newInviteLink;
|
_inviteLink = newInviteLink;
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,8 +96,9 @@ public:
|
||||||
return _botCommands;
|
return _botCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setIsForum(not_null<ChannelData*> that, bool is);
|
void ensureForum(not_null<ChannelData*> that);
|
||||||
[[nodiscard]] Data::Forum *forum() const;
|
[[nodiscard]] Data::Forum *forum() const;
|
||||||
|
[[nodiscard]] std::unique_ptr<Data::Forum> takeForumData();
|
||||||
|
|
||||||
std::deque<not_null<UserData*>> lastParticipants;
|
std::deque<not_null<UserData*>> lastParticipants;
|
||||||
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
|
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
|
||||||
|
@ -148,15 +149,9 @@ public:
|
||||||
void setPhoto(const MTPChatPhoto &photo);
|
void setPhoto(const MTPChatPhoto &photo);
|
||||||
void setAccessHash(uint64 accessHash);
|
void setAccessHash(uint64 accessHash);
|
||||||
|
|
||||||
void setFlags(ChannelDataFlags which) {
|
void setFlags(ChannelDataFlags which);
|
||||||
_flags.set(which);
|
void addFlags(ChannelDataFlags which);
|
||||||
}
|
void removeFlags(ChannelDataFlags which);
|
||||||
void addFlags(ChannelDataFlags which) {
|
|
||||||
_flags.add(which);
|
|
||||||
}
|
|
||||||
void removeFlags(ChannelDataFlags which) {
|
|
||||||
_flags.remove(which);
|
|
||||||
}
|
|
||||||
[[nodiscard]] auto flags() const {
|
[[nodiscard]] auto flags() const {
|
||||||
return _flags.current();
|
return _flags.current();
|
||||||
}
|
}
|
||||||
|
@ -490,8 +485,6 @@ private:
|
||||||
int _slowmodeSeconds = 0;
|
int _slowmodeSeconds = 0;
|
||||||
TimeId _slowmodeLastMessage = 0;
|
TimeId _slowmodeLastMessage = 0;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
|
@ -29,12 +29,25 @@ constexpr auto kTopicsPerPage = 500;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Forum::Forum(not_null<History*> forum)
|
Forum::Forum(not_null<History*> history)
|
||||||
: _forum(forum)
|
: _history(history)
|
||||||
, _topicsList(&forum->session(), FilterId(0), rpl::single(1)) {
|
, _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() {
|
not_null<Dialogs::MainList*> Forum::topicsList() {
|
||||||
return &_topicsList;
|
return &_topicsList;
|
||||||
|
@ -44,28 +57,24 @@ void Forum::requestTopics() {
|
||||||
if (_allLoaded || _requestId) {
|
if (_allLoaded || _requestId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto forum = _forum;
|
|
||||||
const auto firstLoad = !_offsetDate;
|
const auto firstLoad = !_offsetDate;
|
||||||
const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage;
|
const auto loadCount = firstLoad ? kTopicsFirstLoad : kTopicsPerPage;
|
||||||
const auto api = &forum->session().api();
|
const auto api = &_history->session().api();
|
||||||
_requestId = api->request(MTPchannels_GetForumTopics(
|
_requestId = api->request(MTPchannels_GetForumTopics(
|
||||||
MTP_flags(0),
|
MTP_flags(0),
|
||||||
forum->peer->asChannel()->inputChannel,
|
channel()->inputChannel,
|
||||||
MTPstring(), // q
|
MTPstring(), // q
|
||||||
MTP_int(_offsetDate),
|
MTP_int(_offsetDate),
|
||||||
MTP_int(_offsetId),
|
MTP_int(_offsetId),
|
||||||
MTP_int(_offsetTopicId),
|
MTP_int(_offsetTopicId),
|
||||||
MTP_int(loadCount)
|
MTP_int(loadCount)
|
||||||
)).done([=](const MTPmessages_ForumTopics &result) {
|
)).done([=](const MTPmessages_ForumTopics &result) {
|
||||||
if (!forum->peer->isForum()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto &data = result.data();
|
const auto &data = result.data();
|
||||||
const auto owner = &forum->owner();
|
const auto owner = &channel()->owner();
|
||||||
owner->processUsers(data.vusers());
|
owner->processUsers(data.vusers());
|
||||||
owner->processChats(data.vchats());
|
owner->processChats(data.vchats());
|
||||||
owner->processMessages(data.vmessages(), NewMessageType::Existing);
|
owner->processMessages(data.vmessages(), NewMessageType::Existing);
|
||||||
forum->peer->asChannel()->ptsReceived(data.vpts().v);
|
channel()->ptsReceived(data.vpts().v);
|
||||||
const auto &list = data.vtopics().v;
|
const auto &list = data.vtopics().v;
|
||||||
for (const auto &topic : list) {
|
for (const auto &topic : list) {
|
||||||
const auto rootId = MsgId(topic.data().vid().v);
|
const auto rootId = MsgId(topic.data().vid().v);
|
||||||
|
@ -74,7 +83,7 @@ void Forum::requestTopics() {
|
||||||
const auto raw = creating
|
const auto raw = creating
|
||||||
? _topics.emplace(
|
? _topics.emplace(
|
||||||
rootId,
|
rootId,
|
||||||
std::make_unique<ForumTopic>(forum, rootId)
|
std::make_unique<ForumTopic>(_history, rootId)
|
||||||
).first->second.get()
|
).first->second.get()
|
||||||
: i->second.get();
|
: i->second.get();
|
||||||
raw->applyTopic(topic);
|
raw->applyTopic(topic);
|
||||||
|
@ -107,7 +116,7 @@ void Forum::applyTopicAdded(MsgId rootId, const QString &title) {
|
||||||
} else {
|
} else {
|
||||||
const auto raw = _topics.emplace(
|
const auto raw = _topics.emplace(
|
||||||
rootId,
|
rootId,
|
||||||
std::make_unique<ForumTopic>(_forum, rootId)
|
std::make_unique<ForumTopic>(_history, rootId)
|
||||||
).first->second.get();
|
).first->second.get();
|
||||||
raw->applyTitle(title);
|
raw->applyTitle(title);
|
||||||
raw->addToChatList(FilterId(), topicsList());
|
raw->addToChatList(FilterId(), topicsList());
|
||||||
|
@ -122,10 +131,18 @@ void Forum::applyTopicRemoved(MsgId rootId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ForumTopic *Forum::topicFor(not_null<HistoryItem*> item) {
|
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)) {
|
if (const auto i = _topics.find(rootId); i != end(_topics)) {
|
||||||
return i->second.get();
|
return i->second.get();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// #TODO forum lang
|
||||||
|
applyTopicAdded(rootId, "General! Created.");
|
||||||
|
return _topics.find(rootId)->second.get();
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -148,13 +165,13 @@ void ShowAddForumTopic(
|
||||||
object_ptr<Ui::InputField>(
|
object_ptr<Ui::InputField>(
|
||||||
box,
|
box,
|
||||||
st::defaultInputField,
|
st::defaultInputField,
|
||||||
rpl::single(u"Topic Title"_q))); // #TODO forum
|
rpl::single(u"Topic Title"_q))); // #TODO forum lang
|
||||||
const auto message = box->addRow(
|
const auto message = box->addRow(
|
||||||
object_ptr<Ui::InputField>(
|
object_ptr<Ui::InputField>(
|
||||||
box,
|
box,
|
||||||
st::newGroupDescription,
|
st::newGroupDescription,
|
||||||
Ui::InputField::Mode::MultiLine,
|
Ui::InputField::Mode::MultiLine,
|
||||||
rpl::single(u"Message"_q))); // #TODO forum
|
rpl::single(u"Message"_q))); // #TODO forum lang
|
||||||
box->setFocusCallback([=] {
|
box->setFocusCallback([=] {
|
||||||
title->setFocusFast();
|
title->setFocusFast();
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,9 +20,11 @@ namespace Data {
|
||||||
|
|
||||||
class Forum final {
|
class Forum final {
|
||||||
public:
|
public:
|
||||||
explicit Forum(not_null<History*> forum);
|
explicit Forum(not_null<History*> history);
|
||||||
~Forum();
|
~Forum();
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<History*> history() const;
|
||||||
|
[[nodiscard]] not_null<ChannelData*> channel() const;
|
||||||
[[nodiscard]] not_null<Dialogs::MainList*> topicsList();
|
[[nodiscard]] not_null<Dialogs::MainList*> topicsList();
|
||||||
|
|
||||||
void requestTopics();
|
void requestTopics();
|
||||||
|
@ -32,9 +34,10 @@ public:
|
||||||
void applyTopicAdded(MsgId rootId, const QString &title);
|
void applyTopicAdded(MsgId rootId, const QString &title);
|
||||||
void applyTopicRemoved(MsgId rootId);
|
void applyTopicRemoved(MsgId rootId);
|
||||||
[[nodiscard]] ForumTopic *topicFor(not_null<HistoryItem*> item);
|
[[nodiscard]] ForumTopic *topicFor(not_null<HistoryItem*> item);
|
||||||
|
[[nodiscard]] ForumTopic *topicFor(MsgId rootId);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const not_null<History*> _forum;
|
const not_null<History*> _history;
|
||||||
|
|
||||||
base::flat_map<MsgId, std::unique_ptr<ForumTopic>> _topics;
|
base::flat_map<MsgId, std::unique_ptr<ForumTopic>> _topics;
|
||||||
Dialogs::MainList _topicsList;
|
Dialogs::MainList _topicsList;
|
||||||
|
|
|
@ -198,6 +198,9 @@ void ForumTopic::requestChatListMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeId ForumTopic::adjustedChatListTimeId() const {
|
TimeId ForumTopic::adjustedChatListTimeId() const {
|
||||||
|
if (isGeneral()) {
|
||||||
|
return TimeId(1);
|
||||||
|
}
|
||||||
const auto result = chatListTimeId();
|
const auto result = chatListTimeId();
|
||||||
#if 0 // #TODO forum
|
#if 0 // #TODO forum
|
||||||
if (const auto draft = cloudDraft()) {
|
if (const auto draft = cloudDraft()) {
|
||||||
|
@ -236,10 +239,10 @@ bool ForumTopic::lastServerMessageKnown() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ForumTopic::applyTitle(const QString &title) {
|
void ForumTopic::applyTitle(const QString &title) {
|
||||||
if (_title == title) {
|
if (_title == title || (isGeneral() && !_title.isEmpty())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_title = title;
|
_title = isGeneral() ? "General! Topic." : title; // #TODO forum lang
|
||||||
++_titleVersion;
|
++_titleVersion;
|
||||||
indexTitleParts();
|
indexTitleParts();
|
||||||
updateChatListEntry();
|
updateChatListEntry();
|
||||||
|
|
|
@ -26,6 +26,8 @@ class Session;
|
||||||
|
|
||||||
class ForumTopic final : public Dialogs::Entry {
|
class ForumTopic final : public Dialogs::Entry {
|
||||||
public:
|
public:
|
||||||
|
static constexpr auto kGeneralId = 1;
|
||||||
|
|
||||||
ForumTopic(not_null<History*> forum, MsgId rootId);
|
ForumTopic(not_null<History*> forum, MsgId rootId);
|
||||||
|
|
||||||
ForumTopic(const ForumTopic &) = delete;
|
ForumTopic(const ForumTopic &) = delete;
|
||||||
|
@ -33,6 +35,9 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] not_null<History*> forum() const;
|
[[nodiscard]] not_null<History*> forum() const;
|
||||||
[[nodiscard]] MsgId rootId() const;
|
[[nodiscard]] MsgId rootId() const;
|
||||||
|
[[nodiscard]] bool isGeneral() const {
|
||||||
|
return (_rootId == kGeneralId);
|
||||||
|
}
|
||||||
|
|
||||||
void applyTopic(const MTPForumTopic &topic);
|
void applyTopic(const MTPForumTopic &topic);
|
||||||
|
|
||||||
|
@ -88,7 +93,6 @@ private:
|
||||||
int unreadCount,
|
int unreadCount,
|
||||||
MsgId maxInboxRead,
|
MsgId maxInboxRead,
|
||||||
MsgId maxOutboxRead);
|
MsgId maxOutboxRead);
|
||||||
void applyChatListMessage(HistoryItem *item);
|
|
||||||
|
|
||||||
void setLastMessage(HistoryItem *item);
|
void setLastMessage(HistoryItem *item);
|
||||||
void setLastServerMessage(HistoryItem *item);
|
void setLastServerMessage(HistoryItem *item);
|
||||||
|
|
|
@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_messages.h"
|
#include "data/data_messages.h"
|
||||||
|
#include "data/data_forum_topic.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
|
|
||||||
|
@ -125,7 +126,10 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) {
|
||||||
}
|
}
|
||||||
slice.ids.reserve(messages.size());
|
slice.ids.reserve(messages.size());
|
||||||
for (const auto &item : messages) {
|
for (const auto &item : messages) {
|
||||||
if (item->replyToTop() != _rootId) {
|
const auto checkId = (_rootId == ForumTopic::kGeneralId)
|
||||||
|
? item->topicRootId()
|
||||||
|
: item->replyToTop();
|
||||||
|
if (!item->inThread(_rootId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
slice.ids.push_back(item->fullId());
|
slice.ids.push_back(item->fullId());
|
||||||
|
@ -143,7 +147,7 @@ void RepliesList::appendClientSideMessages(MessagesSlice &slice) {
|
||||||
dates.push_back(message->date());
|
dates.push_back(message->date());
|
||||||
}
|
}
|
||||||
for (const auto &item : messages) {
|
for (const auto &item : messages) {
|
||||||
if (item->replyToTop() != _rootId) {
|
if (!item->inThread(_rootId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto date = item->date();
|
const auto date = item->date();
|
||||||
|
@ -341,12 +345,15 @@ bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
|
||||||
auto nearestToAround = std::optional<MsgId>();
|
auto nearestToAround = std::optional<MsgId>();
|
||||||
slice->ids.reserve(useAfter + useBefore);
|
slice->ids.reserve(useAfter + useBefore);
|
||||||
for (auto j = i - useAfter, e = i + useBefore; j != e; ++j) {
|
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)
|
nearestToAround = (j == i - useAfter)
|
||||||
? *j
|
? id
|
||||||
: *(j - 1);
|
: *(j - 1);
|
||||||
}
|
}
|
||||||
slice->ids.emplace_back(peerId, *j);
|
slice->ids.emplace_back(peerId, id);
|
||||||
}
|
}
|
||||||
slice->nearestToAround = FullMsgId(
|
slice->nearestToAround = FullMsgId(
|
||||||
peerId,
|
peerId,
|
||||||
|
@ -380,7 +387,7 @@ bool RepliesList::applyUpdate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (update.item->replyToTop() != _rootId) {
|
if (!update.item->inThread(_rootId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto id = update.item->id;
|
const auto id = update.item->id;
|
||||||
|
@ -613,7 +620,7 @@ bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
|
||||||
auto skipped = 0;
|
auto skipped = 0;
|
||||||
for (const auto &message : list) {
|
for (const auto &message : list) {
|
||||||
if (const auto item = owner.addNewMessage(message, localFlags, type)) {
|
if (const auto item = owner.addNewMessage(message, localFlags, type)) {
|
||||||
if (item->replyToTop() == _rootId) {
|
if (item->inThread(_rootId)) {
|
||||||
if (toFront && item->id > _list.front()) {
|
if (toFront && item->id > _list.front()) {
|
||||||
refreshed.push_back(item->id);
|
refreshed.push_back(item->id);
|
||||||
} else if (_list.empty() || item->id < _list.back()) {
|
} else if (_list.empty() || item->id < _list.back()) {
|
||||||
|
|
|
@ -807,12 +807,7 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|
||||||
| ((data.is_forum() && data.is_megagroup())
|
| ((data.is_forum() && data.is_megagroup())
|
||||||
? Flag::Forum
|
? Flag::Forum
|
||||||
: Flag());
|
: Flag());
|
||||||
const auto wasForum = channel->isForum();
|
|
||||||
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
|
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(
|
channel->setName(
|
||||||
qs(data.vtitle()),
|
qs(data.vtitle()),
|
||||||
|
|
|
@ -404,12 +404,13 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::changeOpenedForum(ChannelData *forum) {
|
void InnerWidget::changeOpenedForum(ChannelData *forum) {
|
||||||
if (_openedForum == forum) {
|
const auto now = _openedForum ? _openedForum->channel().get() : nullptr;
|
||||||
|
if (now == forum) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopReorderPinned();
|
stopReorderPinned();
|
||||||
clearSelection();
|
clearSelection();
|
||||||
_openedForum = forum;
|
_openedForum = forum ? forum->forum() : nullptr;
|
||||||
|
|
||||||
_openedForumLifetime.destroy();
|
_openedForumLifetime.destroy();
|
||||||
if (forum) {
|
if (forum) {
|
||||||
|
@ -1791,7 +1792,7 @@ not_null<IndexedList*> InnerWidget::shownDialogs() const {
|
||||||
return _filterId
|
return _filterId
|
||||||
? session().data().chatsFilters().chatsList(_filterId)->indexed()
|
? session().data().chatsFilters().chatsList(_filterId)->indexed()
|
||||||
: _openedForum
|
: _openedForum
|
||||||
? _openedForum->forum()->topicsList()->indexed()
|
? _openedForum->topicsList()->indexed()
|
||||||
: session().data().chatsList(_openedFolder)->indexed();
|
: session().data().chatsList(_openedFolder)->indexed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2296,7 +2297,7 @@ Data::Folder *InnerWidget::shownFolder() const {
|
||||||
return _openedFolder;
|
return _openedFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelData *InnerWidget::shownForum() const {
|
Data::Forum *InnerWidget::shownForum() const {
|
||||||
return _openedForum;
|
return _openedForum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2410,7 +2411,7 @@ void InnerWidget::refreshEmptyLabel() {
|
||||||
} else if (_emptyState == EmptyState::EmptyFolder) {
|
} else if (_emptyState == EmptyState::EmptyFolder) {
|
||||||
editOpenedFilter();
|
editOpenedFilter();
|
||||||
} else if (_emptyState == EmptyState::EmptyForum) {
|
} else if (_emptyState == EmptyState::EmptyForum) {
|
||||||
Data::ShowAddForumTopic(_controller, _openedForum);
|
Data::ShowAddForumTopic(_controller, _openedForum->channel());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_empty->setVisible(_state == WidgetState::Default);
|
_empty->setVisible(_state == WidgetState::Default);
|
||||||
|
@ -3286,7 +3287,9 @@ void InnerWidget::setupShortcuts() {
|
||||||
});
|
});
|
||||||
request->check(Command::ChatSelf) && request->handle([=] {
|
request->check(Command::ChatSelf) && request->handle([=] {
|
||||||
if (_openedForum) {
|
if (_openedForum) {
|
||||||
Data::ShowAddForumTopic(_controller, _openedForum);
|
Data::ShowAddForumTopic(
|
||||||
|
_controller,
|
||||||
|
_openedForum->channel());
|
||||||
} else {
|
} else {
|
||||||
_controller->content()->choosePeer(
|
_controller->content()->choosePeer(
|
||||||
session().userPeerId(),
|
session().userPeerId(),
|
||||||
|
|
|
@ -36,6 +36,8 @@ class SessionController;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class CloudImageView;
|
class CloudImageView;
|
||||||
|
class Folder;
|
||||||
|
class Forum;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Dialogs::Ui {
|
namespace Dialogs::Ui {
|
||||||
|
@ -110,7 +112,7 @@ public:
|
||||||
void scrollToEntry(const RowDescriptor &entry);
|
void scrollToEntry(const RowDescriptor &entry);
|
||||||
|
|
||||||
Data::Folder *shownFolder() const;
|
Data::Folder *shownFolder() const;
|
||||||
ChannelData *shownForum() const;
|
Data::Forum *shownForum() const;
|
||||||
int32 lastSearchDate() const;
|
int32 lastSearchDate() const;
|
||||||
PeerData *lastSearchPeer() const;
|
PeerData *lastSearchPeer() const;
|
||||||
MsgId lastSearchId() const;
|
MsgId lastSearchId() const;
|
||||||
|
@ -351,7 +353,7 @@ private:
|
||||||
Qt::MouseButton _pressButton = Qt::LeftButton;
|
Qt::MouseButton _pressButton = Qt::LeftButton;
|
||||||
|
|
||||||
Data::Folder *_openedFolder = nullptr;
|
Data::Folder *_openedFolder = nullptr;
|
||||||
ChannelData *_openedForum = nullptr;
|
Data::Forum *_openedForum = nullptr;
|
||||||
rpl::lifetime _openedForumLifetime;
|
rpl::lifetime _openedForumLifetime;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
|
||||||
|
|
|
@ -260,50 +260,7 @@ Widget::Widget(
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
_inner->chosenRow(
|
_inner->chosenRow(
|
||||||
) | rpl::start_with_next([=](const ChosenRow &row) {
|
) | rpl::start_with_next([=](const ChosenRow &row) {
|
||||||
const auto openSearchResult = !controller->selectingPeer()
|
chosenRow(row);
|
||||||
&& 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();
|
|
||||||
}
|
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
_scroll->geometryChanged(
|
_scroll->geometryChanged(
|
||||||
|
@ -422,6 +379,53 @@ Widget::Widget(
|
||||||
setupDownloadBar();
|
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(
|
void Widget::setGeometryWithTopMoved(
|
||||||
const QRect &newGeometry,
|
const QRect &newGeometry,
|
||||||
int topDelta) {
|
int topDelta) {
|
||||||
|
|
|
@ -116,6 +116,7 @@ private:
|
||||||
Internal,
|
Internal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void chosenRow(const ChosenRow &row);
|
||||||
void listScrollUpdated();
|
void listScrollUpdated();
|
||||||
void cancelSearchInChat();
|
void cancelSearchInChat();
|
||||||
void filterCursorMoved(int from = -1, int to = -1);
|
void filterCursorMoved(int from = -1, int to = -1);
|
||||||
|
|
|
@ -47,6 +47,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_messages.h"
|
#include "data/data_messages.h"
|
||||||
#include "data/data_media_types.h"
|
#include "data/data_media_types.h"
|
||||||
#include "data/data_folder.h"
|
#include "data/data_folder.h"
|
||||||
|
#include "data/data_forum_topic.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -1058,6 +1059,21 @@ MsgId HistoryItem::replyToTop() const {
|
||||||
return 0;
|
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 {
|
not_null<PeerData*> HistoryItem::author() const {
|
||||||
return (isPost() && !isSponsored()) ? history()->peer : from();
|
return (isPost() && !isSponsored()) ? history()->peer : from();
|
||||||
}
|
}
|
||||||
|
|
|
@ -410,6 +410,8 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] MsgId replyToId() const;
|
[[nodiscard]] MsgId replyToId() const;
|
||||||
[[nodiscard]] MsgId replyToTop() const;
|
[[nodiscard]] MsgId replyToTop() const;
|
||||||
|
[[nodiscard]] MsgId topicRootId() const;
|
||||||
|
[[nodiscard]] MsgId inThread(MsgId rootId) const;
|
||||||
|
|
||||||
[[nodiscard]] not_null<PeerData*> author() const;
|
[[nodiscard]] not_null<PeerData*> author() const;
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ bool HistoryMessageReply::updateData(
|
||||||
} else {
|
} else {
|
||||||
holder->history()->owner().registerDependentMessage(
|
holder->history()->owner().registerDependentMessage(
|
||||||
holder,
|
holder,
|
||||||
replyToMsg);
|
replyToMsg.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -298,7 +298,7 @@ bool HistoryMessageReply::updateData(
|
||||||
void HistoryMessageReply::setReplyToLinkFrom(
|
void HistoryMessageReply::setReplyToLinkFrom(
|
||||||
not_null<HistoryMessage*> holder) {
|
not_null<HistoryMessage*> holder) {
|
||||||
replyToLnk = replyToMsg
|
replyToLnk = replyToMsg
|
||||||
? goToMessageClickHandler(replyToMsg, holder->fullId())
|
? goToMessageClickHandler(replyToMsg.get(), holder->fullId())
|
||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ void HistoryMessageReply::clearData(not_null<HistoryMessage*> holder) {
|
||||||
if (replyToMsg) {
|
if (replyToMsg) {
|
||||||
holder->history()->owner().unregisterDependentMessage(
|
holder->history()->owner().unregisterDependentMessage(
|
||||||
holder,
|
holder,
|
||||||
replyToMsg);
|
replyToMsg.get());
|
||||||
replyToMsg = nullptr;
|
replyToMsg = nullptr;
|
||||||
}
|
}
|
||||||
replyToMsgId = 0;
|
replyToMsgId = 0;
|
||||||
|
@ -399,7 +399,7 @@ void HistoryMessageReply::resize(int width) const {
|
||||||
void HistoryMessageReply::itemRemoved(
|
void HistoryMessageReply::itemRemoved(
|
||||||
HistoryMessage *holder,
|
HistoryMessage *holder,
|
||||||
HistoryItem *removed) {
|
HistoryItem *removed) {
|
||||||
if (replyToMsg == removed) {
|
if (replyToMsg.get() == removed) {
|
||||||
clearData(holder);
|
clearData(holder);
|
||||||
holder->history()->owner().requestItemResize(holder);
|
holder->history()->owner().requestItemResize(holder);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,29 +132,55 @@ struct HistoryMessageSponsored : public RuntimeComponent<HistoryMessageSponsored
|
||||||
bool recommended = false;
|
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() = default;
|
||||||
HistoryMessageReply(const HistoryMessageReply &other) = delete;
|
HistoryMessageReply(const HistoryMessageReply &other) = delete;
|
||||||
HistoryMessageReply(HistoryMessageReply &&other) = delete;
|
HistoryMessageReply(HistoryMessageReply &&other) = delete;
|
||||||
HistoryMessageReply &operator=(const HistoryMessageReply &other) = delete;
|
HistoryMessageReply &operator=(
|
||||||
HistoryMessageReply &operator=(HistoryMessageReply &&other) {
|
const HistoryMessageReply &other) = delete;
|
||||||
replyToPeerId = other.replyToPeerId;
|
HistoryMessageReply &operator=(HistoryMessageReply &&other) = default;
|
||||||
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() {
|
~HistoryMessageReply() {
|
||||||
// clearData() should be called by holder.
|
// clearData() should be called by holder.
|
||||||
Expects(replyToMsg == nullptr);
|
Expects(replyToMsg.empty());
|
||||||
Expects(replyToVia == nullptr);
|
Expects(replyToVia == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,15 +231,16 @@ struct HistoryMessageReply : public RuntimeComponent<HistoryMessageReply, Histor
|
||||||
PeerId replyToPeerId = 0;
|
PeerId replyToPeerId = 0;
|
||||||
MsgId replyToMsgId = 0;
|
MsgId replyToMsgId = 0;
|
||||||
MsgId replyToMsgTop = 0;
|
MsgId replyToMsgTop = 0;
|
||||||
HistoryItem *replyToMsg = nullptr;
|
|
||||||
DocumentId replyToDocumentId = 0;
|
DocumentId replyToDocumentId = 0;
|
||||||
WebPageId replyToWebPageId = 0;
|
WebPageId replyToWebPageId = 0;
|
||||||
|
ReplyToMessagePointer replyToMsg;
|
||||||
|
std::unique_ptr<HistoryMessageVia> replyToVia;
|
||||||
ClickHandlerPtr replyToLnk;
|
ClickHandlerPtr replyToLnk;
|
||||||
mutable Ui::Text::String replyToName, replyToText;
|
mutable Ui::Text::String replyToName, replyToText;
|
||||||
mutable int replyToVersion = 0;
|
mutable int replyToVersion = 0;
|
||||||
mutable int maxReplyWidth = 0;
|
mutable int maxReplyWidth = 0;
|
||||||
std::unique_ptr<HistoryMessageVia> replyToVia;
|
|
||||||
int toWidth = 0;
|
int toWidth = 0;
|
||||||
|
bool topicPost = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ TextForMimeData WrapAsItem(
|
||||||
not_null<HistoryItem*> item,
|
not_null<HistoryItem*> item,
|
||||||
TextForMimeData &&result) {
|
TextForMimeData &&result) {
|
||||||
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
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);
|
result = WrapAsReply(std::move(result), message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,6 +247,7 @@ struct HistoryMessage::CreateConfig {
|
||||||
PeerId replyToPeer = 0;
|
PeerId replyToPeer = 0;
|
||||||
MsgId replyTo = 0;
|
MsgId replyTo = 0;
|
||||||
MsgId replyToTop = 0;
|
MsgId replyToTop = 0;
|
||||||
|
bool replyIsTopicPost = false;
|
||||||
UserId viaBotId = 0;
|
UserId viaBotId = 0;
|
||||||
int viewsCount = -1;
|
int viewsCount = -1;
|
||||||
QString author;
|
QString author;
|
||||||
|
@ -963,6 +964,7 @@ void HistoryMessage::createComponents(CreateConfig &&config) {
|
||||||
reply->replyToPeerId = config.replyToPeer;
|
reply->replyToPeerId = config.replyToPeer;
|
||||||
reply->replyToMsgId = config.replyTo;
|
reply->replyToMsgId = config.replyTo;
|
||||||
reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
|
reply->replyToMsgTop = isScheduled() ? 0 : config.replyToTop;
|
||||||
|
reply->topicPost = config.replyIsTopicPost;
|
||||||
if (!reply->updateData(this)) {
|
if (!reply->updateData(this)) {
|
||||||
RequestDependentMessageData(
|
RequestDependentMessageData(
|
||||||
this,
|
this,
|
||||||
|
|
|
@ -57,6 +57,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
#include "data/data_chat.h"
|
#include "data/data_chat.h"
|
||||||
#include "data/data_channel.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_replies_list.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
@ -160,6 +162,7 @@ RepliesWidget::RepliesWidget(
|
||||||
, _history(history)
|
, _history(history)
|
||||||
, _rootId(rootId)
|
, _rootId(rootId)
|
||||||
, _root(lookupRoot())
|
, _root(lookupRoot())
|
||||||
|
, _topic(lookupTopic())
|
||||||
, _areComments(computeAreComments())
|
, _areComments(computeAreComments())
|
||||||
, _sendAction(history->owner().sendActionManager().repliesPainter(
|
, _sendAction(history->owner().sendActionManager().repliesPainter(
|
||||||
history,
|
history,
|
||||||
|
@ -332,6 +335,7 @@ RepliesWidget::~RepliesWidget() {
|
||||||
if (_readRequestTimer.isActive()) {
|
if (_readRequestTimer.isActive()) {
|
||||||
sendReadTillRequest();
|
sendReadTillRequest();
|
||||||
}
|
}
|
||||||
|
_history->session().api().request(_resolveTopicRequestId).cancel();
|
||||||
base::take(_sendAction);
|
base::take(_sendAction);
|
||||||
_history->owner().sendActionManager().repliesPainterRemoved(
|
_history->owner().sendActionManager().repliesPainterRemoved(
|
||||||
_history,
|
_history,
|
||||||
|
@ -437,6 +441,58 @@ HistoryItem *RepliesWidget::lookupRoot() const {
|
||||||
return _history->owner().message(_history->peer, _rootId);
|
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 {
|
bool RepliesWidget::computeAreComments() const {
|
||||||
return _root && _root->isDiscussionPost();
|
return _root && _root->isDiscussionPost();
|
||||||
}
|
}
|
||||||
|
@ -1294,7 +1350,11 @@ void RepliesWidget::refreshTopBarActiveChat() {
|
||||||
|
|
||||||
MsgId RepliesWidget::replyToId() const {
|
MsgId RepliesWidget::replyToId() const {
|
||||||
const auto custom = _composeControls->replyingToMessage().msg;
|
const auto custom = _composeControls->replyingToMessage().msg;
|
||||||
return custom ? custom : _rootId;
|
return custom
|
||||||
|
? custom
|
||||||
|
: (_rootId == Data::ForumTopic::kGeneralId)
|
||||||
|
? MsgId(0)
|
||||||
|
: _rootId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepliesWidget::setupScrollDownButton() {
|
void RepliesWidget::setupScrollDownButton() {
|
||||||
|
@ -1578,23 +1638,34 @@ bool RepliesWidget::showMessage(
|
||||||
}
|
}
|
||||||
const auto id = FullMsgId(_history->peer->id, messageId);
|
const auto id = FullMsgId(_history->peer->id, messageId);
|
||||||
const auto message = _history->owner().message(id);
|
const auto message = _history->owner().message(id);
|
||||||
if (!message || message->replyToTop() != _rootId) {
|
if (!message) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
auto originFound = false;
|
||||||
const auto originItem = [&]() -> HistoryItem* {
|
const auto general = (_rootId == Data::ForumTopic::kGeneralId);
|
||||||
|
const auto originMessage = [&]() -> HistoryItem* {
|
||||||
using OriginMessage = Window::SectionShow::OriginMessage;
|
using OriginMessage = Window::SectionShow::OriginMessage;
|
||||||
if (const auto origin = std::get_if<OriginMessage>(¶ms.origin)) {
|
if (const auto origin = std::get_if<OriginMessage>(¶ms.origin)) {
|
||||||
if (const auto returnTo = session().data().message(origin->id)) {
|
if (const auto returnTo = session().data().message(origin->id)) {
|
||||||
if (returnTo->history() == _history
|
if (returnTo->history() != _history) {
|
||||||
&& returnTo->replyToTop() == _rootId
|
return nullptr;
|
||||||
&& _replyReturn != returnTo) {
|
} else if (general
|
||||||
|
&& _inner->viewByPosition(returnTo->position())
|
||||||
|
&& returnTo->replyToId() == messageId) {
|
||||||
|
return returnTo;
|
||||||
|
} else if (!general && (returnTo->replyToTop() == _rootId)) {
|
||||||
return returnTo;
|
return returnTo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}();
|
}();
|
||||||
|
if (!originMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto originItem = (!originMessage || _replyReturn == originMessage)
|
||||||
|
? nullptr
|
||||||
|
: originMessage;
|
||||||
showAtPosition(message->position(), originItem);
|
showAtPosition(message->position(), originItem);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1756,8 +1827,8 @@ void RepliesWidget::updateInnerVisibleArea() {
|
||||||
void RepliesWidget::updatePinnedVisibility() {
|
void RepliesWidget::updatePinnedVisibility() {
|
||||||
if (!_loaded) {
|
if (!_loaded) {
|
||||||
return;
|
return;
|
||||||
} else if (!_root) {
|
} else if (!_root || _root->isEmpty()) {
|
||||||
setPinnedVisibility(true);
|
setPinnedVisibility(!_root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto item = [&] {
|
const auto item = [&] {
|
||||||
|
|
|
@ -49,6 +49,7 @@ class Result;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class RepliesList;
|
class RepliesList;
|
||||||
|
class ForumTopic;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
|
@ -208,6 +209,7 @@ private:
|
||||||
[[nodiscard]] SendMenu::Type sendMenuType() const;
|
[[nodiscard]] SendMenu::Type sendMenuType() const;
|
||||||
[[nodiscard]] MsgId replyToId() const;
|
[[nodiscard]] MsgId replyToId() const;
|
||||||
[[nodiscard]] HistoryItem *lookupRoot() const;
|
[[nodiscard]] HistoryItem *lookupRoot() const;
|
||||||
|
[[nodiscard]] Data::ForumTopic *lookupTopic();
|
||||||
[[nodiscard]] bool computeAreComments() const;
|
[[nodiscard]] bool computeAreComments() const;
|
||||||
[[nodiscard]] std::optional<int> computeUnreadCount() const;
|
[[nodiscard]] std::optional<int> computeUnreadCount() const;
|
||||||
void orderWidgets();
|
void orderWidgets();
|
||||||
|
@ -270,6 +272,8 @@ private:
|
||||||
const MsgId _rootId = 0;
|
const MsgId _rootId = 0;
|
||||||
std::shared_ptr<Ui::ChatTheme> _theme;
|
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||||
HistoryItem *_root = nullptr;
|
HistoryItem *_root = nullptr;
|
||||||
|
Data::ForumTopic *_topic = nullptr;
|
||||||
|
|
||||||
std::shared_ptr<Data::RepliesList> _replies;
|
std::shared_ptr<Data::RepliesList> _replies;
|
||||||
rpl::variable<bool> _areComments = false;
|
rpl::variable<bool> _areComments = false;
|
||||||
std::shared_ptr<SendActionPainter> _sendAction;
|
std::shared_ptr<SendActionPainter> _sendAction;
|
||||||
|
@ -301,6 +305,8 @@ private:
|
||||||
bool _readRequestPending = false;
|
bool _readRequestPending = false;
|
||||||
mtpRequestId _readRequestId = 0;
|
mtpRequestId _readRequestId = 0;
|
||||||
|
|
||||||
|
mtpRequestId _resolveTopicRequestId = 0;
|
||||||
|
|
||||||
mtpRequestId _reloadUnreadCountRequestId = 0;
|
mtpRequestId _reloadUnreadCountRequestId = 0;
|
||||||
bool _loaded = false;
|
bool _loaded = false;
|
||||||
|
|
||||||
|
|
|
@ -676,19 +676,6 @@ SessionController::SessionController(
|
||||||
closeFolder();
|
closeFolder();
|
||||||
}, lifetime());
|
}, 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(
|
session->data().chatsFilters().changed(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
checkOpenedFilter();
|
checkOpenedFilter();
|
||||||
|
@ -842,7 +829,9 @@ void SessionController::checkOpenedFilter() {
|
||||||
const auto &list = session().data().chatsFilters().list();
|
const auto &list = session().data().chatsFilters().list();
|
||||||
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
|
const auto i = ranges::find(list, filterId, &Data::ChatFilter::id);
|
||||||
if (i == end(list)) {
|
if (i == end(list)) {
|
||||||
setActiveChatsFilter(0);
|
setActiveChatsFilter(
|
||||||
|
0,
|
||||||
|
{ anim::type::normal, anim::activation::background });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -874,18 +863,35 @@ void SessionController::closeFolder() {
|
||||||
_openedFolder = nullptr;
|
_openedFolder = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionController::openForum(not_null<ChannelData*> forum) {
|
void SessionController::openForum(
|
||||||
|
not_null<ChannelData*> forum,
|
||||||
|
const SectionShow ¶ms) {
|
||||||
Expects(forum->isForum());
|
Expects(forum->isForum());
|
||||||
|
|
||||||
|
_openedForumLifetime.destroy();
|
||||||
if (_openedForum.current() != forum) {
|
if (_openedForum.current() != forum) {
|
||||||
resetFakeUnreadWhileOpened();
|
resetFakeUnreadWhileOpened();
|
||||||
}
|
}
|
||||||
setActiveChatsFilter(0);
|
setActiveChatsFilter(0, params);
|
||||||
closeFolder();
|
closeFolder();
|
||||||
_openedForum = forum.get();
|
_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() {
|
void SessionController::closeForum() {
|
||||||
|
_openedForumLifetime.destroy();
|
||||||
_openedForum = nullptr;
|
_openedForum = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -926,12 +932,27 @@ void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) {
|
||||||
const auto was = _activeChatEntry.current().key.history();
|
const auto was = _activeChatEntry.current().key.history();
|
||||||
const auto now = row.key.history();
|
const auto now = row.key.history();
|
||||||
if (was && was != now) {
|
if (was && was != now) {
|
||||||
|
_activeHistoryLifetime.destroy();
|
||||||
was->setFakeUnreadWhileOpened(false);
|
was->setFakeUnreadWhileOpened(false);
|
||||||
_invitePeekTimer.cancel();
|
_invitePeekTimer.cancel();
|
||||||
}
|
}
|
||||||
_activeChatEntry = row;
|
_activeChatEntry = row;
|
||||||
if (now) {
|
if (now) {
|
||||||
now->setFakeUnreadWhileOpened(true);
|
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()) {
|
if (session().supportMode()) {
|
||||||
pushToChatEntryHistory(row);
|
pushToChatEntryHistory(row);
|
||||||
|
@ -1601,7 +1622,9 @@ FilterId SessionController::activeChatsFilterCurrent() const {
|
||||||
return _activeChatsFilter.current();
|
return _activeChatsFilter.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionController::setActiveChatsFilter(FilterId id) {
|
void SessionController::setActiveChatsFilter(
|
||||||
|
FilterId id,
|
||||||
|
const SectionShow ¶ms) {
|
||||||
if (activeChatsFilterCurrent() != id) {
|
if (activeChatsFilterCurrent() != id) {
|
||||||
resetFakeUnreadWhileOpened();
|
resetFakeUnreadWhileOpened();
|
||||||
}
|
}
|
||||||
|
@ -1610,7 +1633,7 @@ void SessionController::setActiveChatsFilter(FilterId id) {
|
||||||
closeFolder();
|
closeFolder();
|
||||||
}
|
}
|
||||||
if (adaptive().isOneColumn()) {
|
if (adaptive().isOneColumn()) {
|
||||||
Ui::showChatsList(&session());
|
clearSectionStack(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -345,7 +345,9 @@ public:
|
||||||
void closeFolder();
|
void closeFolder();
|
||||||
const rpl::variable<Data::Folder*> &openedFolder() const;
|
const rpl::variable<Data::Folder*> &openedFolder() const;
|
||||||
|
|
||||||
void openForum(not_null<ChannelData*> forum);
|
void openForum(
|
||||||
|
not_null<ChannelData*> forum,
|
||||||
|
const SectionShow ¶ms = SectionShow::Way::ClearStack);
|
||||||
void closeForum();
|
void closeForum();
|
||||||
const rpl::variable<ChannelData*> &openedForum() const;
|
const rpl::variable<ChannelData*> &openedForum() const;
|
||||||
|
|
||||||
|
@ -474,7 +476,9 @@ public:
|
||||||
[[nodiscard]] int filtersWidth() const;
|
[[nodiscard]] int filtersWidth() const;
|
||||||
[[nodiscard]] rpl::producer<FilterId> activeChatsFilter() const;
|
[[nodiscard]] rpl::producer<FilterId> activeChatsFilter() const;
|
||||||
[[nodiscard]] FilterId activeChatsFilterCurrent() const;
|
[[nodiscard]] FilterId activeChatsFilterCurrent() const;
|
||||||
void setActiveChatsFilter(FilterId id);
|
void setActiveChatsFilter(
|
||||||
|
FilterId id,
|
||||||
|
const SectionShow ¶ms = SectionShow::Way::ClearStack);
|
||||||
|
|
||||||
void toggleFiltersMenu(bool enabled);
|
void toggleFiltersMenu(bool enabled);
|
||||||
[[nodiscard]] rpl::producer<> filtersMenuChanged() const;
|
[[nodiscard]] rpl::producer<> filtersMenuChanged() const;
|
||||||
|
@ -582,6 +586,7 @@ private:
|
||||||
const std::unique_ptr<ChatHelpers::TabbedSelector> _tabbedSelector;
|
const std::unique_ptr<ChatHelpers::TabbedSelector> _tabbedSelector;
|
||||||
|
|
||||||
rpl::variable<Dialogs::RowDescriptor> _activeChatEntry;
|
rpl::variable<Dialogs::RowDescriptor> _activeChatEntry;
|
||||||
|
rpl::lifetime _activeHistoryLifetime;
|
||||||
base::Variable<bool> _dialogsListFocused = { false };
|
base::Variable<bool> _dialogsListFocused = { false };
|
||||||
base::Variable<bool> _dialogsListDisplayForced = { false };
|
base::Variable<bool> _dialogsListDisplayForced = { false };
|
||||||
std::deque<Dialogs::RowDescriptor> _chatEntryHistory;
|
std::deque<Dialogs::RowDescriptor> _chatEntryHistory;
|
||||||
|
@ -600,6 +605,7 @@ private:
|
||||||
PeerData *_showEditPeer = nullptr;
|
PeerData *_showEditPeer = nullptr;
|
||||||
rpl::variable<Data::Folder*> _openedFolder;
|
rpl::variable<Data::Folder*> _openedFolder;
|
||||||
rpl::variable<ChannelData*> _openedForum;
|
rpl::variable<ChannelData*> _openedForum;
|
||||||
|
rpl::lifetime _openedForumLifetime;
|
||||||
|
|
||||||
rpl::event_stream<> _filtersMenuChanged;
|
rpl::event_stream<> _filtersMenuChanged;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue