Update API scheme to layer 148, start forums.

This commit is contained in:
John Preston 2022-09-20 13:35:47 +04:00
parent 87b228b256
commit c88140e256
25 changed files with 349 additions and 40 deletions

View File

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

View File

@ -125,7 +125,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#29562865 id:long = Chat;
chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
chatForbidden#6592a1a7 id:long title:string = Chat;
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions = ChatFull;
@ -142,7 +142,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;
message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true topic_start:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;
messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message;
messageMediaEmpty#3ded6320 = MessageMedia;
@ -192,6 +192,7 @@ messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction;
messageActionTopicCreate#4619708d title:string = MessageAction;
dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@ -591,7 +592,7 @@ inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet;
stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector<PhotoSize> thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet;
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
messages.stickerSet#6e153f16 set:StickerSet packs:Vector<StickerPack> keywords:Vector<StickerKeyword> documents:Vector<Document> = messages.StickerSet;
messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
botCommand#c27ac8c7 command:string description:string = BotCommand;
@ -770,7 +771,7 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector<StickerSetCovered>
stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered;
stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector<Document> = StickerSetCovered;
stickerSetFullCovered#1aed5ee5 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = StickerSetCovered;
stickerSetFullCovered#40d13c0e set:StickerSet packs:Vector<StickerPack> keywords:Vector<StickerKeyword> documents:Vector<Document> = StickerSetCovered;
maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;
@ -1248,7 +1249,7 @@ messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> use
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
@ -1448,6 +1449,12 @@ sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer
messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int thumb:flags.1?PhotoSize video_duration:flags.2?int = MessageExtendedMedia;
messageExtendedMedia#ee479c64 media:MessageMedia = MessageExtendedMedia;
stickerKeyword#fcfeb29c document_id:long keyword:Vector<string> = StickerKeyword;
forumTopic#4a0005d9 flags:# pinned:flags.2?true id:int date:int title:string top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int = ForumTopic;
messages.forumTopics#ed93d3e flags:# count:int topics:Vector<ForumTopic> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> pts:int next_date:flags.0?int = messages.ForumTopics;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1846,6 +1853,9 @@ channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory;
channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
channels.createForumTopic#22cf4868 flags:# no_webpage:flags.3?true channel:InputChannel title:string media:flags.0?InputMedia message:string random_id:long entities:flags.1?Vector<MessageEntity> send_as:flags.2?InputPeer = Updates;
channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@ -1924,4 +1934,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 146
// LAYER 148

View File

