Allow enabling forum, creating topics.

This commit is contained in:
John Preston 2022-09-20 22:12:30 +04:00
parent c88140e256
commit 388fe6adfb
21 changed files with 781 additions and 49 deletions

View File

@ -469,6 +469,8 @@ PRIVATE
data/data_folder.h
data/data_forum.cpp
data/data_forum.h
data/data_forum_topic.cpp
data/data_forum_topic.h
data/data_file_click_handler.cpp
data/data_file_click_handler.h
data/data_file_origin.cpp

View File

@ -60,11 +60,11 @@ Data::ChatBotCommands::Changed MegagroupInfo::setBotCommands(
return _botCommands.update(list);
}
void MegagroupInfo::setIsForum(bool is) {
void MegagroupInfo::setIsForum(not_null<ChannelData*> that, bool is) {
if (is == (_forum != nullptr)) {
return;
} else if (is) {
_forum = std::make_unique<Data::Forum>();
_forum = std::make_unique<Data::Forum>(that->owner().history(that));
} else {
_forum = nullptr;
}
@ -97,10 +97,6 @@ ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
mgInfo = nullptr;
}
}
if (change.diff & Flag::Forum) {
Assert(mgInfo != nullptr);
mgInfo->setIsForum(change.value & Flag::Forum);
}
if (change.diff & Flag::CallNotEmpty) {
if (const auto history = this->owner().historyLoaded(this)) {
history->updateChatListEntry();

View File

@ -100,7 +100,7 @@ public:
return _botCommands;
}
void setIsForum(bool is);
void setIsForum(not_null<ChannelData*> that, bool is);
[[nodiscard]] Data::Forum *forum() const;
std::deque<not_null<UserData*>> lastParticipants;

View File

@ -41,8 +41,7 @@ Folder::Folder(not_null<Data::Session*> owner, FolderId id)
&owner->session(),
FilterId(),
owner->maxPinnedChatsLimitValue(this, FilterId()))
, _name(tr::lng_archived_name(tr::now))
, _chatListNameSortKey(owner->nameSortKey(_name)) {
, _name(tr::lng_archived_name(tr::now)) {
indexNameParts();
session().changes().peerUpdates(
@ -374,7 +373,8 @@ const base::flat_set<QChar> &Folder::chatListFirstLetters() const {
}
const QString &Folder::chatListNameSortKey() const {
return _chatListNameSortKey;
static const auto empty = QString();
return empty;
}
} // namespace Data

View File

@ -21,7 +21,6 @@ class Session;
namespace Data {
class Session;
class Folder;
class Folder final : public Dialogs::Entry, public base::has_weak_ptr {
public:
@ -31,12 +30,12 @@ public:
Folder(const Folder &) = delete;
Folder &operator=(const Folder &) = delete;
FolderId id() const;
[[nodiscard]] FolderId id() const;
void registerOne(not_null<History*> history);
void unregisterOne(not_null<History*> history);
void oneListMessageChanged(HistoryItem *from, HistoryItem *to);
not_null<Dialogs::MainList*> chatsList();
[[nodiscard]] not_null<Dialogs::MainList*> chatsList();
void applyDialog(const MTPDdialogFolder &data);
void applyPinnedUpdate(const MTPDupdateDialogPinned &data);
@ -94,13 +93,12 @@ private:
const style::color *overrideBg,
const style::color *overrideFg) const;
FolderId _id = 0;
const FolderId _id = 0;
Dialogs::MainList _chatsList;
QString _name;
base::flat_set<QString> _nameWords;
base::flat_set<QChar> _nameFirstLetters;
QString _chatListNameSortKey;
std::vector<not_null<History*>> _lastHistories;
HistoryItem *_chatListMessage = nullptr;

View File

@ -7,10 +7,172 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_forum.h"
namespace Data {
#include "data/data_channel.h"
#include "data/data_session.h"
#include "data/data_forum_topic.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
#include "base/random.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "ui/layers/generic_box.h"
#include "ui/widgets/input_fields.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
Forum::Forum() = default;
namespace Data {
namespace {
constexpr auto kTopicsFirstLoad = 20;
constexpr auto kTopicsPerPage = 500;
} // namespace
Forum::Forum(not_null<History*> forum)
: _forum(forum)
, _topicsList(&forum->session(), FilterId(0), rpl::single(1)) {
}
Forum::~Forum() = default;
not_null<Dialogs::MainList*> Forum::topicsList() {
return &_topicsList;
}
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();
_requestId = api->request(MTPchannels_GetForumTopics(
MTP_flags(0),
forum->peer->asChannel()->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();
owner->processUsers(data.vusers());
owner->processChats(data.vchats());
owner->processMessages(data.vmessages(), NewMessageType::Existing);
forum->peer->asChannel()->ptsReceived(data.vpts().v);
const auto &list = data.vtopics().v;
for (const auto &topic : list) {
const auto rootId = MsgId(topic.data().vid().v);
if (const auto i = _topics.find(rootId); i != end(_topics)) {
i->second->applyTopic(topic);
} else {
const auto raw = _topics.emplace(
rootId,
std::make_unique<ForumTopic>(forum, rootId)
).first->second.get();
raw->applyTopic(topic);
raw->addToChatList(FilterId(), topicsList());
}
}
if (list.isEmpty() || list.size() == data.vcount().v) {
_allLoaded = true;
}
if (const auto date = data.vnext_date()) {
_offsetDate = date->v;
}
_requestId = 0;
_chatsListChanges.fire({});
if (_allLoaded) {
_chatsListLoadedEvents.fire({});
}
}).fail([=](const MTP::Error &error) {
_allLoaded = true;
_requestId = 0;
}).send();
}
void Forum::topicAdded(not_null<HistoryItem*> root) {
const auto rootId = root->id;
if (const auto i = _topics.find(rootId); i != end(_topics)) {
//i->second->applyTopic(topic);
} else {
const auto raw = _topics.emplace(
rootId,
std::make_unique<ForumTopic>(_forum, rootId)
).first->second.get();
//raw->applyTopic(topic);
raw->addToChatList(FilterId(), topicsList());
_chatsListChanges.fire({});
}
}
rpl::producer<> Forum::chatsListChanges() const {
return _chatsListChanges.events();
}
rpl::producer<> Forum::chatsListLoadedEvents() const {
return _chatsListLoadedEvents.events();
}
void ShowAddForumTopic(
not_null<Window::SessionController*> controller,
not_null<ChannelData*> forum) {
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(rpl::single(u"New Topic"_q));
const auto title = box->addRow(
object_ptr<Ui::InputField>(
box,
st::defaultInputField,
rpl::single(u"Topic Title"_q))); // #TODO forum
const auto message = box->addRow(
object_ptr<Ui::InputField>(
box,
st::newGroupDescription,
Ui::InputField::Mode::MultiLine,
rpl::single(u"Message"_q))); // #TODO forum
box->setFocusCallback([=] {
title->setFocusFast();
});
box->addButton(tr::lng_create_group_create(), [=] {
if (!forum->isForum()) {
box->closeBox();
return;
} else if (title->getLastText().trimmed().isEmpty()) {
title->setFocus();
return;
} else if (message->getLastText().trimmed().isEmpty()) {
message->setFocus();
return;
}
const auto randomId = base::RandomValue<uint64>();
const auto api = &forum->session().api();
api->request(MTPchannels_CreateForumTopic(
MTP_flags(0),
forum->inputChannel,
MTP_string(title->getLastText().trimmed()),
MTPInputMedia(),
MTP_string(message->getLastText().trimmed()),
MTP_long(randomId),
MTPVector<MTPMessageEntity>(),
MTPInputPeer() // send_as
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
box->closeBox();
}).fail([=](const MTP::Error &error) {
api->sendMessageFail(error, forum, randomId);
}).send();
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}), Ui::LayerOption::KeepOther);
}
} // namespace Data

View File

@ -7,15 +7,49 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_main_list.h"
class History;
class ChannelData;
namespace Window {
class SessionController;
} // namespace Window;
namespace Data {
class Forum final {
public:
Forum();
explicit Forum(not_null<History*> forum);
~Forum();
[[nodiscard]] not_null<Dialogs::MainList*> topicsList();
void requestTopics();
[[nodiscard]] rpl::producer<> chatsListChanges() const;
[[nodiscard]] rpl::producer<> chatsListLoadedEvents() const;
void topicAdded(not_null<HistoryItem*> root);
private:
const not_null<History*> _forum;
base::flat_map<MsgId, std::unique_ptr<ForumTopic>> _topics;
Dialogs::MainList _topicsList;
mtpRequestId _requestId = 0;
TimeId _offsetDate = 0;
MsgId _offsetId = 0;
MsgId _offsetTopicId = 0;
bool _allLoaded = false;
rpl::event_stream<> _chatsListChanges;
rpl::event_stream<> _chatsListLoadedEvents;
};
void ShowAddForumTopic(
not_null<Window::SessionController*> controller,
not_null<ChannelData*> forum);
} // namespace Data

View File

@ -0,0 +1,340 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_forum_topic.h"
#include "data/data_channel.h"
#include "data/data_forum.h"
#include "data/data_session.h"
#include "dialogs/dialogs_main_list.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "history/history.h"
#include "history/history_item.h"
namespace Data {
ForumTopic::ForumTopic(not_null<History*> forum, MsgId rootId)
: Entry(&forum->owner(), Type::ForumTopic)
, _forum(forum)
, _list(forum->peer->asChannel()->forum()->topicsList())
, _rootId(rootId) {
}
not_null<History*> ForumTopic::forum() const {
return _forum;
}
MsgId ForumTopic::rootId() const {
return _rootId;
}
void ForumTopic::applyTopic(const MTPForumTopic &topic) {
Expects(_rootId == topic.data().vid().v);
const auto &data = topic.data();
const auto title = qs(data.vtitle());
if (_title != title) {
_title = title;
++_titleVersion;
indexTitleParts();
updateChatListEntry();
}
const auto pinned = _list->pinned();
if (data.is_pinned()) {
pinned->addPinned(Dialogs::Key(this));
} else {
pinned->setPinned(Dialogs::Key(this), false);
}
applyTopicFields(
data.vunread_count().v,
data.vread_inbox_max_id().v,
data.vread_outbox_max_id().v);
applyTopicTopMessage(data.vtop_message().v);
//setUnreadMark(data.is_unread_mark());
}
void ForumTopic::indexTitleParts() {
_titleWords.clear();
_titleFirstLetters.clear();
auto toIndexList = QStringList();
auto appendToIndex = [&](const QString &value) {
if (!value.isEmpty()) {
toIndexList.push_back(TextUtilities::RemoveAccents(value));
}
};
appendToIndex(_title);
const auto appendTranslit = !toIndexList.isEmpty()
&& cRussianLetters().match(toIndexList.front()).hasMatch();
if (appendTranslit) {
appendToIndex(translitRusEng(toIndexList.front()));
}
auto toIndex = toIndexList.join(' ');
toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex);
const auto namesList = TextUtilities::PrepareSearchWords(toIndex);
for (const auto &name : namesList) {
_titleWords.insert(name);
_titleFirstLetters.insert(name[0]);
}
}
int ForumTopic::chatListNameVersion() const {
return _titleVersion;
}
void ForumTopic::applyTopicFields(
int unreadCount,
MsgId maxInboxRead,
MsgId maxOutboxRead) {
if (maxInboxRead + 1 >= _inboxReadBefore.value_or(1)) {
setUnreadCount(unreadCount);
setInboxReadTill(maxInboxRead);
}
setOutboxReadTill(maxOutboxRead);
}
void ForumTopic::applyTopicTopMessage(MsgId topMessageId) {
if (topMessageId) {
const auto itemId = FullMsgId(_forum->peer->id, topMessageId);
if (const auto item = owner().message(itemId)) {
setLastServerMessage(item);
} else {
setLastServerMessage(nullptr);
}
} else {
setLastServerMessage(nullptr);
}
}
void ForumTopic::setLastServerMessage(HistoryItem *item) {
_lastServerMessage = item;
if (_lastMessage
&& *_lastMessage
&& !(*_lastMessage)->isRegular()
&& (!item || (*_lastMessage)->date() > item->date())) {
return;
}
setLastMessage(item);
}
void ForumTopic::setLastMessage(HistoryItem *item) {
if (_lastMessage && *_lastMessage == item) {
return;
}
_lastMessage = item;
if (!item || item->isRegular()) {
_lastServerMessage = item;
}
setChatListMessage(item);
}
void ForumTopic::setChatListMessage(HistoryItem *item) {
if (_chatListMessage && *_chatListMessage == item) {
return;
}
const auto was = _chatListMessage.value_or(nullptr);
if (item) {
if (item->isSponsored()) {
return;
}
if (_chatListMessage
&& *_chatListMessage
&& !(*_chatListMessage)->isRegular()
&& (*_chatListMessage)->date() > item->date()) {
return;
}
_chatListMessage = item;
setChatListTimeId(item->date());
#if 0 // #TODO forum
// If we have a single message from a group, request the full album.
if (hasOrphanMediaGroupPart()
&& !item->toPreview({
.hideSender = true,
.hideCaption = true }).images.empty()) {
owner().histories().requestGroupAround(item);
}
#endif
} else if (!_chatListMessage || *_chatListMessage) {
_chatListMessage = nullptr;
updateChatListEntry();
}
}
void ForumTopic::setInboxReadTill(MsgId upTo) {
if (_inboxReadBefore) {
accumulate_max(*_inboxReadBefore, upTo + 1);
} else {
_inboxReadBefore = upTo + 1;
}
}
void ForumTopic::setOutboxReadTill(MsgId upTo) {
if (_outboxReadBefore) {
accumulate_max(*_outboxReadBefore, upTo + 1);
} else {
_outboxReadBefore = upTo + 1;
}
}
void ForumTopic::loadUserpic() {
}
void ForumTopic::paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const {
// #TODO forum
}
void ForumTopic::requestChatListMessage() {
if (!chatListMessageKnown()) {
// #TODO forum
}
}
TimeId ForumTopic::adjustedChatListTimeId() const {
const auto result = chatListTimeId();
#if 0 // #TODO forum
if (const auto draft = cloudDraft()) {
if (!Data::draftIsNull(draft) && !session().supportMode()) {
return std::max(result, draft->date);
}
}
#endif
return result;
}
int ForumTopic::fixedOnTopIndex() const {
return kArchiveFixOnTopIndex;
}
bool ForumTopic::shouldBeInChatList() const {
return isPinnedDialog(FilterId())
|| !lastMessageKnown()
|| (lastMessage() != nullptr);
}
HistoryItem *ForumTopic::lastMessage() const {
return _lastMessage.value_or(nullptr);
}
bool ForumTopic::lastMessageKnown() const {
return _lastMessage.has_value();
}
HistoryItem *ForumTopic::lastServerMessage() const {
return _lastServerMessage.value_or(nullptr);
}
bool ForumTopic::lastServerMessageKnown() const {
return _lastServerMessage.has_value();
}
int ForumTopic::unreadCount() const {
return _unreadCount ? *_unreadCount : 0;
}
int ForumTopic::unreadCountForBadge() const {
const auto result = unreadCount();
return (!result && unreadMark()) ? 1 : result;
}
bool ForumTopic::unreadCountKnown() const {
return _unreadCount.has_value();
}
void ForumTopic::setUnreadCount(int newUnreadCount) {
if (_unreadCount == newUnreadCount) {
return;
}
const auto wasForBadge = (unreadCountForBadge() > 0);
const auto notifier = unreadStateChangeNotifier(true);
_unreadCount = newUnreadCount;
}
void ForumTopic::setUnreadMark(bool unread) {
if (_unreadMark == unread) {
return;
}
const auto noUnreadMessages = !unreadCount();
const auto refresher = gsl::finally([&] {
if (inChatList() && noUnreadMessages) {
updateChatListEntry();
}
});
const auto notifier = unreadStateChangeNotifier(noUnreadMessages);
_unreadMark = unread;
}
bool ForumTopic::unreadMark() const {
return _unreadMark;
}
int ForumTopic::chatListUnreadCount() const {
const auto state = chatListUnreadState();
return state.marks
+ (Core::App().settings().countUnreadMessages()
? state.messages
: state.chats);
}
Dialogs::UnreadState ForumTopic::chatListUnreadState() const {
auto result = Dialogs::UnreadState();
const auto count = _unreadCount.value_or(0);
const auto mark = !count && _unreadMark;
const auto muted = _forum->mute();
result.messages = count;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;
result.chatsMuted = (count && muted) ? 1 : 0;
result.marks = mark ? 1 : 0;
result.marksMuted = (mark && muted) ? 1 : 0;
result.known = _unreadCount.has_value();
return result;
}
bool ForumTopic::chatListUnreadMark() const {
return false;
}
bool ForumTopic::chatListMutedBadge() const {
return true;
}
HistoryItem *ForumTopic::chatListMessage() const {
return _lastMessage.value_or(nullptr);
}
bool ForumTopic::chatListMessageKnown() const {
return _lastMessage.has_value();
}
const QString &ForumTopic::chatListName() const {
return _title;
}
const base::flat_set<QString> &ForumTopic::chatListNameWords() const {
return _titleWords;
}
const base::flat_set<QChar> &ForumTopic::chatListFirstLetters() const {
return _titleFirstLetters;
}
const QString &ForumTopic::chatListNameSortKey() const {
static const auto empty = QString();
return empty;
}
} // namespace Data

View File

@ -0,0 +1,115 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "dialogs/dialogs_entry.h"
class ChannelData;
namespace Dialogs {
class MainList;
} // namespace Dialogs
namespace Main {
class Session;
} // namespace Main
namespace Data {
class Session;
class ForumTopic final : public Dialogs::Entry {
public:
ForumTopic(not_null<History*> forum, MsgId rootId);
ForumTopic(const ForumTopic &) = delete;
ForumTopic &operator=(const ForumTopic &) = delete;
[[nodiscard]] not_null<History*> forum() const;
[[nodiscard]] MsgId rootId() const;
void applyTopic(const MTPForumTopic &topic);
TimeId adjustedChatListTimeId() const override;
int fixedOnTopIndex() const override;
bool shouldBeInChatList() const override;
int chatListUnreadCount() const override;
bool chatListUnreadMark() const override;
bool chatListMutedBadge() const override;
Dialogs::UnreadState chatListUnreadState() const override;
HistoryItem *chatListMessage() const override;
bool chatListMessageKnown() const override;
void requestChatListMessage() override;
const QString &chatListName() const override;
const QString &chatListNameSortKey() const override;
const base::flat_set<QString> &chatListNameWords() const override;
const base::flat_set<QChar> &chatListFirstLetters() const override;
[[nodiscard]] HistoryItem *lastMessage() const;
[[nodiscard]] HistoryItem *lastServerMessage() const;
[[nodiscard]] bool lastMessageKnown() const;
[[nodiscard]] bool lastServerMessageKnown() const;
void loadUserpic() override;
void paintUserpic(
Painter &p,
std::shared_ptr<Data::CloudImageView> &view,
int x,
int y,
int size) const override;
[[nodiscard]] int unreadCount() const;
[[nodiscard]] bool unreadCountKnown() const;
[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
void setUnreadCount(int newUnreadCount);
void setUnreadMark(bool unread);
[[nodiscard]] bool unreadMark() const;
private:
void indexTitleParts();
void applyTopicTopMessage(MsgId topMessageId);
void applyTopicFields(
int unreadCount,
MsgId maxInboxRead,
MsgId maxOutboxRead);
void applyChatListMessage(HistoryItem *item);
void setLastMessage(HistoryItem *item);
void setLastServerMessage(HistoryItem *item);
void setChatListMessage(HistoryItem *item);
void setInboxReadTill(MsgId upTo);
void setOutboxReadTill(MsgId upTo);
int chatListNameVersion() const override;
const not_null<History*> _forum;
const not_null<Dialogs::MainList*> _list;
const MsgId _rootId = 0;
QString _title;
base::flat_set<QString> _titleWords;
base::flat_set<QChar> _titleFirstLetters;
int _titleVersion = 0;
std::optional<MsgId> _inboxReadBefore;
std::optional<MsgId> _outboxReadBefore;
std::optional<int> _unreadCount;
std::optional<HistoryItem*> _lastMessage;
std::optional<HistoryItem*> _lastServerMessage;
std::optional<HistoryItem*> _chatListMessage;
bool _unreadMark = false;
rpl::lifetime _lifetime;
};
} // namespace Data

View File

@ -805,7 +805,12 @@ 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

@ -239,52 +239,53 @@ enum class MessageFlag : uint64 {
MentionsMe = (1ULL << 15),
IsOrWasScheduled = (1ULL << 16),
NoForwards = (1ULL << 17),
TopicStart = (1ULL << 18),
// Needs to return back to inline mode.
HasSwitchInlineButton = (1ULL << 18),
HasSwitchInlineButton = (1ULL << 19),
// For "shared links" indexing.
HasTextLinks = (1ULL << 19),
HasTextLinks = (1ULL << 20),
// Group / channel create or migrate service message.
IsGroupEssential = (1ULL << 20),
IsGroupEssential = (1ULL << 21),
// Edited media is generated on the client
// and should not update media from server.
IsLocalUpdateMedia = (1ULL << 21),
IsLocalUpdateMedia = (1ULL << 22),
// Sent from inline bot, need to re-set media when sent.
FromInlineBot = (1ULL << 22),
FromInlineBot = (1ULL << 23),
// Generated on the client side and should be unread.
ClientSideUnread = (1ULL << 23),
ClientSideUnread = (1ULL << 24),
// In a supergroup.
HasAdminBadge = (1ULL << 24),
HasAdminBadge = (1ULL << 25),
// Outgoing message that is being sent.
BeingSent = (1ULL << 25),
BeingSent = (1ULL << 26),
// Outgoing message and failed to be sent.
SendingFailed = (1ULL << 26),
SendingFailed = (1ULL << 27),
// No media and only a several emoji or an only custom emoji text.
SpecialOnlyEmoji = (1ULL << 27),
SpecialOnlyEmoji = (1ULL << 28),
// Message existing in the message history.
HistoryEntry = (1ULL << 28),
HistoryEntry = (1ULL << 29),
// Local message, not existing on the server.
Local = (1ULL << 29),
Local = (1ULL << 30),
// Fake message for some UI element.
FakeHistoryItem = (1ULL << 30),
FakeHistoryItem = (1ULL << 31),
// Contact sign-up message, notification should be skipped for Silent.
IsContactSignUp = (1ULL << 31),
IsContactSignUp = (1ULL << 32),
// Optimization for item text custom emoji repainting.
CustomEmojiRepainting = (1ULL << 32),
CustomEmojiRepainting = (1ULL << 33),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>;

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_session.h"
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_chat_filters.h"
#include "mainwidget.h"
#include "main/main_session.h"
@ -45,7 +46,7 @@ uint64 PinnedDialogPos(int pinnedIndex) {
Entry::Entry(not_null<Data::Session*> owner, Type type)
: _owner(owner)
, _isFolder(type == Type::Folder) {
, _type(type) {
}
Data::Session &Entry::owner() const {
@ -57,11 +58,19 @@ Main::Session &Entry::session() const {
}
History *Entry::asHistory() {
return _isFolder ? nullptr : static_cast<History*>(this);
return (_type == Type::History) ? static_cast<History*>(this) : nullptr;
}
Data::Folder *Entry::asFolder() {
return _isFolder ? static_cast<Data::Folder*>(this) : nullptr;
return (_type == Type::Folder)
? static_cast<Data::Folder*>(this)
: nullptr;
}
Data::ForumTopic *Entry::asForumTopic() {
return (_type == Type::ForumTopic)
? static_cast<Data::ForumTopic*>(this)
: nullptr;
}
void Entry::pinnedIndexChanged(FilterId filterId, int was, int now) {
@ -177,6 +186,9 @@ const Ui::Text::String &Entry::chatListNameText() const {
}
void Entry::setChatListExistence(bool exists) {
if (asForumTopic()) {
return;
}
if (exists && _sortKeyInChatList) {
owner().refreshChatListEntry(this);
updateChatListEntry();
@ -251,7 +263,7 @@ not_null<Row*> Entry::addToChatList(
void Entry::removeFromChatList(
FilterId filterId,
not_null<MainList*> list) {
if (isPinnedDialog(filterId)) {
if (!asForumTopic() && isPinnedDialog(filterId)) {
owner().setChatPinned(this, filterId, false);
}

View File

@ -19,6 +19,7 @@ class Session;
namespace Data {
class Session;
class Folder;
class ForumTopic;
class CloudImageView;
} // namespace Data
@ -91,9 +92,10 @@ inline UnreadState operator-(const UnreadState &a, const UnreadState &b) {
class Entry {
public:
enum class Type {
enum class Type : uchar {
History,
Folder,
ForumTopic,
};
Entry(not_null<Data::Session*> owner, Type type);
Entry(const Entry &other) = delete;
@ -105,6 +107,7 @@ public:
History *asHistory();
Data::Folder *asFolder();
Data::ForumTopic *asForumTopic();
PositionChange adjustByPosInChatList(
FilterId filterId,
@ -230,7 +233,7 @@ private:
mutable int _chatListNameVersion = 0;
TimeId _timeId = 0;
bool _isTopPromoted = false;
const bool _isFolder = false;
const Type _type;
};

View File

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
#include "data/data_drafts.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@ -409,6 +410,17 @@ void InnerWidget::changeOpenedForum(ChannelData *forum) {
stopReorderPinned();
clearSelection();
_openedForum = forum;
_openedForumLifetime.destroy();
if (forum) {
rpl::merge(
forum->forum()->chatsListChanges(),
forum->forum()->chatsListLoadedEvents()
) | rpl::start_with_next([=] {
refresh();
}, _openedForumLifetime);
}
refreshWithCollapsedRows(true);
if (_loadMoreCallback) {
_loadMoreCallback();
@ -1778,6 +1790,8 @@ void InnerWidget::updateSelectedRow(Key key) {
not_null<IndexedList*> InnerWidget::shownDialogs() const {
return _filterId
? session().data().chatsFilters().chatsList(_filterId)->indexed()
: _openedForum
? _openedForum->forum()->topicsList()->indexed()
: session().data().chatsList(_openedFolder)->indexed();
}
@ -2346,6 +2360,8 @@ void InnerWidget::refreshEmptyLabel() {
const auto data = &session().data();
const auto state = !shownDialogs()->empty()
? EmptyState::None
: _openedForum
? EmptyState::EmptyForum
: (!_filterId && data->contactsLoaded().current())
? EmptyState::NoContacts
: (_filterId > 0) && data->chatsList()->loaded()
@ -2364,11 +2380,17 @@ void InnerWidget::refreshEmptyLabel() {
? tr::lng_no_chats()
: (state == EmptyState::EmptyFolder)
? tr::lng_no_chats_filter()
: (state == EmptyState::EmptyForum)
// #TODO forum
? rpl::single(u"No chats currently created in this forum."_q)
: tr::lng_contacts_loading();
auto link = (state == EmptyState::NoContacts)
? tr::lng_add_contact_button()
: (state == EmptyState::EmptyFolder)
? tr::lng_filters_context_edit()
: (state == EmptyState::EmptyForum)
// #TODO forum
? rpl::single(u"Create topic"_q)
: rpl::single(QString());
auto full = rpl::combine(
std::move(phrase),
@ -2387,6 +2409,8 @@ void InnerWidget::refreshEmptyLabel() {
_controller->showAddContact();
} else if (_emptyState == EmptyState::EmptyFolder) {
editOpenedFilter();
} else if (_emptyState == EmptyState::EmptyForum) {
Data::ShowAddForumTopic(_controller, _openedForum);
}
});
_empty->setVisible(_state == WidgetState::Default);
@ -3261,9 +3285,13 @@ void InnerWidget::setupShortcuts() {
return jumpToDialogRow(last);
});
request->check(Command::ChatSelf) && request->handle([=] {
_controller->content()->choosePeer(
session().userPeerId(),
ShowAtUnreadMsgId);
if (_openedForum) {
Data::ShowAddForumTopic(_controller, _openedForum);
} else {
_controller->content()->choosePeer(
session().userPeerId(),
ShowAtUnreadMsgId);
}
return true;
});
request->check(Command::ShowArchive) && request->handle([=] {

View File

@ -331,7 +331,7 @@ private:
void clearSearchResults(bool clearPeerSearchResults = true);
void updateSelectedRow(Key key = Key());
not_null<IndexedList*> shownDialogs() const;
[[nodiscard]] not_null<IndexedList*> shownDialogs() const;
void checkReorderPinnedStart(QPoint localPosition);
int updateReorderIndexGetCount();
@ -343,7 +343,7 @@ private:
bool pinnedShiftAnimationCallback(crl::time now);
void handleChatListEntryRefreshes();
not_null<Window::SessionController*> _controller;
const not_null<Window::SessionController*> _controller;
FilterId _filterId = 0;
bool _mouseSelection = false;
@ -352,6 +352,7 @@ private:
Data::Folder *_openedFolder = nullptr;
ChannelData *_openedForum = nullptr;
rpl::lifetime _openedForumLifetime;
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
int _collapsedSelected = -1;

View File

@ -8,12 +8,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_key.h"
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "history/history.h"
namespace Dialogs {
namespace {
using Folder = Data::Folder;
using ForumTopic = Data::ForumTopic;
} // namespace
@ -23,12 +25,18 @@ Key::Key(History *history) : _value(history) {
Key::Key(Data::Folder *folder) : _value(folder) {
}
Key::Key(Data::ForumTopic *forumTopic) : _value(forumTopic) {
}
Key::Key(not_null<History*> history) : _value(history) {
}
Key::Key(not_null<Data::Folder*> folder) : _value(folder) {
}
Key::Key(not_null<Data::ForumTopic*> forumTopic) : _value(forumTopic) {
}
not_null<Entry*> Key::entry() const {
Expects(_value != nullptr);
@ -43,6 +51,10 @@ Folder *Key::folder() const {
return _value ? _value->asFolder() : nullptr;
}
ForumTopic *Key::forumTopic() const {
return _value ? _value->asForumTopic() : nullptr;
}
PeerData *Key::peer() const {
if (const auto history = this->history()) {
return history->peer;

View File

@ -14,6 +14,7 @@ class PeerData;
namespace Data {
class Folder;
class ForumTopic;
} // namespace Data
namespace Dialogs {
@ -27,10 +28,12 @@ public:
}
Key(History *history);
Key(Data::Folder *folder);
Key(Data::ForumTopic *forumTopic);
Key(not_null<Entry*> entry) : _value(entry) {
}
Key(not_null<History*> history);
Key(not_null<Data::Folder*> folder);
Key(not_null<Data::ForumTopic*> forumTopic);
explicit operator bool() const {
return (_value != nullptr);
@ -38,6 +41,7 @@ public:
not_null<Entry*> entry() const;
History *history() const;
Data::Folder *folder() const;
Data::ForumTopic *forumTopic() const;
PeerData *peer() const;
inline bool operator<(const Key &other) const {

View File

@ -46,6 +46,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_histories.h"
#include "data/data_changes.h"
#include "data/data_download_manager.h"
@ -261,7 +263,11 @@ Widget::Widget(
const auto openSearchResult = !controller->selectingPeer()
&& row.filteredRow;
const auto history = row.key.history();
if (history && history->peer->isForum()) {
if (const auto forumTopic = row.key.forumTopic()) {
controller->showRepliesForMessage(
forumTopic->forum(),
forumTopic->rootId());
} else if (history && history->peer->isForum()) {
controller->openForum(history->peer->asChannel());
} else if (history) {
const auto peer = history->peer;
@ -372,6 +378,8 @@ Widget::Widget(
&& _searchFull
&& !_searchFullMigrated))) {
searchMore();
} else if (_openedForum) {
_openedForum->forum()->requestTopics();
} else {
const auto folder = _inner->shownFolder();
if (!folder || !folder->chatsList()->loaded()) {

View File

@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_sponsored_messages.h"
#include "data/data_send_action.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_photo.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
@ -1123,6 +1124,11 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (!folderKnown()) {
owner().histories().requestDialogEntry(this);
}
if (item->isTopicStart() && peer->isForum()) {
if (const auto forum = peer->asChannel()->forum()) {
forum->topicAdded(item);
}
}
}
void History::registerClientSideMessage(not_null<HistoryItem*> item) {
@ -2019,12 +2025,13 @@ Dialogs::UnreadState History::chatListUnreadState() const {
auto result = Dialogs::UnreadState();
const auto count = _unreadCount.value_or(0);
const auto mark = !count && _unreadMark;
const auto muted = mute();
result.messages = count;
result.messagesMuted = mute() ? count : 0;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;
result.chatsMuted = (count && mute()) ? 1 : 0;
result.chatsMuted = (count && muted) ? 1 : 0;
result.marks = mark ? 1 : 0;
result.marksMuted = (mark && mute()) ? 1 : 0;
result.marksMuted = (mark && muted) ? 1 : 0;
result.known = _unreadCount.has_value();
return result;
}

View File

@ -1424,7 +1424,8 @@ MessageFlags FlagsFromMTP(
| ((flags & MTP::f_reply_markup) ? Flag::HasReplyMarkup : Flag())
| ((flags & MTP::f_from_scheduled) ? Flag::IsOrWasScheduled : Flag())
| ((flags & MTP::f_views) ? Flag::HasViews : Flag())
| ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag());
| ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag())
| ((flags & MTP::f_topic_start) ? Flag::TopicStart : Flag());
}
MessageFlags FlagsFromMTP(

View File

@ -145,6 +145,9 @@ public:
[[nodiscard]] bool isPinned() const {
return _flags & MessageFlag::Pinned;
}
[[nodiscard]] bool isTopicStart() const {
return _flags & MessageFlag::TopicStart;
}
[[nodiscard]] bool unread() const;
[[nodiscard]] bool showNotification() const;
void markClientSideAsRead();