Add a Reopen Topic button on topic top for admins.

This commit is contained in:
John Preston 2022-10-21 16:32:23 +04:00
parent 92bf925fd0
commit b497e5ea21
9 changed files with 222 additions and 70 deletions

View file

@ -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?";

View file

@ -147,7 +147,8 @@ ForumTopic::ForumTopic(not_null<Forum*> 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 {

View file

@ -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;

View file

@ -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<HistoryItem*> 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)

View file

@ -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::SessionController*> window,
SlidingBar::SlidingBar(
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer)
: _controller(window)
, _bar(parent, object_ptr<Bar>(parent, peer->shortName()))
object_ptr<Ui::RpWidget> 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<Ui::RpWidget*> parent) {
void SlidingBar::setup(not_null<Ui::RpWidget*> 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<Ui::RpWidget*> 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<int> SlidingBar::heightValue() const {
return _wrapped.heightValue();
}
ContactStatus::ContactStatus(
not_null<Window::SessionController*> window,
not_null<Ui::RpWidget*> parent,
not_null<PeerData*> peer)
: _controller(window)
, _inner(Ui::CreateChild<Bar>(parent.get(), peer->shortName()))
, _bar(parent, object_ptr<Bar>::fromRaw(_inner)) {
setupState(peer);
setupHandlers(peer);
}
auto ContactStatus::PeerState(not_null<PeerData*> peer)
-> rpl::producer<State> {
using SettingsChange = PeerData::Settings::Change;
@ -520,7 +566,7 @@ void ContactStatus::setupState(not_null<PeerData*> 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<PeerData*> 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<PeerData*> peer) {
}
void ContactStatus::setupAddHandler(not_null<UserData*> user) {
_bar.entity()->addClicks(
_inner->addClicks(
) | rpl::start_with_next([=] {
_controller->window().show(Box(EditContactBox, _controller, user));
}, _bar.lifetime());
}
void ContactStatus::setupBlockHandler(not_null<UserData*> 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<UserData*> user) {
}
void ContactStatus::setupShareHandler(not_null<UserData*> user) {
_bar.entity()->shareClicks(
_inner->shareClicks(
) | rpl::start_with_next([=] {
const auto show = std::make_shared<Window::Show>(_controller);
const auto share = [=](Fn<void()> &&close) {
@ -606,7 +652,7 @@ void ContactStatus::setupShareHandler(not_null<UserData*> user) {
}
void ContactStatus::setupUnarchiveHandler(not_null<PeerData*> 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<PeerData*> peer) {
}
void ContactStatus::setupReportHandler(not_null<PeerData*> peer) {
_bar.entity()->reportClicks(
_inner->reportClicks(
) | rpl::start_with_next([=] {
Expects(!peer->isUser());
const auto show = std::make_shared<Window::Show>(_controller);
const auto callback = crl::guard(&_bar, [=](Fn<void()> &&close) {
const auto callback = crl::guard(_inner, [=](Fn<void()> &&close) {
close();
peer->session().api().request(MTPmessages_ReportSpam(
@ -665,7 +711,7 @@ void ContactStatus::setupReportHandler(not_null<PeerData*> peer) {
void ContactStatus::setupCloseHandler(not_null<PeerData*> peer) {
const auto request = _bar.lifetime().make_state<mtpRequestId>(0);
_bar.entity()->closeClicks(
_inner->closeClicks(
) | rpl::filter([=] {
return !(*request);
}) | rpl::start_with_next([=] {
@ -678,7 +724,7 @@ void ContactStatus::setupCloseHandler(not_null<PeerData*> peer) {
void ContactStatus::setupRequestInfoHandler(not_null<PeerData*> peer) {
const auto request = _bar.lifetime().make_state<mtpRequestId>(0);
_bar.entity()->requestInfoClicks(
_inner->requestInfoClicks(
) | rpl::filter([=] {
return !(*request);
}) | rpl::start_with_next([=] {
@ -714,39 +760,57 @@ void ContactStatus::setupRequestInfoHandler(not_null<PeerData*> peer) {
}
void ContactStatus::setupEmojiStatusHandler(not_null<PeerData*> 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<Ui::RpWidget*> parent,
not_null<Data::ForumTopic*> topic)
: _topic(topic)
, _reopen(Ui::CreateChild<Ui::FlatButton>(
parent.get(),
tr::lng_forum_topic_reopen(tr::now),
st::historyContactStatusButton))
, _bar(parent, object_ptr<Ui::FlatButton>::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<int> ContactStatus::heightValue() const {
return _bar.heightValue();
void TopicReopenBar::setupHandler() {
_reopen->setClickedCallback([=] {
_topic->setClosedAndSave(false);
});
}
} // namespace HistoryView

View file

@ -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<Ui::RpWidget*> parent,
object_ptr<Ui::RpWidget> wrapped);
void setVisible(bool visible);
void raise();
void move(int x, int y);
[[nodiscard]] int height() const;
[[nodiscard]] rpl::producer<int> 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<Ui::RpWidget*> parent);
Ui::SlideWrap<Ui::RpWidget> _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<PeerData*> peer);
void show();
void raise();
void move(int x, int y);
[[nodiscard]] int height() const;
[[nodiscard]] rpl::producer<int> 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<Ui::RpWidget*> parent);
void setupState(not_null<PeerData*> peer);
void setupHandlers(not_null<PeerData*> peer);
void setupAddHandler(not_null<UserData*> user);
@ -80,11 +115,29 @@ private:
State _state;
TextWithEntities _status;
Fn<std::any(Fn<void()> customEmojiRepaint)> _context;
Ui::SlideWrap<Bar> _bar;
Ui::PlainShadow _shadow;
QPointer<Bar> _inner;
SlidingBar _bar;
bool _shown = false;
rpl::lifetime _lifetime;
};
class TopicReopenBar final {
public:
TopicReopenBar(
not_null<Ui::RpWidget*> parent,
not_null<Data::ForumTopic*> topic);
[[nodiscard]] SlidingBar &bar() {
return _bar;
}
private:
void setupState();
void setupHandler();
const not_null<Data::ForumTopic*> _topic;
QPointer<Ui::FlatButton> _reopen;
SlidingBar _bar;
};

View file

@ -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<TopicReopenBar>(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);

View file

@ -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<Ui::PlainShadow> _topBarShadow;
std::unique_ptr<ComposeControls> _composeControls;
std::unique_ptr<Ui::FlatButton> _joinGroup;
std::unique_ptr<TopicReopenBar> _topicReopenBar;
bool _skipScrollEvent = false;
std::unique_ptr<Ui::PinnedBar> _rootView;
@ -308,6 +310,9 @@ private:
HistoryView::CornerButtons _cornerButtons;
rpl::lifetime _topicLifetime;
int _topicReopenBarHeight = 0;
int _scrollTopDelta = 0;
bool _choosingAttach = false;
bool _loaded = false;

View file

@ -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);
}