@ -95,6 +95,7 @@ public:
Group,
Channel,
Megagroup,
Forum,
};
GroupInfoBox(
QWidget*,

View File

@ -266,6 +266,7 @@ private:
std::optional<QString> title;
std::optional<QString> description;
std::optional<bool> hiddenPreHistory;
std::optional<bool> forum;
std::optional<bool> signatures;
std::optional<bool> noForwards;
std::optional<bool> joinToWrite;
@ -289,6 +290,7 @@ private:
void fillPrivacyTypeButton();
void fillLinkedChatButton();
//void fillInviteLinkButton();
void fillForumButton();
void fillSignaturesButton();
void fillHistoryVisibilityButton();
void fillManageSection();
@ -305,6 +307,7 @@ private:
[[nodiscard]] bool validateTitle(Saving &to) const;
[[nodiscard]] bool validateDescription(Saving &to) const;
[[nodiscard]] bool validateHistoryVisibility(Saving &to) const;
[[nodiscard]] bool validateForum(Saving &to) const;
[[nodiscard]] bool validateSignatures(Saving &to) const;
[[nodiscard]] bool validateForwards(Saving &to) const;
[[nodiscard]] bool validateJoinToWrite(Saving &to) const;
@ -316,6 +319,7 @@ private:
void saveTitle();
void saveDescription();
void saveHistoryVisibility();
void saveForum();
void saveSignatures();
void saveForwards();
void saveJoinToWrite();
@ -339,6 +343,7 @@ private:
bool _channelHasLocationOriginalValue = false;
std::optional<HistoryVisibility> _historyVisibilitySavedValue;
std::optional<EditPeerTypeData> _typeDataSavedValue;
std::optional<bool> _forumSavedValue;
std::optional<bool> _signaturesSavedValue;
const not_null<Window::SessionNavigation*> _navigation;
@ -800,6 +805,27 @@ void Controller::fillLinkedChatButton() {
// buttonCallback);
//}
void Controller::fillForumButton() {
Expects(_controls.buttonsLayout != nullptr);
const auto channel = _peer->asChannel();
if (!channel) {
return;
}
AddButtonWithText(
_controls.buttonsLayout,
rpl::single(u"Forum"_q), // #TODO forum
rpl::single(QString()),
[] {},
{ &st::settingsIconGroup, Settings::kIconPurple }
)->toggleOn(rpl::single(channel->isForum())
)->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
_forumSavedValue = toggled;
}, _controls.buttonsLayout->lifetime());
}
void Controller::fillSignaturesButton() {
Expects(_controls.buttonsLayout != nullptr);
@ -907,6 +933,9 @@ void Controller::fillManageSection() {
? channel->canEditPreHistoryHidden()
: chat->canEditPreHistoryHidden();
}();
const auto canEditForum = isChannel
&& channel->isMegagroup()
&& channel->canEditInformation();
const auto canEditPermissions = [&] {
return isChannel
@ -972,10 +1001,14 @@ void Controller::fillManageSection() {
if (canEditPreHistoryHidden) {
fillHistoryVisibilityButton();
}
if (canEditForum) {
fillForumButton();
}
if (canEditSignatures) {
fillSignaturesButton();
}
if (canEditPreHistoryHidden
|| canEditForum
|| canEditSignatures
//|| canEditInviteLinks
|| canViewOrEditLinkedChat
@ -1235,6 +1268,7 @@ std::optional<Controller::Saving> Controller::validate() const {
&& validateTitle(result)
&& validateDescription(result)
&& validateHistoryVisibility(result)
&& validateForum(result)
&& validateSignatures(result)
&& validateForwards(result)
&& validateJoinToWrite(result)
@ -1302,6 +1336,14 @@ bool Controller::validateHistoryVisibility(Saving &to) const {
return true;
}
bool Controller::validateForum(Saving &to) const {
if (!_forumSavedValue.has_value()) {
return true;
}
to.forum = _forumSavedValue;
return true;
}
bool Controller::validateSignatures(Saving &to) const {
if (!_signaturesSavedValue.has_value()) {
return true;
@ -1347,6 +1389,7 @@ void Controller::save() {
pushSaveStage([=] { saveTitle(); });
pushSaveStage([=] { saveDescription(); });
pushSaveStage([=] { saveHistoryVisibility(); });
pushSaveStage([=] { saveForum(); });
pushSaveStage([=] { saveSignatures(); });
pushSaveStage([=] { saveForwards(); });
pushSaveStage([=] { saveJoinToWrite(); });
@ -1585,6 +1628,28 @@ void Controller::togglePreHistoryHidden(
}).send();
}
void Controller::saveForum() {
const auto channel = _peer->asChannel();
if (!_savingData.forum
|| !channel
|| *_savingData.forum == channel->isForum()) {
return continueSave();
}
_api.request(MTPchannels_ToggleForum(
channel->inputChannel,
MTP_bool(*_savingData.forum)
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
continueSave();
}).fail([=](const MTP::Error &error) {
if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
continueSave();
} else {
cancelSave();
}
}).send();
}
void Controller::saveSignatures() {
const auto channel = _peer->asChannel();
if (!_savingData.signatures

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_session.h"
#include "data/data_folder.h"
#include "data/data_forum.h"
#include "data/data_location.h"
#include "data/data_histories.h"
#include "data/data_group_call.h"
@ -34,6 +35,10 @@ using UpdateFlag = Data::PeerUpdate::Flag;
} // namespace
MegagroupInfo::MegagroupInfo() = default;
MegagroupInfo::~MegagroupInfo() = default;
ChatData *MegagroupInfo::getMigrateFromChat() const {
return _migratedFrom;
}
@ -55,6 +60,20 @@ Data::ChatBotCommands::Changed MegagroupInfo::setBotCommands(
return _botCommands.update(list);
}
void MegagroupInfo::setIsForum(bool is) {
if (is == (_forum != nullptr)) {
return;
} else if (is) {
_forum = std::make_unique<Data::Forum>();
} else {
_forum = nullptr;
}
}
Data::Forum *MegagroupInfo::forum() const {
return _forum.get();
}
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
: PeerData(owner, id)
, inputChannel(
@ -78,6 +97,10 @@ 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

@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat_participant_status.h"
#include "data/data_peer_bot_commands.h"
namespace Data {
class Forum;
} // namespace Data
struct ChannelLocation {
QString address;
Data::LocationPoint point;
@ -55,12 +59,16 @@ enum class ChannelDataFlag {
NoForwards = (1 << 20),
JoinToWrite = (1 << 21),
RequestToJoin = (1 << 22),
Forum = (1 << 23),
};
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>;
class MegagroupInfo {
public:
MegagroupInfo();
~MegagroupInfo();
struct Admin {
explicit Admin(ChatAdminRightsInfo rights)
: rights(rights) {
@ -92,6 +100,9 @@ public:
return _botCommands;
}
void setIsForum(bool is);
[[nodiscard]] Data::Forum *forum() const;
std::deque<not_null<UserData*>> lastParticipants;
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
@ -119,10 +130,11 @@ private:
ChatData *_migratedFrom = nullptr;
ChannelLocation _location;
Data::ChatBotCommands _botCommands;
std::unique_ptr<Data::Forum> _forum;
};
class ChannelData : public PeerData {
class ChannelData final : public PeerData {
public:
using Flag = ChannelDataFlag;
using Flags = Data::Flags<ChannelDataFlags>;
@ -243,6 +255,9 @@ public:
[[nodiscard]] bool isGigagroup() const {
return flags() & Flag::Gigagroup;
}
[[nodiscard]] bool isForum() const {
return flags() & Flag::Forum;
}
[[nodiscard]] bool hasUsername() const {
return flags() & Flag::Username;
}
@ -420,6 +435,10 @@ public:
void setAllowedReactions(Data::AllowedReactions value);
[[nodiscard]] const Data::AllowedReactions &allowedReactions() const;
[[nodiscard]] Data::Forum *forum() const {
return mgInfo ? mgInfo->forum() : nullptr;
}
// Still public data members.
uint64 access = 0;

View File

@ -27,7 +27,7 @@ enum class ChatDataFlag {
inline constexpr bool is_flag_type(ChatDataFlag) { return true; };
using ChatDataFlags = base::flags<ChatDataFlag>;
class ChatData : public PeerData {
class ChatData final : public PeerData {
public:
using Flag = ChatDataFlag;
using Flags = Data::Flags<ChatDataFlags>;

View File

@ -0,0 +1,16 @@
/*
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.h"
namespace Data {
Forum::Forum() = default;
Forum::~Forum() = default;
} // namespace Data

View File

@ -0,0 +1,21 @@
/*
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
namespace Data {
class Forum final {
public:
Forum();
~Forum();
private:
};
} // namespace Data

View File

@ -844,15 +844,31 @@ bool PeerData::isFake() const {
}
bool PeerData::isMegagroup() const {
return isChannel() && asChannel()->isMegagroup();
if (const auto channel = asChannel()) {
return channel->isMegagroup();
}
return false;
}
bool PeerData::isBroadcast() const {
return isChannel() && asChannel()->isBroadcast();
if (const auto channel = asChannel()) {
return channel->isBroadcast();
}
return false;
}
bool PeerData::isForum() const {
if (const auto channel = asChannel()) {
return channel->isForum();
}
return false;
}
bool PeerData::isGigagroup() const {
return isChannel() && asChannel()->isGigagroup();
if (const auto channel = asChannel()) {
return channel->isGigagroup();
}
return false;
}
bool PeerData::isRepliesChat() const {

View File

@ -180,6 +180,7 @@ public:
[[nodiscard]] bool isFake() const;
[[nodiscard]] bool isMegagroup() const;
[[nodiscard]] bool isBroadcast() const;
[[nodiscard]] bool isForum() const;
[[nodiscard]] bool isGigagroup() const;
[[nodiscard]] bool isRepliesChat() const;
[[nodiscard]] bool sharedMediaInfo() const {

View File

@ -777,7 +777,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
| (!minimal ? (Flag::Left | Flag::Creator) : Flag())
| Flag::NoForwards
| Flag::JoinToWrite
| Flag::RequestToJoin;
| Flag::RequestToJoin
| Flag::Forum;
const auto flagsSet = (data.is_broadcast() ? Flag::Broadcast : Flag())
| (data.is_verified() ? Flag::Verified : Flag())
| (data.is_scam() ? Flag::Scam : Flag())
@ -800,7 +801,10 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
: Flag())
| (data.is_noforwards() ? Flag::NoForwards : Flag())
| (data.is_join_to_send() ? Flag::JoinToWrite : Flag())
| (data.is_join_request() ? Flag::RequestToJoin : Flag());
| (data.is_join_request() ? Flag::RequestToJoin : Flag())
| ((data.is_forum() && data.is_megagroup())
? Flag::Forum
: Flag());
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
channel->setName(

View File

@ -57,7 +57,7 @@ enum class UserDataFlag {
inline constexpr bool is_flag_type(UserDataFlag) { return true; };
using UserDataFlags = base::flags<UserDataFlag>;
class UserData : public PeerData {
class UserData final : public PeerData {
public:
using Flag = UserDataFlag;
using Flags = Data::Flags<UserDataFlags>;

View File

@ -394,15 +394,22 @@ void InnerWidget::changeOpenedFolder(Data::Folder *folder) {
return;
}
stopReorderPinned();
//const auto mouseSelection = _mouseSelection;
//const auto lastMousePosition = _lastMousePosition;
clearSelection();
_openedFolder = folder;
refreshWithCollapsedRows(true);
// This doesn't work, because we clear selection in leaveEvent on hide.
//if (mouseSelection && lastMousePosition) {
// selectByMouse(*lastMousePosition);
//}
if (_loadMoreCallback) {
_loadMoreCallback();
}
}
void InnerWidget::changeOpenedForum(ChannelData *forum) {
if (_openedForum == forum) {
return;
}
stopReorderPinned();
clearSelection();
_openedForum = forum;
refreshWithCollapsedRows(true);
if (_loadMoreCallback) {
_loadMoreCallback();
}
@ -1160,6 +1167,8 @@ void InnerWidget::checkReorderPinnedStart(QPoint localPosition) {
} else if (qAbs(localPosition.y() - _dragStart.y())
< style::ConvertScale(kStartReorderThreshold)) {
return;
} else if (_openedForum) {
return; // #TODO forum
}
_dragging = _pressed;
if (updateReorderIndexGetCount() < 2) {
@ -1311,7 +1320,7 @@ bool InnerWidget::updateReorderPinned(QPoint localPosition) {
const auto delta = [&] {
if (localPosition.y() < _visibleTop) {
return localPosition.y() - _visibleTop;
} else if ((_openedFolder || _filterId)
} else if ((_openedFolder || _openedForum || _filterId)
&& localPosition.y() > _visibleBottom) {
return localPosition.y() - _visibleBottom;
}
@ -2273,6 +2282,10 @@ Data::Folder *InnerWidget::shownFolder() const {
return _openedFolder;
}
ChannelData *InnerWidget::shownForum() const {
return _openedForum;
}
bool InnerWidget::needCollapsedRowsRefresh() const {
const auto list = shownDialogs();
const auto archive = !list->empty()

View File

@ -94,6 +94,7 @@ public:
void clearSelection();
void changeOpenedFolder(Data::Folder *folder);
void changeOpenedForum(ChannelData *forum);
void selectSkip(int32 direction);
void selectSkipPage(int32 pixels, int32 direction);
@ -109,6 +110,7 @@ public:
void scrollToEntry(const RowDescriptor &entry);
Data::Folder *shownFolder() const;
ChannelData *shownForum() const;
int32 lastSearchDate() const;
PeerData *lastSearchPeer() const;
MsgId lastSearchId() const;
@ -179,6 +181,7 @@ private:
Loading,
NoContacts,
EmptyFolder,
EmptyForum,
};
Main::Session &session() const;
@ -348,6 +351,7 @@ private:
Qt::MouseButton _pressButton = Qt::LeftButton;
Data::Folder *_openedFolder = nullptr;
ChannelData *_openedForum = nullptr;
std::vector<std::unique_ptr<CollapsedRow>> _collapsedRows;
int _collapsedSelected = -1;

View File

@ -260,7 +260,10 @@ Widget::Widget(
) | rpl::start_with_next([=](const ChosenRow &row) {
const auto openSearchResult = !controller->selectingPeer()
&& row.filteredRow;
if (const auto history = row.key.history()) {
const auto history = row.key.history();
if (history && history->peer->isForum()) {
controller->openForum(history->peer->asChannel());
} else if (history) {
const auto peer = history->peer;
const auto showAtMsgId = controller->uniqueChatsInSearchResults()
? ShowAtUnreadMsgId
@ -399,6 +402,15 @@ Widget::Widget(
changeOpenedFolder(folder, anim::type::normal);
}, lifetime());
changeOpenedForum(
controller->openedForum().current(),
anim::type::instant);
controller->openedForum().changes(
) | rpl::start_with_next([=](ChannelData *forum) {
changeOpenedForum(forum, anim::type::normal);
}, lifetime());
setupDownloadBar();
}
@ -597,14 +609,14 @@ void Widget::updateControlsVisibility(bool fast) {
if (_forwardCancel) {
_forwardCancel->show();
}
if (_openedFolder && _filter->hasFocus()) {
if ((_openedFolder || _openedForum) && _filter->hasFocus()) {
setFocus();
}
if (_updateTelegram) {
_updateTelegram->show();
}
_searchControls->setVisible(!_openedFolder);
if (_openedFolder) {
_searchControls->setVisible(!_openedFolder && !_openedForum);
if (_openedFolder || _openedForum) {
_folderTopBar->show();
} else {
if (hasFocus()) {
@ -618,24 +630,26 @@ void Widget::updateControlsVisibility(bool fast) {
_connecting->setForceHidden(false);
}
void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
void Widget::changeOpenedSubsection(
FnMut<void()> change,
bool fromRight,
anim::type animated) {
_a_show.stop();
if (isHidden()) {
animated = anim::type::instant;
}
if (animated == anim::type::normal) {
_showDirection = folder
_showDirection = fromRight
? Window::SlideDirection::FromRight
: Window::SlideDirection::FromLeft;
_showAnimationType = ShowAnimation::Internal;
_connecting->setForceHidden(true);
_cacheUnder = grabForFolderSlideAnimation();
}
_openedFolder = folder;
change();
refreshFolderTopBar();
updateControlsVisibility(true);
_inner->changeOpenedFolder(folder);
if (animated == anim::type::normal) {
_connecting->setForceHidden(true);
_cacheOver = grabForFolderSlideAnimation();
@ -644,15 +658,31 @@ void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
}
}
void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
changeOpenedSubsection([&] {
_openedFolder = folder;
_inner->changeOpenedFolder(folder);
}, (folder != nullptr), animated);
}
void Widget::changeOpenedForum(ChannelData *forum, anim::type animated) {
changeOpenedSubsection([&] {
_openedForum = forum;
_inner->changeOpenedForum(forum);
}, (forum != nullptr), animated);
}
void Widget::refreshFolderTopBar() {
if (_openedFolder) {
if (_openedFolder || _openedForum) {
if (!_folderTopBar) {
_folderTopBar.create(this, controller());
updateControlsGeometry();
}
_folderTopBar->setActiveChat(
HistoryView::TopBarWidget::ActiveChat{
.key = _openedFolder,
.key = (_openedFolder
? Dialogs::Key(_openedFolder)
: Dialogs::Key(session().data().history(_openedForum))),
.section = Dialogs::EntryState::Section::ChatsList,
},
nullptr);
@ -713,7 +743,7 @@ void Widget::checkUpdateStatus() {
}
void Widget::setInnerFocus() {
if (_openedFolder) {
if (_openedFolder || _openedForum) {
setFocus();
} else {
_filter->setFocus();
@ -853,6 +883,8 @@ void Widget::animationCallback() {
void Widget::escape() {
if (controller()->openedFolder().current()) {
controller()->closeFolder();
} else if (controller()->openedForum().current()) {
controller()->closeForum();
} else if (!cancelSearch()) {
if (controller()->activeChatEntryCurrent().key) {
controller()->content()->dialogsCancelled();
@ -1633,6 +1665,7 @@ void Widget::updateLoadMoreChatsVisibility() {
return;
}
const auto hidden = (_openedFolder != nullptr)
|| (_openedForum != nullptr)
|| !_filter->getLastText().isEmpty();
if (_loadMoreChats->isHidden() != hidden) {
_loadMoreChats->setVisible(!hidden);
@ -1778,6 +1811,8 @@ void Widget::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Escape) {
if (_openedFolder) {
controller()->closeFolder();
} else if (_openedForum) {
controller()->closeForum();
} else {
e->ignore();
}

View File

@ -153,7 +153,12 @@ private:
void updateControlsGeometry();
void refreshFolderTopBar();
void checkUpdateStatus();
void changeOpenedSubsection(
FnMut<void()> change,
bool fromRight,
anim::type animated);
void changeOpenedFolder(Data::Folder *folder, anim::type animated);
void changeOpenedForum(ChannelData *forum, anim::type animated);
QPixmap grabForFolderSlideAnimation();
void startSlideAnimation();
@ -209,6 +214,7 @@ private:
object_ptr<Ui::HistoryDownButton> _scrollToTop;
Data::Folder *_openedFolder = nullptr;
ChannelData *_openedForum = nullptr;
Dialogs::Key _searchInChat;
History *_searchInMigrated = nullptr;
PeerData *_searchFromAuthor = nullptr;

View File

@ -1152,6 +1152,10 @@ ServiceAction ParseServiceAction(
qs(data.vcurrency())).toUtf8();
content.months = data.vmonths().v;
result.content = content;
}, [&](const MTPDmessageActionTopicCreate &data) {
auto content = ActionTopicCreated();
content.title = ParseString(data.vtitle());
result.content = content;
}, [](const MTPDmessageActionEmpty &data) {});
return result;
}

View File

@ -499,6 +499,10 @@ struct ActionGiftPremium {
int months;
};
struct ActionTopicCreated {
Utf8String title;
};
struct ServiceAction {
std::variant<
v::null_t,
@ -531,7 +535,8 @@ struct ServiceAction {
ActionSetChatTheme,
ActionChatJoinedByRequest,
ActionWebViewDataSent,
ActionGiftPremium> content;
ActionGiftPremium,
ActionTopicCreated> content;
};
ServiceAction ParseServiceAction(

View File

@ -1137,13 +1137,19 @@ auto HtmlWriter::Wrap::pushMessage(
+ "&raquo; button to the bot";
}, [&](const ActionGiftPremium &data) {
if (!data.months || data.cost.isEmpty()) {
return (serviceFrom + " sent you a gift.");
return serviceFrom + " sent you a gift.";
}
return (serviceFrom
return serviceFrom
+ " sent you a gift for "
+ data.cost
+ ": Telegram Premium for "
+ QString::number(data.months).toUtf8() + " months.");
+ QString::number(data.months).toUtf8()
+ " months.";
}, [&](const ActionTopicCreated &data) {
return serviceFrom
+ " created topic &laquo;"
+ SerializeString(data.title)
+ "&raquo;";
}, [](v::null_t) { return QByteArray(); });
if (!serviceText.isEmpty()) {

View File

@ -560,6 +560,10 @@ QByteArray SerializeMessage(
if (data.months) {
push("months", data.months);
}
}, [&](const ActionTopicCreated &data) {
pushActor();
pushAction("topic_created");
push("title", data.title);
}, [](v::null_t) {});
if (v::is_null(message.action.content)) {

View File

@ -633,6 +633,12 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result;
};
auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) {
auto result = PreparedText{};
result.text = { "topic created" };
return result;
};
setServiceText(action.match([&](
const MTPDmessageActionChatAddUser &data) {
return prepareChatAddUserText(data);
@ -702,6 +708,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return prepareWebViewDataSent(data);
}, [&](const MTPDmessageActionGiftPremium &data) {
return prepareGiftPremium(data);
}, [&](const MTPDmessageActionTopicCreate &data) {
return prepareTopicCreate(data);
}, [&](const MTPDmessageActionWebViewDataSentMe &data) {
LOG(("API Error: messageActionWebViewDataSentMe received."));
return PreparedText{

View File

@ -477,8 +477,9 @@ void TopBarWidget::paintTopBar(Painter &p) {
if (folder
|| history->peer->sharedMediaInfo()
|| (_activeChat.section == Section::Scheduled)
|| (_activeChat.section == Section::Pinned)) {
// #TODO feed name emoji.
|| (_activeChat.section == Section::Pinned)
|| (_activeChat.section == Section::ChatsList)) {
// #TODO forum name emoji.
auto text = (_activeChat.section == Section::Scheduled)
? ((history && history->peer->isSelf())
? tr::lng_reminder_messages(tr::now)
@ -489,7 +490,9 @@ void TopBarWidget::paintTopBar(Painter &p) {
? folder->chatListName()
: history->peer->isSelf()
? tr::lng_saved_messages(tr::now)
: tr::lng_replies_messages(tr::now);
: history->peer->isRepliesChat()
? tr::lng_replies_messages(tr::now)
: history->peer->name();
const auto textWidth = st::historySavedFont->width(text);
if (availableWidth < textWidth) {
text = st::historySavedFont->elided(text, availableWidth);
@ -686,6 +689,10 @@ void TopBarWidget::infoClicked() {
void TopBarWidget::backClicked() {
if (_activeChat.key.folder()) {
_controller->closeFolder();
} else if (_activeChat.section == Section::ChatsList
&& _activeChat.key.history()
&& _activeChat.key.history()->peer->isForum()) {
_controller->closeForum();
} else {
_controller->showBackFromStack();
}
@ -933,7 +940,7 @@ void TopBarWidget::updateControlsVisibility() {
const auto isOneColumn = _controller->adaptive().isOneColumn();
auto backVisible = isOneColumn
|| !_controller->content()->stackIsEmpty()
|| _activeChat.key.folder();
|| (_activeChat.section == Section::ChatsList);
_back->setVisible(backVisible && !_chooseForReportReason);
_cancelChoose->setVisible(_chooseForReportReason.has_value());
if (_info) {

View File

@ -676,6 +676,19 @@ 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();
@ -853,6 +866,7 @@ void SessionController::openFolder(not_null<Data::Folder*> folder) {
resetFakeUnreadWhileOpened();
}
setActiveChatsFilter(0);
closeForum();
_openedFolder = folder.get();
}
@ -860,6 +874,21 @@ void SessionController::closeFolder() {
_openedFolder = nullptr;
}
void SessionController::openForum(not_null<ChannelData*> forum) {
Expects(forum->isForum());
if (_openedForum.current() != forum) {
resetFakeUnreadWhileOpened();
}
setActiveChatsFilter(0);
closeFolder();
_openedForum = forum.get();
}
void SessionController::closeForum() {
_openedForum = nullptr;
}
void SessionController::setupPremiumToast() {
rpl::combine(
Data::AmPremiumValue(&session()),
@ -889,6 +918,10 @@ const rpl::variable<Data::Folder*> &SessionController::openedFolder() const {
return _openedFolder;
}
const rpl::variable<ChannelData*> &SessionController::openedForum() const {
return _openedForum;
}
void SessionController::setActiveChatEntry(Dialogs::RowDescriptor row) {
const auto was = _activeChatEntry.current().key.history();
const auto now = row.key.history();

View File

@ -340,10 +340,15 @@ public:
// is changed in the Dialogs::Widget of the current window.
rpl::variable<Dialogs::Key> searchInChat;
bool uniqueChatsInSearchResults() const;
void openFolder(not_null<Data::Folder*> folder);
void closeFolder();
const rpl::variable<Data::Folder*> &openedFolder() const;
void openForum(not_null<ChannelData*> forum);
void closeForum();
const rpl::variable<ChannelData*> &openedForum() const;
void setActiveChatEntry(Dialogs::RowDescriptor row);
void setActiveChatEntry(Dialogs::Key key);
Dialogs::RowDescriptor activeChatEntryCurrent() const;
@ -594,6 +599,7 @@ private:
PeerData *_showEditPeer = nullptr;
rpl::variable<Data::Folder*> _openedFolder;
rpl::variable<ChannelData*> _openedForum;
rpl::event_stream<> _filtersMenuChanged;