diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index d93ba8854c..29f0bad098 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3517,10 +3517,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_forum_topic_new" = "New Topic"; "lng_forum_topic_edit" = "Edit Topic"; "lng_forum_topic_title" = "Topic Title"; +"lng_forum_topic_closed" = "This topic is now closed."; "lng_forum_topics_switch" = "Topics"; "lng_forum_no_topics" = "No topics currently created in this forum."; "lng_forum_create_topic" = "Create topic"; -"lng_forum_discard_sure" = "Discard sure?"; +"lng_forum_discard_sure" = "Are you sure you want to discard this topic?"; "lng_forum_view_as_messages" = "View as Messages"; // Wnd specific diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index de90e9bf83..9a8a303ac3 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -154,6 +154,7 @@ struct TopicUpdate { IconId = (1U << 6), ColorId = (1U << 7), CloudDraft = (1U << 8), + Closed = (1U << 9), LastUsedBit = (1U << 8), }; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index d1e7ffc8bb..d7d5d66fe7 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -198,6 +198,14 @@ MsgId ForumTopic::rootId() const { return _rootId; } +bool ForumTopic::canEdit() const { + return (_flags & Flag::My) || channel()->canEditTopics(); +} + +bool ForumTopic::canToggleClosed() const { + return !creating() && canEdit(); +} + bool ForumTopic::creating() const { return _forum->creating(_rootId); } @@ -231,13 +239,11 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) { applyColorId(data.vicon_color().v); const auto pinned = _list->pinned(); -#if 0 // #TODO forum pinned if (data.is_pinned()) { pinned->addPinned(Dialogs::Key(this)); } else { pinned->setPinned(Dialogs::Key(this), false); } -#endif owner().notifySettings().apply(this, data.vnotify_settings()); @@ -249,19 +255,60 @@ void ForumTopic::applyTopic(const MTPDforumTopic &data) { _rootId, draft->c_draftMessage()); } + if (data.is_my()) { + _flags |= Flag::My; + } else { + _flags &= ~Flag::My; + } + setClosed(data.is_closed()); _replies->setInboxReadTill( data.vread_inbox_max_id().v, data.vunread_count().v); _replies->setOutboxReadTill(data.vread_outbox_max_id().v); applyTopicTopMessage(data.vtop_message().v); -#if 0 // #TODO forum unread mark - setUnreadMark(data.is_unread_mark()); -#endif unreadMentions().setCount(data.vunread_mentions_count().v); unreadReactions().setCount(data.vunread_reactions_count().v); } +bool ForumTopic::closed() const { + return _flags & Flag::Closed; +} + +void ForumTopic::setClosed(bool closed) { + if (this->closed() == closed) { + return; + } else if (closed) { + _flags |= Flag::Closed; + } else { + _flags &= ~Flag::Closed; + } + session().changes().topicUpdated(this, UpdateFlag::Closed); +} + +void ForumTopic::setClosedAndSave(bool closed) { + setClosed(closed); + + const auto api = &session().api(); + const auto weak = base::make_weak(this); + api->request(MTPchannels_EditForumTopic( + MTP_flags(MTPchannels_EditForumTopic::Flag::f_closed), + channel()->inputChannel, + MTP_int(_rootId), + MTPstring(), // title + MTPlong(), // icon_emoji_id + MTP_bool(closed) + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); + }).fail([=](const MTP::Error &error) { + if (error.type() != u"TOPIC_NOT_MODIFIED") { + if (const auto topic = weak.get()) { + topic->forum()->requestTopic(topic->rootId()); + } + } + }).send(); +} + void ForumTopic::indexTitleParts() { _titleWords.clear(); _titleFirstLetters.clear(); diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index d18d1a900e..7e9586cc86 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -61,6 +61,13 @@ public: [[nodiscard]] rpl::producer<> destroyed() const; [[nodiscard]] MsgId rootId() const; + [[nodiscard]] bool canEdit() const; + [[nodiscard]] bool canToggleClosed() const; + + [[nodiscard]] bool closed() const; + void setClosed(bool closed); + void setClosedAndSave(bool closed); + [[nodiscard]] bool creating() const; void discard(); @@ -128,6 +135,13 @@ public: ->not_null override; private: + enum class Flag : uchar { + Closed = (1 << 0), + My = (1 << 1), + }; + friend inline constexpr bool is_flag_type(Flag) { return true; } + using Flags = base::flags; + void indexTitleParts(); void validateDefaultIcon() const; void applyTopicTopMessage(MsgId topMessageId); @@ -158,6 +172,7 @@ private: base::flat_set _titleFirstLetters; int _titleVersion = 0; int32 _colorId = 0; + Flags _flags; std::unique_ptr _icon; mutable QImage _defaultIcon; // on-demand diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index e59f4aee1b..bd28ef77cd 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -534,6 +534,15 @@ bool PeerData::canCreateTopics() const { return isForum() && canPinMessages(); } +bool PeerData::canEditTopics() const { + if (const auto channel = asChannel()) { + return channel->isForum() + && (channel->amCreator() + || (channel->adminRights() & ChatAdminRight::PinMessages)); + } + return false; +} + bool PeerData::canEditMessagesIndefinitely() const { if (const auto user = asUser()) { return user->isSelf(); diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index 2c31944f9f..1893dfd824 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -340,6 +340,7 @@ public: [[nodiscard]] bool canPinMessages() const; [[nodiscard]] bool canEditMessagesIndefinitely() const; [[nodiscard]] bool canCreateTopics() const; + [[nodiscard]] bool canEditTopics() const; [[nodiscard]] bool canExportChatHistory() const; // Returns true if about text was changed. diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index bbfcc1272e..c522814353 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -638,7 +638,6 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { auto result = PreparedText{}; - // #TODO lang-forum result.text = { tr::lng_action_topic_created(tr::now) }; return result; }; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 0eb3d01646..b66736c2d1 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -568,19 +568,34 @@ void RepliesWidget::setupComposeControls() { std::move(hasSendingMessage), _1 && _2); + auto topicWriteRestrictions = rpl::single( + ) | rpl::then(session().changes().topicUpdates( + Data::TopicUpdate::Flag::Closed + ) | rpl::filter([=](const Data::TopicUpdate &update) { + return (update.topic->history() == _history) + && (update.topic->rootId() == _rootId); + }) | rpl::to_empty) | rpl::map([=] { + const auto topic = _topic + ? _topic + : _history->peer->forumTopicFor(_rootId); + return (topic->canToggleClosed() || !topic->closed()) + ? std::optional() + : tr::lng_forum_topic_closed(tr::now); + }); auto writeRestriction = rpl::combine( session().changes().peerFlagsValue( _history->peer, Data::PeerUpdate::Flag::Rights), - Data::CanWriteValue(_history->peer) - ) | rpl::map([=] { + Data::CanWriteValue(_history->peer), + std::move(topicWriteRestrictions) + ) | rpl::map([=](auto, auto, std::optional topicRestriction) { const auto restriction = Data::RestrictionError( _history->peer, ChatRestriction::SendMessages); return restriction ? restriction : _history->peer->canWrite() - ? std::optional() + ? std::move(topicRestriction) : tr::lng_group_not_accessible(tr::now); }); @@ -1465,7 +1480,7 @@ bool RepliesWidget::preventsClose(Fn &&continueCallback) const { } }; controller()->show(Ui::MakeConfirmBox({ - .text = rpl::single(u"Sure discard?"_q), // #TODO lang-forum + .text = tr::lng_forum_discard_sure(tr::now), .confirmed = std::move(sure), .confirmText = tr::lng_record_lock_discard(), .confirmStyle = &st::attentionBoxButton, diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 2960f5c4fa..579b2f71e7 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -226,6 +226,7 @@ private: void addThemeEdit(); void addBlockUser(); void addViewDiscussion(); + void addToggleTopicClosed(); void addExportChat(); void addReport(); void addNewContact(); @@ -239,6 +240,7 @@ private: void addCreateTopic(); void addViewAsMessages(); void addSearchTopics(); + void addDeleteTopic(); not_null _controller; Dialogs::EntryState _request; @@ -386,6 +388,19 @@ void Filler::addHidePromotion() { }, &st::menuIconRemove); } +void Filler::addToggleTopicClosed() { + if (!_topic || !_topic->canToggleClosed()) { + return; + } + const auto closed = _topic->closed(); + const auto weak = base::make_weak(_topic); + _addAction(closed ? u"Reopen"_q : u"Close"_q, [=] { + if (const auto topic = weak.get()) { + topic->setClosedAndSave(!closed); + } + }, closed ? &st::menuIconRestartBot : &st::menuIconBlock); +} + void Filler::addTogglePin() { if (!_peer || _topic) { // #TODO forum pinned @@ -802,13 +817,19 @@ void Filler::addDeleteContact() { }); } -void Filler::addManageTopic() { - const auto topic = _thread->asTopic(); - if (!topic) { +void Filler::addDeleteTopic() { + if (!_topic/* || !_topic->canDelete()*/) { return; } - const auto history = topic->history(); - const auto rootId = topic->rootId(); +} + + +void Filler::addManageTopic() { + if (!_topic || !_topic->canEdit()) { + return; + } + const auto history = _topic->history(); + const auto rootId = _topic->rootId(); const auto navigation = _controller; _addAction(tr::lng_forum_topic_edit(tr::now), [=] { navigation->show( @@ -995,6 +1016,7 @@ void Filler::fillContextMenuActions() { } addToggleMuteSubmenu(false); addToggleUnreadMark(); + addToggleTopicClosed(); addToggleFolder(); if (const auto user = _peer->asUser()) { if (!user->isContact()) { @@ -1004,6 +1026,7 @@ void Filler::fillContextMenuActions() { addClearHistory(); addDeleteChat(); addLeaveChat(); + addDeleteTopic(); } void Filler::fillHistoryActions() { @@ -1032,21 +1055,25 @@ void Filler::fillProfileActions() { addNewMembers(); addManageTopic(); addManageChat(); + addToggleTopicClosed(); addViewDiscussion(); addExportChat(); addBlockUser(); addReport(); addLeaveChat(); addDeleteContact(); + addDeleteTopic(); } void Filler::fillRepliesActions() { - if (_thread->asTopic()) { + if (_topic) { addInfo(); addManageTopic(); addManageChat(); + addDeleteTopic(); } addCreatePoll(); + addToggleTopicClosed(); } void Filler::fillScheduledActions() {