From 25a718c54b0dced6b1db061b65495d8ff11a0a85 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 18 Jun 2017 14:08:14 +0300 Subject: [PATCH] Start recent channel actions log section. --- Telegram/Resources/langs/lang.strings | 23 ++ .../history/history_admin_log_inner.cpp | 156 +++++++++ .../history/history_admin_log_inner.h | 87 +++++ .../history/history_admin_log_section.cpp | 313 ++++++++++++++++++ .../history/history_admin_log_section.h | 118 +++++++ .../profile/profile_back_button.cpp | 5 + .../SourceFiles/profile/profile_back_button.h | 2 + .../profile/profile_block_channel_members.cpp | 41 ++- .../profile/profile_block_channel_members.h | 6 +- .../profile/profile_block_settings.cpp | 23 ++ .../profile/profile_block_settings.h | 2 + .../profile/profile_block_shared_media.cpp | 2 +- .../profile/profile_common_groups_section.cpp | 26 +- .../profile/profile_common_groups_section.h | 30 +- Telegram/SourceFiles/window/section_widget.h | 4 + Telegram/gyp/telegram_sources.txt | 4 + 16 files changed, 800 insertions(+), 42 deletions(-) create mode 100644 Telegram/SourceFiles/history/history_admin_log_inner.cpp create mode 100644 Telegram/SourceFiles/history/history_admin_log_inner.h create mode 100644 Telegram/SourceFiles/history/history_admin_log_section.cpp create mode 100644 Telegram/SourceFiles/history/history_admin_log_section.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0496e3a9ee..94e3398fea 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -552,6 +552,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_profile_manage_admins" = "Manage administrators"; "lng_profile_manage_blocklist" = "Manage banned users"; "lng_profile_manage_restrictedlist" = "Manage restricted users"; +"lng_profile_recent_actions" = "Recent actions"; "lng_profile_common_groups#one" = "{count} group in common"; "lng_profile_common_groups#other" = "{count} groups in common"; "lng_profile_common_groups_section" = "Groups in common"; @@ -1300,6 +1301,28 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_restricted_list_title" = "Restricted users"; "lng_banned_list_title" = "Banned users"; +"lng_admin_log_title_all" = "All actions"; +"lng_admin_log_title_selected" = "Selected actions"; +"lng_admin_log_filter" = "Filter"; +"lng_admin_log_filter_title" = "Filter"; +"lng_admin_log_filter_all_actions" = "All actions"; +"lng_admin_log_filter_restrictions" = "New restrictions"; +"lng_admin_log_filter_admins_new" = "New admins"; +"lng_admin_log_filter_members_new" = "New members"; +"lng_admin_log_filter_info_group" = "Group info"; +"lng_admin_log_filter_info_channel" = "Channel info"; +"lng_admin_log_filter_messages_deleted" = "Deleted messages"; +"lng_admin_log_filter_messages_edited" = "Edited messages"; +"lng_admin_log_filter_messages_pinned" = "Pinned messages"; +"lng_admin_log_filter_members_removed" = "Members removed"; +"lng_admin_log_filter_all_admins" = "All admins"; +"lng_admin_log_about" = "What is this?"; +"lng_admin_log_about_text" = "This is a list of all service actions taken by the group's members and admins in the last 48 hours."; +"lng_admin_log_no_results_title" = "No events found"; +"lng_admin_log_no_results_text" = "No recent events that match your query have been found."; +"lng_admin_log_no_events_title" = "No events yet"; +"lng_admin_log_no_events_text" = "There were no service actions taken by the group's members and admins in the last 48 hours."; + // Not used "lng_topbar_info" = "Info"; diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/history_admin_log_inner.cpp new file mode 100644 index 0000000000..dcada88397 --- /dev/null +++ b/Telegram/SourceFiles/history/history_admin_log_inner.cpp @@ -0,0 +1,156 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "history/history_admin_log_inner.h" + +#include "history/history_admin_log_section.h" + +namespace AdminLog { +namespace { + +constexpr int kEventsPerPage = 50; + +} // namespace + +InnerWidget::InnerWidget(QWidget *parent, gsl::not_null channel) : TWidget(parent) +, _channel(channel) { + setMouseTracking(true); +} + +void InnerWidget::setVisibleTopBottom(int visibleTop, int visibleBottom) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + + checkPreloadMore(); +} + +void InnerWidget::checkPreloadMore() { + if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) { + preloadMore(); + } +} + +void InnerWidget::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins) { + _filterFlags = flags; + _filterAdmins = admins; +} + +void InnerWidget::saveState(gsl::not_null memento) const { + //if (auto count = _items.size()) { + // QList> groups; + // groups.reserve(count); + // for_const (auto item, _items) { + // groups.push_back(item->peer); + // } + // memento->setCommonGroups(groups); + //} +} + +void InnerWidget::restoreState(gsl::not_null memento) { + //auto list = memento->getCommonGroups(); + _allLoaded = false; + //if (!list.empty()) { + // showInitial(list); + //} +} + +//void InnerWidget::showInitial(const QList &list) { +// for_const (auto group, list) { +// if (auto item = computeItem(group)) { +// _items.push_back(item); +// } +// _preloadGroupId = group->bareId(); +// } +// updateSize(); +//} + +void InnerWidget::preloadMore() { + if (_preloadRequestId || _allLoaded) { + return; + } + auto flags = MTPchannels_GetAdminLog::Flags(0); + auto filter = MTP_channelAdminLogEventsFilter(MTP_flags(_filterFlags)); + if (_filterFlags != 0) { + flags |= MTPchannels_GetAdminLog::Flag::f_events_filter; + } + auto admins = QVector(0); + if (!_filterAdmins.empty()) { + admins.reserve(_filterAdmins.size()); + for (auto &admin : _filterAdmins) { + admins.push_back(admin->inputUser); + } + flags |= MTPchannels_GetAdminLog::Flag::f_admins; + } + auto query = QString(); + auto _maxId = 0ULL; + auto _minId = 0ULL; + _preloadRequestId = request(MTPchannels_GetAdminLog(MTP_flags(flags), _channel->inputChannel, MTP_string(query), filter, MTP_vector(admins), MTP_long(_maxId), MTP_long(_minId), MTP_int(kEventsPerPage))).done([this](const MTPchannels_AdminLogResults &result) { + _preloadRequestId = 0; + _allLoaded = true; + }).fail([this](const RPCError &error) { + }).send(); +} + +void InnerWidget::updateSize() { + TWidget::resizeToWidth(width()); + checkPreloadMore(); +} + +int InnerWidget::resizeGetHeight(int newWidth) { + update(); + + auto newHeight = 0; + return qMax(newHeight, _minHeight); +} + +void InnerWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto ms = getms(); + auto clip = e->rect(); + + //style::font font(st::msgServiceFont); + //int32 w = font->width(lang(lng_willbe_history)) + st::msgPadding.left() + st::msgPadding.right(), h = font->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + 2; + //QRect tr((width() - w) / 2, (height() - _field->height() - 2 * st::historySendPadding - h) / 2, w, h); + //HistoryLayout::ServiceMessagePainter::paintBubble(p, tr.x(), tr.y(), tr.width(), tr.height()); + + //p.setPen(st::msgServiceFg); + //p.setFont(font->f); + //p.drawText(tr.left() + st::msgPadding.left(), tr.top() + st::msgServicePadding.top() + 1 + font->ascent, lang(lng_willbe_history)); +} + +void InnerWidget::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape && _cancelledCallback) { + _cancelledCallback(); + } +} + +void InnerWidget::mousePressEvent(QMouseEvent *e) { +} + +void InnerWidget::mouseMoveEvent(QMouseEvent *e) { +} + +void InnerWidget::mouseReleaseEvent(QMouseEvent *e) { +} + +InnerWidget::~InnerWidget() = default; + +} // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.h b/Telegram/SourceFiles/history/history_admin_log_inner.h new file mode 100644 index 0000000000..9afb8c47e9 --- /dev/null +++ b/Telegram/SourceFiles/history/history_admin_log_inner.h @@ -0,0 +1,87 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "mtproto/sender.h" + +namespace AdminLog { + +class SectionMemento; + +class InnerWidget final : public TWidget, private MTP::Sender { +public: + InnerWidget(QWidget *parent, gsl::not_null channel); + + gsl::not_null channel() const { + return _channel; + } + + // Updates the area that is visible inside the scroll container. + void setVisibleTopBottom(int visibleTop, int visibleBottom) override; + + void resizeToWidth(int newWidth, int minHeight) { + _minHeight = minHeight; + return TWidget::resizeToWidth(newWidth); + } + + void saveState(gsl::not_null memento) const; + void restoreState(gsl::not_null memento); + void setCancelledCallback(base::lambda callback) { + _cancelledCallback = std::move(callback); + } + + // Empty "flags" means all events. Empty "admins" means all admins. + void applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins); + + ~InnerWidget(); + +protected: + void paintEvent(QPaintEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + + // Resizes content and counts natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +private: + void checkPreloadMore(); + void preloadMore(); + void updateSize(); + + gsl::not_null _channel; + base::lambda _cancelledCallback; + + int _minHeight = 0; + int _visibleTop = 0; + int _visibleBottom = 0; + + int32 _preloadGroupId = 0; + mtpRequestId _preloadRequestId = 0; + bool _allLoaded = true; + + MTPDchannelAdminLogEventsFilter::Flags _filterFlags = 0; + std::vector> _filterAdmins; + +}; + +} // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history_admin_log_section.cpp b/Telegram/SourceFiles/history/history_admin_log_section.cpp new file mode 100644 index 0000000000..a1dbdb59f5 --- /dev/null +++ b/Telegram/SourceFiles/history/history_admin_log_section.cpp @@ -0,0 +1,313 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#include "history/history_admin_log_section.h" + +#include "history/history_admin_log_inner.h" +#include "profile/profile_back_button.h" +#include "styles/style_history.h" +#include "styles/style_window.h" +#include "ui/widgets/scroll_area.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "mainwidget.h" +#include "mainwindow.h" +#include "apiwrap.h" +#include "window/themes/window_theme.h" +#include "boxes/confirm_box.h" +#include "lang/lang_keys.h" + +namespace AdminLog { + +class FixedBar final : public TWidget, private base::Subscriber { +public: + FixedBar(QWidget *parent); + + // When animating mode is enabled the content is hidden and the + // whole fixed bar acts like a back button. + void setAnimatingMode(bool enabled); + + // Empty "flags" means all events. Empty "admins" means all admins. + void applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins); + void goBack(); + +protected: + void mousePressEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + int resizeGetHeight(int newWidth) override; + +private: + object_ptr _backButton; + object_ptr _filter; + + bool _animatingMode = false; + +}; + +object_ptr SectionMemento::createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const { + auto result = object_ptr(parent, controller, _channel); + result->setInternalState(geometry, this); + return std::move(result); +} + +FixedBar::FixedBar(QWidget *parent) : TWidget(parent) +, _backButton(this, lang(lng_admin_log_title_all)) +, _filter(this, langFactory(lng_admin_log_filter), st::topBarButton) { + _backButton->moveToLeft(0, 0); + _backButton->setClickedCallback([this] { goBack(); }); + _filter->setClickedCallback([this] {}); +} + +void FixedBar::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins) { + auto hasFilter = (flags != 0) || !admins.empty(); + _backButton->setText(lang(hasFilter ? lng_admin_log_title_selected : lng_admin_log_title_all)); +} + +void FixedBar::goBack() { + App::main()->showBackFromStack(); +} + +int FixedBar::resizeGetHeight(int newWidth) { + auto newHeight = 0; + + auto buttonLeft = newWidth; + buttonLeft -= _filter->width(); _filter->moveToLeft(buttonLeft, 0); + _backButton->resizeToWidth(buttonLeft); + _backButton->moveToLeft(0, 0); + newHeight += _backButton->height(); + + return newHeight; +} + +void FixedBar::setAnimatingMode(bool enabled) { + if (_animatingMode != enabled) { + _animatingMode = enabled; + setCursor(_animatingMode ? style::cur_pointer : style::cur_default); + if (_animatingMode) { + setAttribute(Qt::WA_OpaquePaintEvent, false); + hideChildren(); + } else { + setAttribute(Qt::WA_OpaquePaintEvent); + showChildren(); + } + show(); + } +} + +void FixedBar::paintEvent(QPaintEvent *e) { + Painter p(this); + p.fillRect(e->rect(), st::topBarBg); +} + +void FixedBar::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton) { + goBack(); + } else { + TWidget::mousePressEvent(e); + } +} + +Widget::Widget(QWidget *parent, gsl::not_null controller, gsl::not_null channel) : Window::SectionWidget(parent, controller) +, _scroll(this, st::historyScroll, false) +, _fixedBar(this) +, _fixedBarShadow(this, st::shadowFg) +, _whatIsThis(this, lang(lng_admin_log_about).toUpper(), st::historyComposeButton) { + _fixedBar->move(0, 0); + _fixedBar->resizeToWidth(width()); + _fixedBar->show(); + + _fixedBarShadow->raise(); + updateAdaptiveLayout(); + subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); + + _inner = _scroll->setOwnedWidget(object_ptr(this, channel)); + _scroll->move(0, _fixedBar->height()); + _scroll->show(); + + connect(_scroll, &Ui::ScrollArea::scrolled, this, [this] { onScroll(); }); + _inner->setCancelledCallback([this] { _fixedBar->goBack(); }); + + _whatIsThis->setClickedCallback([this] { Ui::show(Box(lang(lng_admin_log_about_text))); }); +} + +void Widget::updateAdaptiveLayout() { + _fixedBarShadow->moveToLeft(Adaptive::OneColumn() ? 0 : st::lineWidth, _fixedBar->height()); +} + +gsl::not_null Widget::channel() const { + return _inner->channel(); +} + +QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + if (params.withTopBarShadow) _fixedBarShadow->hide(); + auto result = myGrab(this); + if (params.withTopBarShadow) _fixedBarShadow->show(); + return result; +} + +void Widget::doSetInnerFocus() { + _inner->setFocus(); +} + +bool Widget::showInternal(const Window::SectionMemento *memento) { + if (auto profileMemento = dynamic_cast(memento)) { + if (profileMemento->getChannel() == channel()) { + restoreState(profileMemento); + return true; + } + } + return false; +} + +void Widget::setInternalState(const QRect &geometry, gsl::not_null memento) { + setGeometry(geometry); + myEnsureResized(this); + restoreState(memento); +} + +std::unique_ptr Widget::createMemento() const { + auto result = std::make_unique(channel()); + saveState(result.get()); + return std::move(result); +} + +void Widget::saveState(gsl::not_null memento) const { + memento->setScrollTop(_scroll->scrollTop()); + _inner->saveState(memento); +} + +void Widget::restoreState(gsl::not_null memento) { + _inner->restoreState(memento); + auto scrollTop = memento->getScrollTop(); + _scroll->scrollToY(scrollTop); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + +void Widget::resizeEvent(QResizeEvent *e) { + if (!width() || !height()) { + return; + } + + auto contentWidth = width(); + + auto newScrollTop = _scroll->scrollTop() + topDelta(); + _fixedBar->resizeToWidth(contentWidth); + _fixedBarShadow->resize(contentWidth, st::lineWidth); + + auto bottom = height(); + auto scrollHeight = bottom - _fixedBar->height() - _whatIsThis->height(); + auto scrollSize = QSize(contentWidth, scrollHeight); + if (_scroll->size() != scrollSize) { + _scroll->resize(scrollSize); + _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + } + + if (!_scroll->isHidden()) { + if (topDelta()) { + _scroll->scrollToY(newScrollTop); + } + auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); + } + auto fullWidthButtonRect = myrtlrect(0, bottom - _whatIsThis->height(), contentWidth, _whatIsThis->height()); + _whatIsThis->setGeometry(fullWidthButtonRect); +} + +void Widget::paintEvent(QPaintEvent *e) { + if (animating()) { + SectionWidget::paintEvent(e); + return; + } + if (Ui::skipPaintEvent(this, e)) { + return; + } + //if (hasPendingResizedItems()) { + // updateListSize(); + //} + + Painter p(this); + auto clip = e->rect(); + auto ms = getms(); + //_historyDownShown.step(ms); + + auto fill = QRect(0, 0, width(), App::main()->height()); + auto fromy = App::main()->backgroundFromY(); + auto x = 0, y = 0; + auto cached = App::main()->cachedBackground(fill, x, y); + if (cached.isNull()) { + if (Window::Theme::Background()->tile()) { + auto &pix = Window::Theme::Background()->pixmapForTiled(); + auto left = clip.left(); + auto top = clip.top(); + auto right = clip.left() + clip.width(); + auto bottom = clip.top() + clip.height(); + auto w = pix.width() / cRetinaFactor(); + auto h = pix.height() / cRetinaFactor(); + auto sx = qFloor(left / w); + auto sy = qFloor((top - fromy) / h); + auto cx = qCeil(right / w); + auto cy = qCeil((bottom - fromy) / h); + for (auto i = sx; i < cx; ++i) { + for (auto j = sy; j < cy; ++j) { + p.drawPixmap(QPointF(i * w, fromy + j * h), pix); + } + } + } else { + PainterHighQualityEnabler hq(p); + + auto &pix = Window::Theme::Background()->pixmap(); + QRect to, from; + Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from); + to.moveTop(to.top() + fromy); + p.drawPixmap(to, pix, from); + } + } else { + p.drawPixmap(x, fromy + y, cached); + } +} + +void Widget::onScroll() { + int scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + +void Widget::showAnimatedHook() { + _fixedBar->setAnimatingMode(true); +} + +void Widget::showFinishedHook() { + _fixedBar->setAnimatingMode(false); +} + +bool Widget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return _scroll->viewportEvent(e); +} + +QRect Widget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return mapToGlobal(_scroll->geometry()); +} + +void Widget::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins) { + _inner->applyFilter(flags, admins); + _fixedBar->applyFilter(flags, admins); +} + +} // namespace AdminLog + diff --git a/Telegram/SourceFiles/history/history_admin_log_section.h b/Telegram/SourceFiles/history/history_admin_log_section.h new file mode 100644 index 0000000000..a0188ebecc --- /dev/null +++ b/Telegram/SourceFiles/history/history_admin_log_section.h @@ -0,0 +1,118 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "window/section_widget.h" +#include "window/section_memento.h" + +namespace Notify { +struct PeerUpdate; +} // namespace Notify + +namespace Ui { +class ScrollArea; +class PlainShadow; +class FlatButton; +} // namespace Ui + +namespace Profile { +class BackButton; +} // namespace Profile + +namespace AdminLog { + +class FixedBar; +class InnerWidget; +class SectionMemento; + +class Widget final : public Window::SectionWidget { +public: + Widget(QWidget *parent, gsl::not_null controller, gsl::not_null channel); + + gsl::not_null channel() const; + PeerData *peerForDialogs() const override { + return channel(); + } + + bool hasTopBarShadow() const override { + return true; + } + + QPixmap grabForShowAnimation(const Window::SectionSlideParams ¶ms) override; + + bool showInternal(const Window::SectionMemento *memento) override; + std::unique_ptr createMemento() const override; + + void setInternalState(const QRect &geometry, gsl::not_null memento); + + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + + // Empty "flags" means all events. Empty "admins" means all admins. + void applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook() override; + void showFinishedHook() override; + void doSetInnerFocus() override; + +private: + void onScroll(); + void updateAdaptiveLayout(); + void saveState(gsl::not_null memento) const; + void restoreState(gsl::not_null memento); + + object_ptr _scroll; + QPointer _inner; + object_ptr _fixedBar; + object_ptr _fixedBarShadow; + object_ptr _whatIsThis; + +}; + +class SectionMemento : public Window::SectionMemento { +public: + SectionMemento(gsl::not_null channel) : _channel(channel) { + } + + object_ptr createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const override; + + gsl::not_null getChannel() const { + return _channel; + } + void setScrollTop(int scrollTop) { + _scrollTop = scrollTop; + } + int getScrollTop() const { + return _scrollTop; + } + +private: + gsl::not_null _channel; + int _scrollTop = 0; + +}; + +} // namespace AdminLog diff --git a/Telegram/SourceFiles/profile/profile_back_button.cpp b/Telegram/SourceFiles/profile/profile_back_button.cpp index b514ddeec2..9293a388d3 100644 --- a/Telegram/SourceFiles/profile/profile_back_button.cpp +++ b/Telegram/SourceFiles/profile/profile_back_button.cpp @@ -35,6 +35,11 @@ BackButton::BackButton(QWidget *parent, const QString &text) : Ui::AbstractButto updateAdaptiveLayout(); } +void BackButton::setText(const QString &text) { + _text = text.toUpper(); + update(); +} + int BackButton::resizeGetHeight(int newWidth) { return st::profileTopBarHeight; } diff --git a/Telegram/SourceFiles/profile/profile_back_button.h b/Telegram/SourceFiles/profile/profile_back_button.h index f3dc47407b..c50cdde3b8 100644 --- a/Telegram/SourceFiles/profile/profile_back_button.h +++ b/Telegram/SourceFiles/profile/profile_back_button.h @@ -28,6 +28,8 @@ class BackButton final : public Ui::AbstractButton, private base::Subscriber { public: BackButton(QWidget *parent, const QString &text); + void setText(const QString &text); + protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/SourceFiles/profile/profile_block_channel_members.cpp b/Telegram/SourceFiles/profile/profile_block_channel_members.cpp index f3de98c94a..c57a277bdf 100644 --- a/Telegram/SourceFiles/profile/profile_block_channel_members.cpp +++ b/Telegram/SourceFiles/profile/profile_block_channel_members.cpp @@ -24,6 +24,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/buttons.h" #include "boxes/members_box.h" #include "observer_peer.h" +#include "mainwidget.h" +#include "history/history_admin_log_section.h" #include "lang/lang_keys.h" namespace Profile { @@ -70,23 +72,33 @@ void ChannelMembersWidget::addButton(const QString &text, object_ptr QString { + auto getAdminsText = [this] { if (auto channel = peer()->asChannel()) { if (!channel->isMegagroup() && channel->canViewAdmins()) { - int adminsCount = qMax(channel->adminsCount(), 1); + auto adminsCount = qMax(channel->adminsCount(), 1); return lng_channel_admins_link(lt_count, adminsCount); } } return QString(); }; addButton(getAdminsText(), &_admins, SLOT(onAdmins())); + + auto getRecentActionsText = [this] { + if (auto channel = peer()->asChannel()) { + if (!channel->isMegagroup() && (channel->hasAdminRights() || channel->amCreator())) { + return lang(lng_profile_recent_actions); + } + } + return QString(); + }; + addButton(getRecentActionsText(), &_recentActions, SLOT(onRecentActions())); } void ChannelMembersWidget::refreshMembers() { @@ -122,22 +134,31 @@ int ChannelMembersWidget::resizeGetHeight(int newWidth) { newHeight += button->height(); }; - resizeButton(_admins); resizeButton(_members); + resizeButton(_admins); + resizeButton(_recentActions); return newHeight; } -void ChannelMembersWidget::onAdmins() { - if (auto channel = peer()->asChannel()) { - Ui::show(Box(channel, MembersFilter::Admins)); - } -} - void ChannelMembersWidget::onMembers() { if (auto channel = peer()->asChannel()) { Ui::show(Box(channel, MembersFilter::Recent)); } } +void ChannelMembersWidget::onAdmins() { + if (auto channel = peer()->asChannel()) { + Ui::show(Box(channel, MembersFilter::Admins)); + } +} + +void ChannelMembersWidget::onRecentActions() { + if (auto channel = peer()->asChannel()) { + if (auto main = App::main()) { + main->showWideSection(AdminLog::SectionMemento(channel)); + } + } +} + } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_block_channel_members.h b/Telegram/SourceFiles/profile/profile_block_channel_members.h index c7af60d755..0bc23187b9 100644 --- a/Telegram/SourceFiles/profile/profile_block_channel_members.h +++ b/Telegram/SourceFiles/profile/profile_block_channel_members.h @@ -43,8 +43,9 @@ protected: int resizeGetHeight(int newWidth) override; private slots: - void onAdmins(); void onMembers(); + void onAdmins(); + void onRecentActions(); private: // Observed notifications. @@ -57,8 +58,9 @@ private: void addButton(const QString &text, object_ptr *button, const char *slot); - object_ptr _admins = { nullptr }; object_ptr _members = { nullptr }; + object_ptr _admins = { nullptr }; + object_ptr _recentActions = { nullptr }; }; diff --git a/Telegram/SourceFiles/profile/profile_block_settings.cpp b/Telegram/SourceFiles/profile/profile_block_settings.cpp index 3084df239a..7dc94fc8e6 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.cpp +++ b/Telegram/SourceFiles/profile/profile_block_settings.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "profile/profile_block_settings.h" #include "profile/profile_channel_controllers.h" +#include "history/history_admin_log_section.h" #include "styles/style_profile.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" @@ -96,6 +97,7 @@ int SettingsWidget::resizeGetHeight(int newWidth) { newHeight += button->height(); }; moveLink(_manageAdmins); + moveLink(_recentActions); moveLink(_manageBannedUsers); moveLink(_manageRestrictedUsers); moveLink(_inviteLink); @@ -136,6 +138,19 @@ void SettingsWidget::refreshManageAdminsButton() { _manageAdmins->show(); connect(_manageAdmins, SIGNAL(clicked()), this, SLOT(onManageAdmins())); } + + auto hasRecentActions = [this] { + if (auto channel = peer()->asMegagroup()) { + return channel->hasAdminRights() || channel->amCreator(); + } + return false; + }; + _recentActions.destroy(); + if (hasRecentActions()) { + _recentActions.create(this, lang(lng_profile_recent_actions), st::defaultLeftOutlineButton); + _recentActions->show(); + connect(_recentActions, SIGNAL(clicked()), this, SLOT(onRecentActions())); + } } void SettingsWidget::refreshManageBannedUsersButton() { @@ -201,6 +216,14 @@ void SettingsWidget::onManageAdmins() { } } +void SettingsWidget::onRecentActions() { + if (auto channel = peer()->asChannel()) { + if (auto main = App::main()) { + main->showWideSection(AdminLog::SectionMemento(channel)); + } + } +} + void SettingsWidget::onManageBannedUsers() { if (auto channel = peer()->asMegagroup()) { ParticipantsBoxController::Start(channel, ParticipantsBoxController::Role::Kicked); diff --git a/Telegram/SourceFiles/profile/profile_block_settings.h b/Telegram/SourceFiles/profile/profile_block_settings.h index 7058562d68..bdbd221e30 100644 --- a/Telegram/SourceFiles/profile/profile_block_settings.h +++ b/Telegram/SourceFiles/profile/profile_block_settings.h @@ -46,6 +46,7 @@ protected: private slots: void onNotificationsChange(); void onManageAdmins(); + void onRecentActions(); void onManageBannedUsers(); void onManageRestrictedUsers(); void onInviteLink(); @@ -65,6 +66,7 @@ private: // In groups: creator of non-deactivated groups can see this link. // In channels: creator of supergroup can see this link. object_ptr _manageAdmins = { nullptr }; + object_ptr _recentActions = { nullptr }; object_ptr _manageBannedUsers = { nullptr }; object_ptr _manageRestrictedUsers = { nullptr }; object_ptr _inviteLink = { nullptr }; diff --git a/Telegram/SourceFiles/profile/profile_block_shared_media.cpp b/Telegram/SourceFiles/profile/profile_block_shared_media.cpp index 8052a61e54..b6fb131c40 100644 --- a/Telegram/SourceFiles/profile/profile_block_shared_media.cpp +++ b/Telegram/SourceFiles/profile/profile_block_shared_media.cpp @@ -201,7 +201,7 @@ void SharedMediaWidget::onShowCommonGroups() { return; } if (auto main = App::main()) { - main->showWideSection(Profile::CommonGroups::SectionMemento(peer())); + main->showWideSection(Profile::CommonGroups::SectionMemento(peer()->asUser())); } } diff --git a/Telegram/SourceFiles/profile/profile_common_groups_section.cpp b/Telegram/SourceFiles/profile/profile_common_groups_section.cpp index d05670aeae..26835862b4 100644 --- a/Telegram/SourceFiles/profile/profile_common_groups_section.cpp +++ b/Telegram/SourceFiles/profile/profile_common_groups_section.cpp @@ -43,7 +43,7 @@ constexpr int kCommonGroupsPerPage = 40; } // namespace object_ptr SectionMemento::createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const { - auto result = object_ptr(parent, controller, _peer); + auto result = object_ptr(parent, controller, _user); result->setInternalState(geometry, this); return std::move(result); } @@ -97,8 +97,8 @@ InnerWidget::Item::Item(PeerData *peer) : peer(peer) { InnerWidget::Item::~Item() = default; -InnerWidget::InnerWidget(QWidget *parent, PeerData *peer) : TWidget(parent) -, _peer(peer) { +InnerWidget::InnerWidget(QWidget *parent, gsl::not_null user) : TWidget(parent) +, _user(user) { setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); _rowHeight = st::profileCommonGroupsPadding.top() + st::profileCommonGroupsPhotoSize + st::profileCommonGroupsPadding.bottom(); @@ -120,7 +120,7 @@ void InnerWidget::checkPreloadMore() { void InnerWidget::saveState(SectionMemento *memento) const { if (auto count = _items.size()) { - QList groups; + QList> groups; groups.reserve(count); for_const (auto item, _items) { groups.push_back(item->peer); @@ -137,7 +137,7 @@ void InnerWidget::restoreState(const SectionMemento *memento) { } } -void InnerWidget::showInitial(const QList &list) { +void InnerWidget::showInitial(const QList> &list) { for_const (auto group, list) { if (auto item = computeItem(group)) { _items.push_back(item); @@ -151,9 +151,7 @@ void InnerWidget::preloadMore() { if (_preloadRequestId || _allLoaded) { return; } - auto user = peer()->asUser(); - t_assert(user != nullptr); - auto request = MTPmessages_GetCommonChats(user->inputUser, MTP_int(_preloadGroupId), MTP_int(kCommonGroupsPerPage)); + auto request = MTPmessages_GetCommonChats(user()->inputUser, MTP_int(_preloadGroupId), MTP_int(kCommonGroupsPerPage)); _preloadRequestId = MTP::send(request, ::rpcDone(base::lambda_guarded(this, [this](const MTPmessages_Chats &result) { _preloadRequestId = 0; _preloadGroupId = 0; @@ -337,7 +335,7 @@ InnerWidget::~InnerWidget() { } } -Widget::Widget(QWidget *parent, gsl::not_null controller, PeerData *peer) : Window::SectionWidget(parent, controller) +Widget::Widget(QWidget *parent, gsl::not_null controller, gsl::not_null user) : Window::SectionWidget(parent, controller) , _scroll(this, st::settingsScroll) , _fixedBar(this) , _fixedBarShadow(this, st::shadowFg) { @@ -349,7 +347,7 @@ Widget::Widget(QWidget *parent, gsl::not_null controller, P updateAdaptiveLayout(); subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); - _inner = _scroll->setOwnedWidget(object_ptr(this, peer)); + _inner = _scroll->setOwnedWidget(object_ptr(this, user)); _scroll->move(0, _fixedBar->height()); _scroll->show(); @@ -361,8 +359,8 @@ void Widget::updateAdaptiveLayout() { _fixedBarShadow->moveToLeft(Adaptive::OneColumn() ? 0 : st::lineWidth, _fixedBar->height()); } -PeerData *Widget::peer() const { - return _inner->peer(); +gsl::not_null Widget::user() const { + return _inner->user(); } QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { @@ -378,7 +376,7 @@ void Widget::doSetInnerFocus() { bool Widget::showInternal(const Window::SectionMemento *memento) { if (auto profileMemento = dynamic_cast(memento)) { - if (profileMemento->getPeer() == peer()) { + if (profileMemento->getUser() == user()) { restoreState(profileMemento); return true; } @@ -393,7 +391,7 @@ void Widget::setInternalState(const QRect &geometry, const SectionMemento *memen } std::unique_ptr Widget::createMemento() const { - auto result = std::make_unique(peer()); + auto result = std::make_unique(user()); saveState(result.get()); return std::move(result); } diff --git a/Telegram/SourceFiles/profile/profile_common_groups_section.h b/Telegram/SourceFiles/profile/profile_common_groups_section.h index a46c0ec2e6..6eea2da6a0 100644 --- a/Telegram/SourceFiles/profile/profile_common_groups_section.h +++ b/Telegram/SourceFiles/profile/profile_common_groups_section.h @@ -40,13 +40,13 @@ namespace CommonGroups { class SectionMemento : public Window::SectionMemento { public: - SectionMemento(PeerData *peer) : _peer(peer) { + SectionMemento(gsl::not_null user) : _user(user) { } object_ptr createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const override; - PeerData *getPeer() const { - return _peer; + gsl::not_null getUser() const { + return _user; } void setScrollTop(int scrollTop) { _scrollTop = scrollTop; @@ -54,17 +54,17 @@ public: int getScrollTop() const { return _scrollTop; } - void setCommonGroups(const QList &groups) { + void setCommonGroups(const QList> &groups) { _commonGroups = groups; } - const QList &getCommonGroups() const { + const QList> &getCommonGroups() const { return _commonGroups; } private: - PeerData *_peer; + gsl::not_null _user; int _scrollTop = 0; - QList _commonGroups; + QList> _commonGroups; }; @@ -96,10 +96,10 @@ class InnerWidget final : public TWidget { Q_OBJECT public: - InnerWidget(QWidget *parent, PeerData *peer); + InnerWidget(QWidget *parent, gsl::not_null user); - PeerData *peer() const { - return _peer; + gsl::not_null user() const { + return _user; } // Updates the area that is visible inside the scroll container. @@ -131,13 +131,13 @@ protected: private: void updateSelected(QPoint localPos); void updateRow(int index); - void showInitial(const QList &list); + void showInitial(const QList> &list); void checkPreloadMore(); void preloadMore(); void updateSize(); void paintRow(Painter &p, int index, TimeMs ms); - PeerData *_peer; + gsl::not_null _user; int _minHeight = 0; int _rowHeight = 0; @@ -171,11 +171,11 @@ class Widget final : public Window::SectionWidget { Q_OBJECT public: - Widget(QWidget *parent, gsl::not_null controller, PeerData *peer); + Widget(QWidget *parent, gsl::not_null controller, gsl::not_null user); - PeerData *peer() const; + gsl::not_null user() const; PeerData *peerForDialogs() const override { - return peer(); + return user(); } bool hasTopBarShadow() const override { diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 6bf05676f4..72dfc06b71 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -126,6 +126,10 @@ protected: setFocus(); } + bool animating() const { + return _showAnimation != nullptr; + } + private: void showFinished(); diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 8bf6759541..f042062ff3 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -141,6 +141,10 @@ <(src_loc)/dialogs/dialogs_list.h <(src_loc)/dialogs/dialogs_row.cpp <(src_loc)/dialogs/dialogs_row.h +<(src_loc)/history/history_admin_log_inner.cpp +<(src_loc)/history/history_admin_log_inner.h +<(src_loc)/history/history_admin_log_section.cpp +<(src_loc)/history/history_admin_log_section.h <(src_loc)/history/history_common.h <(src_loc)/history/history_drag_area.cpp <(src_loc)/history/history_drag_area.h