From b497e5ea21407556dc332971afa28450f7db536c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 21 Oct 2022 16:32:23 +0400 Subject: [PATCH] Add a Reopen Topic button on topic top for admins. --- Telegram/Resources/langs/lang.strings | 2 + .../SourceFiles/data/data_forum_topic.cpp | 11 +- Telegram/SourceFiles/data/data_forum_topic.h | 1 + .../SourceFiles/history/history_widget.cpp | 14 +- .../view/history_view_contact_status.cpp | 148 +++++++++++++----- .../view/history_view_contact_status.h | 75 +++++++-- .../view/history_view_replies_section.cpp | 34 +++- .../view/history_view_replies_section.h | 5 + .../SourceFiles/window/window_peer_menu.cpp | 2 +- 9 files changed, 222 insertions(+), 70 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b3eb9e252..585ce426e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3521,6 +3521,8 @@ 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_close" = "Close Topic"; +"lng_forum_topic_reopen" = "Reopen Topic"; "lng_forum_topic_closed" = "This topic is now closed."; "lng_forum_topic_delete" = "Delete"; "lng_forum_topic_delete_sure" = "Are you sure you want to delete this topic?"; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index b069dce95..717792db1 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -147,7 +147,8 @@ ForumTopic::ForumTopic(not_null forum, MsgId rootId) history(), rootId)) , _rootId(rootId) -, _lastKnownServerMessageId(rootId) { +, _lastKnownServerMessageId(rootId) +, _flags(creating() ? Flag::My : Flag()) { Thread::setMuted(owner().notifySettings().isMuted(this)); _sendActionPainter->setTopic(this); @@ -200,15 +201,19 @@ MsgId ForumTopic::rootId() const { return _rootId; } +bool ForumTopic::my() const { + return (_flags & Flag::My); +} + bool ForumTopic::canEdit() const { - return (_flags & Flag::My) || channel()->canEditTopics(); + return my() || channel()->canEditTopics(); } bool ForumTopic::canDelete() const { return !creating() && (channel()->canEditTopics() // We don't know if we can delete or not. - /*|| ((_flags & Flag::My) && onlyOneMyMessage)*/); + /*|| (my() && onlyOneMyMessage)*/); } bool ForumTopic::canToggleClosed() const { diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 2f23842f8..48ee8162b 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -61,6 +61,7 @@ public: [[nodiscard]] rpl::producer<> destroyed() const; [[nodiscard]] MsgId rootId() const; + [[nodiscard]] bool my() const; [[nodiscard]] bool canEdit() const; [[nodiscard]] bool canToggleClosed() const; [[nodiscard]] bool canTogglePinned() const; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 373fd9e00..398e16855 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1500,7 +1500,7 @@ void HistoryWidget::orderWidgets() { _voiceRecordBar->raise(); _send->raise(); if (_contactStatus) { - _contactStatus->raise(); + _contactStatus->bar().raise(); } if (_pinnedBar) { _pinnedBar->raise(); @@ -2127,9 +2127,9 @@ void HistoryWidget::showHistory( controller(), this, _peer); - _contactStatus->heightValue() | rpl::start_with_next([=] { + _contactStatus->bar().heightValue() | rpl::start_with_next([=] { updateControlsGeometry(); - }, _contactStatus->lifetime()); + }, _contactStatus->bar().lifetime()); orderWidgets(); controller()->tabbedSelector()->setCurrentPeer(_peer); } @@ -5204,9 +5204,9 @@ void HistoryWidget::updateControlsGeometry() { } const auto contactStatusTop = pinnedBarTop + (_pinnedBar ? _pinnedBar->height() : 0); if (_contactStatus) { - _contactStatus->move(0, contactStatusTop); + _contactStatus->bar().move(0, contactStatusTop); } - const auto scrollAreaTop = contactStatusTop + (_contactStatus ? _contactStatus->height() : 0); + const auto scrollAreaTop = contactStatusTop + (_contactStatus ? _contactStatus->bar().height() : 0); if (_scroll->y() != scrollAreaTop) { _scroll->moveToLeft(0, scrollAreaTop); _fieldAutocomplete->setBoundings(_scroll->geometry()); @@ -5390,7 +5390,7 @@ void HistoryWidget::updateHistoryGeometry( newScrollHeight -= _requestsBar->height(); } if (_contactStatus) { - newScrollHeight -= _contactStatus->height(); + newScrollHeight -= _contactStatus->bar().height(); } if (isChoosingTheme()) { newScrollHeight -= _chooseTheme->height(); @@ -5761,7 +5761,7 @@ void HistoryWidget::botCallbackSent(not_null item) { int HistoryWidget::computeMaxFieldHeight() const { const auto available = height() - _topBar->height() - - (_contactStatus ? _contactStatus->height() : 0) + - (_contactStatus ? _contactStatus->bar().height() : 0) - (_pinnedBar ? _pinnedBar->height() : 0) - (_groupCallBar ? _groupCallBar->height() : 0) - (_requestsBar ? _requestsBar->height() : 0) diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp index f10c4daf4..b306fb908 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp @@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_document.h" #include "data/data_session.h" +#include "data/data_forum_topic.h" #include "data/stickers/data_custom_emoji.h" #include "settings/settings_premium.h" #include "window/window_peer_menu.h" @@ -418,25 +419,22 @@ void ContactStatus::Bar::emojiStatusRepaint() { _emojiStatusInfo->entity()->update(); } -ContactStatus::ContactStatus( - not_null window, +SlidingBar::SlidingBar( not_null parent, - not_null peer) -: _controller(window) -, _bar(parent, object_ptr(parent, peer->shortName())) + object_ptr wrapped) +: _wrapped(parent, std::move(wrapped)) , _shadow(parent) { - setupWidgets(parent); - setupState(peer); - setupHandlers(peer); + setup(parent); + _wrapped.hide(anim::type::instant); } -void ContactStatus::setupWidgets(not_null parent) { +void SlidingBar::setup(not_null parent) { parent->widthValue( ) | rpl::start_with_next([=](int width) { - _bar.resizeToWidth(width); - }, _bar.lifetime()); + _wrapped.resizeToWidth(width); + }, _wrapped.lifetime()); - _bar.geometryValue( + _wrapped.geometryValue( ) | rpl::start_with_next([=](QRect geometry) { _shadow.setGeometry( geometry.x(), @@ -445,12 +443,60 @@ void ContactStatus::setupWidgets(not_null parent) { st::lineWidth); }, _shadow.lifetime()); - _bar.shownValue( + _wrapped.shownValue( ) | rpl::start_with_next([=](bool shown) { _shadow.setVisible(shown); }, _shadow.lifetime()); } +void SlidingBar::toggleContent(bool visible) { + _contentShown = visible; + if (_shown) { + _wrapped.toggle(visible, anim::type::normal); + } +} + +void SlidingBar::raise() { + _wrapped.raise(); + _shadow.raise(); +} + +void SlidingBar::setVisible(bool visible) { + if (_shown == visible) { + return; + } + _shown = visible; + if (!_shown) { + _wrapped.hide(anim::type::instant); + } else if (_contentShown) { + _wrapped.show(anim::type::instant); + } +} + +void SlidingBar::move(int x, int y) { + _wrapped.move(x, y); + _shadow.move(x, y + _wrapped.height()); +} + +int SlidingBar::height() const { + return _wrapped.height(); +} + +rpl::producer SlidingBar::heightValue() const { + return _wrapped.heightValue(); +} + +ContactStatus::ContactStatus( + not_null window, + not_null parent, + not_null peer) +: _controller(window) +, _inner(Ui::CreateChild(parent.get(), peer->shortName())) +, _bar(parent, object_ptr::fromRaw(_inner)) { + setupState(peer); + setupHandlers(peer); +} + auto ContactStatus::PeerState(not_null peer) -> rpl::producer { using SettingsChange = PeerData::Settings::Change; @@ -520,7 +566,7 @@ void ContactStatus::setupState(not_null peer) { .customEmojiRepaint = customEmojiRepaint, }; }; - _bar.entity()->showState({}, {}, _context); + _inner->showState({}, {}, _context); rpl::combine( PeerState(peer), PeerCustomStatus(peer) @@ -528,10 +574,10 @@ void ContactStatus::setupState(not_null peer) { _state = state; _status = status; if (state.type == State::Type::None) { - _bar.hide(anim::type::normal); + _bar.toggleContent(false); } else { - _bar.entity()->showState(state, std::move(status), _context); - _bar.show(anim::type::normal); + _inner->showState(state, std::move(status), _context); + _bar.toggleContent(true); } }, _bar.lifetime()); } @@ -550,14 +596,14 @@ void ContactStatus::setupHandlers(not_null peer) { } void ContactStatus::setupAddHandler(not_null user) { - _bar.entity()->addClicks( + _inner->addClicks( ) | rpl::start_with_next([=] { _controller->window().show(Box(EditContactBox, _controller, user)); }, _bar.lifetime()); } void ContactStatus::setupBlockHandler(not_null user) { - _bar.entity()->blockClicks( + _inner->blockClicks( ) | rpl::start_with_next([=] { _controller->window().show(Box( Window::PeerMenuBlockUserBox, @@ -569,7 +615,7 @@ void ContactStatus::setupBlockHandler(not_null user) { } void ContactStatus::setupShareHandler(not_null user) { - _bar.entity()->shareClicks( + _inner->shareClicks( ) | rpl::start_with_next([=] { const auto show = std::make_shared(_controller); const auto share = [=](Fn &&close) { @@ -606,7 +652,7 @@ void ContactStatus::setupShareHandler(not_null user) { } void ContactStatus::setupUnarchiveHandler(not_null peer) { - _bar.entity()->unarchiveClicks( + _inner->unarchiveClicks( ) | rpl::start_with_next([=] { Window::ToggleHistoryArchived(peer->owner().history(peer), false); peer->owner().notifySettings().resetToDefault(peer); @@ -620,12 +666,12 @@ void ContactStatus::setupUnarchiveHandler(not_null peer) { } void ContactStatus::setupReportHandler(not_null peer) { - _bar.entity()->reportClicks( + _inner->reportClicks( ) | rpl::start_with_next([=] { Expects(!peer->isUser()); const auto show = std::make_shared(_controller); - const auto callback = crl::guard(&_bar, [=](Fn &&close) { + const auto callback = crl::guard(_inner, [=](Fn &&close) { close(); peer->session().api().request(MTPmessages_ReportSpam( @@ -665,7 +711,7 @@ void ContactStatus::setupReportHandler(not_null peer) { void ContactStatus::setupCloseHandler(not_null peer) { const auto request = _bar.lifetime().make_state(0); - _bar.entity()->closeClicks( + _inner->closeClicks( ) | rpl::filter([=] { return !(*request); }) | rpl::start_with_next([=] { @@ -678,7 +724,7 @@ void ContactStatus::setupCloseHandler(not_null peer) { void ContactStatus::setupRequestInfoHandler(not_null peer) { const auto request = _bar.lifetime().make_state(0); - _bar.entity()->requestInfoClicks( + _inner->requestInfoClicks( ) | rpl::filter([=] { return !(*request); }) | rpl::start_with_next([=] { @@ -714,39 +760,57 @@ void ContactStatus::setupRequestInfoHandler(not_null peer) { } void ContactStatus::setupEmojiStatusHandler(not_null peer) { - _bar.entity()->emojiStatusClicks( + _inner->emojiStatusClicks( ) | rpl::start_with_next([=] { Settings::ShowEmojiStatusPremium(_controller, peer); }, _bar.lifetime()); } void ContactStatus::show() { - const auto visible = (_state.type != State::Type::None); if (!_shown) { _shown = true; - if (visible) { - _bar.entity()->showState(_state, _status, _context); + if (_state.type != State::Type::None) { + _inner->showState(_state, _status, _context); + _bar.toggleContent(true); } } - _bar.toggle(visible, anim::type::instant); + _bar.show(); } -void ContactStatus::raise() { - _bar.raise(); - _shadow.raise(); +TopicReopenBar::TopicReopenBar( + not_null parent, + not_null topic) +: _topic(topic) +, _reopen(Ui::CreateChild( + parent.get(), + tr::lng_forum_topic_reopen(tr::now), + st::historyContactStatusButton)) +, _bar(parent, object_ptr::fromRaw(_reopen)) { + setupState(); + setupHandler(); } -void ContactStatus::move(int x, int y) { - _bar.move(x, y); - _shadow.move(x, y + _bar.height()); +void TopicReopenBar::setupState() { + const auto channel = _topic->channel(); + auto canToggle = (_topic->my() || channel->amCreator()) + ? (rpl::single(true) | rpl::type_erased()) + : channel->adminRightsValue( + ) | rpl::map([=] { return _topic->canToggleClosed(); }); + + rpl::combine( + _topic->session().changes().topicFlagsValue( + _topic, + Data::TopicUpdate::Flag::Closed), + std::move(canToggle) + ) | rpl::start_with_next([=](const auto &, bool can) { + _bar.toggleContent(can && _topic->closed()); + }, _bar.lifetime()); } -int ContactStatus::height() const { - return _bar.height(); -} - -rpl::producer ContactStatus::heightValue() const { - return _bar.heightValue(); +void TopicReopenBar::setupHandler() { + _reopen->setClickedCallback([=] { + _topic->setClosedAndSave(false); + }); } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.h b/Telegram/SourceFiles/history/view/history_view_contact_status.h index 9dbab24e4..2967bfb23 100644 --- a/Telegram/SourceFiles/history/view/history_view_contact_status.h +++ b/Telegram/SourceFiles/history/view/history_view_contact_status.h @@ -11,6 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/wrap/slide_wrap.h" #include "ui/widgets/shadow.h" +namespace Data { +class ForumTopic; +} // namespace Data + namespace Window { class SessionController; } // namespace Window @@ -23,6 +27,43 @@ class FlatLabel; namespace HistoryView { +class SlidingBar final { +public: + SlidingBar( + not_null parent, + object_ptr wrapped); + + void setVisible(bool visible); + void raise(); + + void move(int x, int y); + [[nodiscard]] int height() const; + [[nodiscard]] rpl::producer heightValue() const; + void toggleContent(bool visible); + + void show() { + setVisible(true); + } + void hide() { + setVisible(false); + } + + [[nodiscard]] rpl::lifetime &lifetime() { + return _lifetime; + } + +private: + void setup(not_null parent); + + Ui::SlideWrap _wrapped; + Ui::PlainShadow _shadow; + bool _shown = false; + bool _contentShown = false; + + rpl::lifetime _lifetime; + +}; + class ContactStatus final { public: ContactStatus( @@ -31,14 +72,9 @@ public: not_null peer); void show(); - void raise(); - void move(int x, int y); - [[nodiscard]] int height() const; - [[nodiscard]] rpl::producer heightValue() const; - - [[nodiscard]] rpl::lifetime &lifetime() { - return _lifetime; + [[nodiscard]] SlidingBar &bar() { + return _bar; } private: @@ -62,7 +98,6 @@ private: TimeId requestDate = 0; }; - void setupWidgets(not_null parent); void setupState(not_null peer); void setupHandlers(not_null peer); void setupAddHandler(not_null user); @@ -80,11 +115,29 @@ private: State _state; TextWithEntities _status; Fn customEmojiRepaint)> _context; - Ui::SlideWrap _bar; - Ui::PlainShadow _shadow; + QPointer _inner; + SlidingBar _bar; bool _shown = false; - rpl::lifetime _lifetime; +}; + +class TopicReopenBar final { +public: + TopicReopenBar( + not_null parent, + not_null topic); + + [[nodiscard]] SlidingBar &bar() { + return _bar; + } + +private: + void setupState(); + void setupHandler(); + + const not_null _topic; + QPointer _reopen; + SlidingBar _bar; }; diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 0fbc24c94..598c0798e 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_pinned_bar.h" #include "history/view/history_view_sticker_toast.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_contact_status.h" #include "history/history.h" #include "history/history_drag_area.h" #include "history/history_item_components.h" @@ -260,6 +261,9 @@ RepliesWidget::RepliesWidget( if (_rootView) { _rootView->raise(); } + if (_topicReopenBar) { + _topicReopenBar->bar().raise(); + } _topBarShadow->raise(); controller->adaptive().value( @@ -462,6 +466,18 @@ void RepliesWidget::setupTopicViewer() { void RepliesWidget::subscribeToTopic() { Expects(_topic != nullptr); + _topicReopenBar = std::make_unique(this, _topic); + _topicReopenBar->bar().setVisible(!animatingShow()); + _topicReopenBarHeight = _topicReopenBar->bar().height(); + _topicReopenBar->bar().heightValue( + ) | rpl::start_with_next([=] { + const auto height = _topicReopenBar->bar().height(); + _scrollTopDelta = (height - _topicReopenBarHeight); + _topicReopenBarHeight = height; + updateControlsGeometry(); + _scrollTopDelta = 0; + }, _topicReopenBar->bar().lifetime()); + using Flag = Data::TopicUpdate::Flag; session().changes().topicUpdates( _topic, @@ -496,12 +512,11 @@ void RepliesWidget::setTopic(Data::ForumTopic *topic) { refreshReplies(); refreshTopBarActiveChat(); if (_topic) { - subscribeToTopic(); if (_rootView) { _rootView = nullptr; _rootViewHeight = 0; - updateControlsGeometry(); } + subscribeToTopic(); } } @@ -1689,7 +1704,9 @@ void RepliesWidget::updateControlsGeometry() { const auto newScrollTop = _scroll->isHidden() ? std::nullopt - : base::make_optional(_scroll->scrollTop() + topDelta()); + : base::make_optional(_scroll->scrollTop() + + topDelta() + + _scrollTopDelta); _topBar->resizeToWidth(contentWidth); _topBarShadow->resize(contentWidth, st::lineWidth); if (_rootView) { @@ -1700,8 +1717,13 @@ void RepliesWidget::updateControlsGeometry() { const auto controlsHeight = _joinGroup ? _joinGroup->height() : _composeControls->heightCurrent(); - const auto scrollY = _topBar->height() + _rootViewHeight; - const auto scrollHeight = bottom - scrollY - controlsHeight; + auto top = _topBar->height() + + _rootViewHeight; + if (_topicReopenBar) { + _topicReopenBar->bar().move(0, top); + top += _topicReopenBar->bar().height(); + } + const auto scrollHeight = bottom - top - controlsHeight; const auto scrollSize = QSize(contentWidth, scrollHeight); if (_scroll->size() != scrollSize) { _skipScrollEvent = true; @@ -1709,7 +1731,7 @@ void RepliesWidget::updateControlsGeometry() { _inner->resizeToWidth(scrollSize.width(), _scroll->height()); _skipScrollEvent = false; } - _scroll->move(0, scrollY); + _scroll->move(0, top); if (!_scroll->isHidden()) { if (newScrollTop) { _scroll->scrollToY(*newScrollTop); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index e91bf702e..3756dc416 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -65,6 +65,7 @@ class RepliesMemento; class ComposeControls; class SendActionPainter; class StickerToast; +class TopicReopenBar; class RepliesWidget final : public Window::SectionWidget @@ -294,6 +295,7 @@ private: object_ptr _topBarShadow; std::unique_ptr _composeControls; std::unique_ptr _joinGroup; + std::unique_ptr _topicReopenBar; bool _skipScrollEvent = false; std::unique_ptr _rootView; @@ -308,6 +310,9 @@ private: HistoryView::CornerButtons _cornerButtons; rpl::lifetime _topicLifetime; + int _topicReopenBarHeight = 0; + int _scrollTopDelta = 0; + bool _choosingAttach = false; bool _loaded = false; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 3e71d3198..c6f6dd8a0 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -409,7 +409,7 @@ void Filler::addToggleTopicClosed() { } const auto closed = _topic->closed(); const auto weak = base::make_weak(_topic); - _addAction(closed ? u"Reopen"_q : u"Close"_q, [=] { + _addAction(closed ? tr::lng_forum_topic_reopen(tr::now) : tr::lng_forum_topic_close(tr::now), [=] { if (const auto topic = weak.get()) { topic->setClosedAndSave(!closed); }