diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index f28c9e6ca2..68109d537d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1322,8 +1322,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "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_filter_members_removed" = "Leaving members"; +"lng_admin_log_filter_all_admins" = "All users and 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"; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index 9a8b4817fc..ff5b92c66f 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -631,3 +631,17 @@ changePhoneLabel: FlatLabel(defaultFlatLabel) { changePhoneError: FlatLabel(changePhoneLabel) { textFg: boxTextFgError; } + +adminLogFilterUserpicLeft: 15px; +adminLogFilterLittleSkip: 16px; +adminLogFilterCheckbox: Checkbox(defaultBoxCheckbox) { + style: TextStyle(boxTextStyle) { + font: font(boxFontSize semibold); + linkFont: font(boxFontSize semibold); + linkFontOver: font(boxFontSize semibold underline); + } +} +adminLogFilterSkip: 32px; +adminLogFilterUserCheckbox: Checkbox(defaultBoxCheckbox) { + margin: margins(8px, 6px, 8px, 6px); +} diff --git a/Telegram/SourceFiles/history/history_admin_log_filter.cpp b/Telegram/SourceFiles/history/history_admin_log_filter.cpp new file mode 100644 index 0000000000..12678afe2e --- /dev/null +++ b/Telegram/SourceFiles/history/history_admin_log_filter.cpp @@ -0,0 +1,413 @@ +/* +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_filter.h" + +#include "styles/style_boxes.h" +#include "ui/widgets/checkbox.h" +#include "ui/effects/ripple_animation.h" +#include "lang/lang_keys.h" + +namespace AdminLog { +namespace { + +class UserCheckbox : public Ui::RippleButton { +public: + UserCheckbox(QWidget *parent, gsl::not_null user, bool checked, base::lambda changedCallback); + + bool checked() const { + return _checked; + } + enum class NotifyAboutChange { + Notify, + DontNotify, + }; + void setChecked(bool checked, NotifyAboutChange notify = NotifyAboutChange::Notify); + + void finishAnimations(); + + QMargins getMargins() const override { + return _st.margin; + } + +protected: + void paintEvent(QPaintEvent *e) override; + + int resizeGetHeight(int newWidth) override; + + QImage prepareRippleMask() const override; + QPoint prepareRippleStartPosition() const override; + +private: + const style::Checkbox &_st; + + QRect _checkRect; + + bool _checked = false; + Animation _a_checked; + + gsl::not_null _user; + base::lambda _changedCallback; + QString _statusText; + bool _statusOnline = false; + +}; + +UserCheckbox::UserCheckbox(QWidget *parent, gsl::not_null user, bool checked, base::lambda changedCallback) : Ui::RippleButton(parent, st::defaultBoxCheckbox.ripple) +, _st(st::adminLogFilterUserCheckbox) +, _checked(checked) +, _user(user) +, _changedCallback(std::move(changedCallback)) { + setCursor(style::cur_pointer); + setClickedCallback([this] { + if (isDisabled()) return; + setChecked(!this->checked()); + }); + auto now = unixtime(); + _statusText = App::onlineText(_user, now); + _statusOnline = App::onlineColorUse(_user, now); + _checkRect = myrtlrect(_st.margin.left(), (st::contactsPhotoSize - _st.diameter) / 2, _st.diameter, _st.diameter); +} + +void UserCheckbox::setChecked(bool checked, NotifyAboutChange notify) { + if (_checked != checked) { + _checked = checked; + _a_checked.start([this] { update(_checkRect); }, _checked ? 0. : 1., _checked ? 1. : 0., _st.duration); + if (notify == NotifyAboutChange::Notify && _changedCallback) { + _changedCallback(); + } + } +} + +void UserCheckbox::paintEvent(QPaintEvent *e) { + Painter p(this); + + auto ms = getms(); + auto active = _a_checked.current(ms, _checked ? 1. : 0.); + auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active); + paintRipple(p, _st.rippleAreaPosition.x(), _st.rippleAreaPosition.y() + (_checkRect.y() - st::defaultBoxCheckbox.margin.top()), ms, &color); + + if (_checkRect.intersects(e->rect())) { + auto pen = anim::pen(_st.checkFg, _st.checkFgActive, active); + pen.setWidth(_st.thickness); + p.setPen(pen); + p.setBrush(anim::brush(_st.checkBg, anim::color(_st.checkFg, _st.checkFgActive, active), active)); + + { + PainterHighQualityEnabler hq(p); + p.drawRoundedRect(QRectF(_checkRect).marginsRemoved(QMarginsF(_st.thickness / 2., _st.thickness / 2., _st.thickness / 2., _st.thickness / 2.)), st::buttonRadius - (_st.thickness / 2.), st::buttonRadius - (_st.thickness / 2.)); + } + + if (active > 0) { + _st.checkIcon.paint(p, _checkRect.topLeft(), width()); + } + } + + auto userpicLeft = _checkRect.x() + _checkRect.width() + st::adminLogFilterUserpicLeft; + auto userpicTop = 0; + _user->paintUserpicLeft(p, userpicLeft, userpicTop, width(), st::contactsPhotoSize); + + auto nameLeft = userpicLeft + st::contactsPhotoSize + st::contactsPadding.left(); + auto nameTop = userpicTop + st::contactsNameTop; + auto nameWidth = width() - nameLeft - st::contactsPadding.right(); + p.setPen(st::contactsNameFg); + _user->nameText.drawLeftElided(p, nameLeft, nameTop, nameWidth, width()); + + auto statusLeft = nameLeft; + auto statusTop = userpicTop + st::contactsStatusTop; + p.setFont(st::contactsStatusFont); + p.setPen(_statusOnline ? st::contactsStatusFgOnline : st::contactsStatusFg); + p.drawTextLeft(statusLeft, statusTop, width(), _statusText); +} + +void UserCheckbox::finishAnimations() { + _a_checked.finish(); +} + +int UserCheckbox::resizeGetHeight(int newWidth) { + return st::contactsPhotoSize; +} + +QImage UserCheckbox::prepareRippleMask() const { + return Ui::RippleAnimation::ellipseMask(QSize(_st.rippleAreaSize, _st.rippleAreaSize)); +} + +QPoint UserCheckbox::prepareRippleStartPosition() const { + auto position = mapFromGlobal(QCursor::pos()) - _st.rippleAreaPosition - QPoint(0, _checkRect.y() - st::defaultBoxCheckbox.margin.top()); + if (QRect(0, 0, _st.rippleAreaSize, _st.rippleAreaSize).contains(position)) { + return position; + } + return disabledRippleStartPosition(); +} + +} // namespace + +class FilterBox::Inner : public TWidget { +public: + Inner(QWidget *parent, gsl::not_null channel, const std::vector> &admins, const FilterValue &filter, base::lambda changedCallback); + + template + QPointer addRow(object_ptr widget, int marginTop) { + widget->setParent(this); + widget->show(); + auto row = Row(); + row.widget = std::move(widget); + row.marginTop = marginTop; + _rows.push_back(std::move(row)); + return static_cast(_rows.back().widget.data()); + } + + bool canSave() const; + FilterValue filter() const; + +protected: + int resizeGetHeight(int newWidth) override; + void resizeEvent(QResizeEvent *e) override; + +private: + void createControls(const std::vector> &admins, const FilterValue &filter); + void createAllActionsCheckbox(const FilterValue &filter); + void createActionsCheckboxes(const FilterValue &filter); + void createAllUsersCheckbox(const FilterValue &filter); + void createAdminsCheckboxes(const std::vector> &admins, const FilterValue &filter); + + gsl::not_null _channel; + + QPointer _allFlags; + QMap> _filterFlags; + + QPointer _allUsers; + QMap, QPointer> _admins; + bool _restoringInvariant = false; + + struct Row { + object_ptr widget = { nullptr }; + int marginTop = 0; + }; + std::vector _rows; + + base::lambda _changedCallback; + +}; + +FilterBox::Inner::Inner(QWidget *parent, gsl::not_null channel, const std::vector> &admins, const FilterValue &filter, base::lambda changedCallback) : TWidget(parent) +, _channel(channel) +, _changedCallback(std::move(changedCallback)) { + createControls(admins, filter); +} + +void FilterBox::Inner::createControls(const std::vector> &admins, const FilterValue &filter) { + createAllActionsCheckbox(filter); + createActionsCheckboxes(filter); + createAllUsersCheckbox(filter); + createAdminsCheckboxes(admins, filter); +} + +void FilterBox::Inner::createAllActionsCheckbox(const FilterValue &filter) { + auto checked = (filter.flags == 0); + _allFlags = addRow(object_ptr(this, lang(lng_admin_log_filter_all_actions), checked, st::adminLogFilterCheckbox), st::adminLogFilterCheckbox.margin.top()); + connect(_allFlags, &Ui::Checkbox::changed, this, [this] { + if (!std::exchange(_restoringInvariant, true)) { + auto allChecked = _allFlags->checked(); + for_const (auto &&checkbox, _filterFlags) { + checkbox->setChecked(allChecked); + } + _restoringInvariant = false; + if (_changedCallback) { + _changedCallback(); + } + } + }); +} + +void FilterBox::Inner::createActionsCheckboxes(const FilterValue &filter) { + using Flag = MTPDchannelAdminLogEventsFilter::Flag; + using Flags = MTPDchannelAdminLogEventsFilter::Flags; + auto addFlag = [this, &filter](Flags flag, QString &&text) { + auto checked = (filter.flags == 0) || (filter.flags & flag); + auto checkbox = addRow(object_ptr(this, std::move(text), checked, st::defaultBoxCheckbox), st::adminLogFilterLittleSkip); + _filterFlags.insert(flag, checkbox); + connect(checkbox, &Ui::Checkbox::changed, this, [this] { + if (!std::exchange(_restoringInvariant, true)) { + auto allChecked = true; + for_const (auto &&checkbox, _filterFlags) { + if (!checkbox->checked()) { + allChecked = false; + break; + } + } + _allFlags->setChecked(allChecked); + _restoringInvariant = false; + if (_changedCallback) { + _changedCallback(); + } + } + }); + }; + auto isGroup = _channel->isMegagroup(); + if (isGroup) { + addFlag(Flag::f_ban | Flag::f_unban | Flag::f_kick | Flag::f_unkick, lang(lng_admin_log_filter_restrictions)); + } + addFlag(Flag::f_promote | Flag::f_demote, lang(lng_admin_log_filter_admins_new)); + addFlag(Flag::f_join | Flag::f_invite, lang(lng_admin_log_filter_members_new)); + addFlag(Flag::f_info | Flag::f_settings, lang(_channel->isMegagroup() ? lng_admin_log_filter_info_group : lng_admin_log_filter_info_channel)); + addFlag(Flag::f_delete, lang(lng_admin_log_filter_messages_deleted)); + addFlag(Flag::f_edit, lang(lng_admin_log_filter_messages_edited)); + if (isGroup) { + addFlag(Flag::f_pinned, lang(lng_admin_log_filter_messages_pinned)); + } + addFlag(Flag::f_leave, lang(lng_admin_log_filter_members_removed)); +} + +void FilterBox::Inner::createAllUsersCheckbox(const FilterValue &filter) { + _allUsers = addRow(object_ptr(this, lang(lng_admin_log_filter_all_admins), filter.allUsers, st::adminLogFilterCheckbox), st::adminLogFilterSkip); + connect(_allUsers, &Ui::Checkbox::changed, this, [this] { + if (_allUsers->checked() && !std::exchange(_restoringInvariant, true)) { + for_const (auto &&checkbox, _admins) { + checkbox->setChecked(true); + } + _restoringInvariant = false; + if (_changedCallback) { + _changedCallback(); + } + } + }); +} + +void FilterBox::Inner::createAdminsCheckboxes(const std::vector> &admins, const FilterValue &filter) { + for (auto user : admins) { + auto checked = filter.allUsers || base::contains(filter.admins, user); + auto checkbox = addRow(object_ptr(this, user, checked, [this] { + if (!std::exchange(_restoringInvariant, true)) { + auto allChecked = true; + for_const (auto &&checkbox, _admins) { + if (!checkbox->checked()) { + allChecked = false; + break; + } + } + if (!allChecked) { + _allUsers->setChecked(allChecked); + } + _restoringInvariant = false; + if (_changedCallback) { + _changedCallback(); + } + } + }), st::adminLogFilterLittleSkip); + _admins.insert(user, checkbox); + } +} + +bool FilterBox::Inner::canSave() const { + for (auto i = _filterFlags.cbegin(), e = _filterFlags.cend(); i != e; ++i) { + if (i.value()->checked()) { + return true; + } + } + return false; +} + +FilterValue FilterBox::Inner::filter() const { + auto result = FilterValue(); + auto allChecked = true; + for (auto i = _filterFlags.cbegin(), e = _filterFlags.cend(); i != e; ++i) { + if (i.value()->checked()) { + result.flags |= i.key(); + } else { + allChecked = false; + } + } + if (allChecked) { + result.flags = 0; + } + result.allUsers = _allUsers->checked(); + if (!result.allUsers) { + result.admins.reserve(_admins.size()); + for (auto i = _admins.cbegin(), e = _admins.cend(); i != e; ++i) { + if (i.value()->checked()) { + result.admins.push_back(i.key()); + } + } + } + return result; +} + +int FilterBox::Inner::resizeGetHeight(int newWidth) { + auto newHeight = 0; + auto rowWidth = newWidth - st::boxPadding.left() - st::boxPadding.right(); + for (auto &&row : _rows) { + newHeight += row.marginTop; + row.widget->resizeToNaturalWidth(rowWidth); + newHeight += row.widget->heightNoMargins(); + } + return newHeight; +} + +void FilterBox::Inner::resizeEvent(QResizeEvent *e) { + auto top = 0; + for (auto &&row : _rows) { + top += row.marginTop; + row.widget->moveToLeft(st::boxPadding.left(), top); + top += row.widget->heightNoMargins(); + } +} + +FilterBox::FilterBox(QWidget*, gsl::not_null channel, const std::vector> &admins, const FilterValue &filter, base::lambda saveCallback) : BoxContent() +, _channel(channel) +, _admins(admins) +, _initialFilter(filter) +, _saveCallback(std::move(saveCallback)) { +} + +void FilterBox::prepare() { + setTitle(langFactory(lng_admin_log_filter_title)); + + _inner = setInnerWidget(object_ptr(this, _channel, _admins, _initialFilter, [this] { refreshButtons(); })); + _inner->resizeToWidth(st::boxWideWidth); + + refreshButtons(); + setDimensions(st::boxWideWidth, _inner->height()); +} + +void FilterBox::refreshButtons() { + clearButtons(); + if (_inner->canSave()) { + addButton(langFactory(lng_settings_save), [this] { + if (_saveCallback) { + _saveCallback(_inner->filter()); + } + }); + } + addButton(langFactory(lng_cancel), [this] { closeBox(); }); +} + +template +QPointer FilterBox::addControl(object_ptr row) { + Expects(_inner != nullptr); + return _inner->addControl(std::move(row)); +} + +void FilterBox::resizeToContent() { + _inner->resizeToWidth(st::boxWideWidth); + setDimensions(_inner->width(), _inner->height()); +} + +} // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history_admin_log_filter.h b/Telegram/SourceFiles/history/history_admin_log_filter.h new file mode 100644 index 0000000000..6c89a537f5 --- /dev/null +++ b/Telegram/SourceFiles/history/history_admin_log_filter.h @@ -0,0 +1,49 @@ +/* +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 "boxes/abstract_box.h" +#include "history/history_admin_log_section.h" + +namespace AdminLog { + +class FilterBox : public BoxContent { +public: + FilterBox(QWidget*, gsl::not_null channel, const std::vector> &admins, const FilterValue &filter, base::lambda saveCallback); + +protected: + void prepare() override; + +private: + void resizeToContent(); + void refreshButtons(); + + gsl::not_null _channel; + std::vector> _admins; + FilterValue _initialFilter; + base::lambda _saveCallback; + + class Inner; + QPointer _inner; + +}; + +} // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/history_admin_log_inner.cpp index c024751cb5..047c2b358e 100644 --- a/Telegram/SourceFiles/history/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/history_admin_log_inner.cpp @@ -312,16 +312,21 @@ void InnerWidget::checkPreloadMore() { } } -void InnerWidget::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins) { - _filterFlags = flags; - _filterAdmins = admins; - updateEmptyText(); +void InnerWidget::applyFilter(FilterValue &&value) { + _filter = value; + request(base::take(_preloadUpRequestId)).cancel(); + request(base::take(_preloadDownRequestId)).cancel(); + _filterChanged = true; + _upLoaded = false; + _downLoaded = true; + updateMinMaxIds(); + preloadMore(Direction::Up); } void InnerWidget::updateEmptyText() { auto options = _defaultOptions; options.flags |= TextParseMono; // For italic :/ - auto hasFilter = (_filterFlags != 0) || !_filterAdmins.empty(); + auto hasFilter = (_filter.flags != 0) || !_filter.allUsers; auto text = TextWithEntities { lang(hasFilter ? lng_admin_log_no_results_title : lng_admin_log_no_events_title) }; text.entities.append(EntityInText(EntityInTextBold, 0, text.text.size())); text.text.append(qstr("\n\n") + lang(hasFilter ? lng_admin_log_no_results_text : lng_admin_log_no_events_text)); @@ -351,8 +356,11 @@ QPoint InnerWidget::tooltipPos() const { } void InnerWidget::saveState(gsl::not_null memento) { - memento->setItems(std::move(_items), std::move(_itemsByIds), _upLoaded, _downLoaded); - memento->setIdManager(std::move(_idManager)); + memento->setFilter(std::move(_filter)); + if (!_filterChanged) { + memento->setItems(std::move(_items), std::move(_itemsByIds), _upLoaded, _downLoaded); + memento->setIdManager(std::move(_idManager)); + } _upLoaded = _downLoaded = true; // Don't load or handle anything anymore. } @@ -360,8 +368,10 @@ void InnerWidget::restoreState(gsl::not_null memento) { _items = memento->takeItems(); _itemsByIds = memento->takeItemsByIds(); _idManager = memento->takeIdManager(); + _filter = memento->takeFilter(); _upLoaded = memento->upLoaded(); _downLoaded = memento->downLoaded(); + _filterChanged = false; updateMinMaxIds(); updateSize(); } @@ -374,15 +384,17 @@ void InnerWidget::preloadMore(Direction direction) { } auto flags = MTPchannels_GetAdminLog::Flags(0); - auto filter = MTP_channelAdminLogEventsFilter(MTP_flags(_filterFlags)); - if (_filterFlags != 0) { + auto filter = MTP_channelAdminLogEventsFilter(MTP_flags(_filter.flags)); + if (_filter.flags != 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); + if (!_filter.allUsers) { + if (!_filter.admins.empty()) { + admins.reserve(_filter.admins.size()); + for (auto &admin : _filter.admins) { + admins.push_back(admin->inputUser); + } } flags |= MTPchannels_GetAdminLog::Flag::f_admins; } @@ -401,6 +413,11 @@ void InnerWidget::preloadMore(Direction direction) { if (loadedFlag) { return; } + + if (_filterChanged) { + clearAfterFilterChange(); + } + auto &events = results.vevents.v; if (!events.empty()) { auto oldItemsCount = _items.size(); @@ -459,7 +476,7 @@ void InnerWidget::preloadMore(Direction direction) { } void InnerWidget::updateMinMaxIds() { - if (_itemsByIds.empty()) { + if (_itemsByIds.empty() || _filterChanged) { _maxId = _minId = 0; } else { _maxId = (--_itemsByIds.end())->first; @@ -585,6 +602,22 @@ void InnerWidget::paintEvent(QPaintEvent *e) { } } +void InnerWidget::clearAfterFilterChange() { + _visibleTopItem = nullptr; + _visibleTopFromItem = 0; + _scrollDateLastItem = nullptr; + _scrollDateLastItemTop = 0; + _mouseActionItem = nullptr; + _selectedItem = nullptr; + _selectedText = TextSelection(); + _filterChanged = false; + _items.clear(); + _itemsByIds.clear(); + _idManager = LocalIdManager(); + updateEmptyText(); + updateSize(); +} + void InnerWidget::paintEmpty(Painter &p) { style::font font(st::msgServiceFont); auto rectWidth = st::historyAdminLogEmptyWidth; diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.h b/Telegram/SourceFiles/history/history_admin_log_inner.h index 7c25cc430b..3675fc719b 100644 --- a/Telegram/SourceFiles/history/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/history_admin_log_inner.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "history/history_admin_log_item.h" +#include "history/history_admin_log_section.h" #include "ui/widgets/tooltip.h" #include "mtproto/sender.h" #include "base/timer.h" @@ -37,26 +38,6 @@ namespace AdminLog { class SectionMemento; -class LocalIdManager { -public: - LocalIdManager() = default; - LocalIdManager(const LocalIdManager &other) = delete; - LocalIdManager &operator=(const LocalIdManager &other) = delete; - LocalIdManager(LocalIdManager &&other) : _counter(std::exchange(other._counter, ServerMaxMsgId)) { - } - LocalIdManager &operator=(LocalIdManager &&other) { - _counter = std::exchange(other._counter, ServerMaxMsgId); - return *this; - } - MsgId next() { - return ++_counter; - } - -private: - MsgId _counter = ServerMaxMsgId; - -}; - class InnerWidget final : public TWidget, public Ui::AbstractTooltipShower, private MTP::Sender, private base::Subscriber { public: InnerWidget(QWidget *parent, gsl::not_null controller, gsl::not_null channel, base::lambda scrollTo); @@ -82,8 +63,11 @@ public: _cancelledCallback = std::move(callback); } - // Empty "flags" means all events. Empty "admins" means all admins. - void applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins); + // Empty "flags" means all events. + void applyFilter(FilterValue &&value); + FilterValue filter() const { + return _filter; + } // AbstractTooltipShower interface QString tooltipText() const override; @@ -154,6 +138,7 @@ private: void updateMinMaxIds(); void updateEmptyText(); void paintEmpty(Painter &p); + void clearAfterFilterChange(); void toggleScrollDateShown(); void repaintScrollDateCallback(); @@ -219,6 +204,7 @@ private: // Don't load anything until the memento was read. bool _upLoaded = true; bool _downLoaded = true; + bool _filterChanged = false; Text _emptyText; MouseAction _mouseAction = MouseAction::None; @@ -243,8 +229,7 @@ private: ClickHandlerPtr _contextMenuLink; - MTPDchannelAdminLogEventsFilter::Flags _filterFlags = 0; - std::vector> _filterAdmins; + FilterValue _filter; }; diff --git a/Telegram/SourceFiles/history/history_admin_log_section.cpp b/Telegram/SourceFiles/history/history_admin_log_section.cpp index 25d6ed4e8a..cb8abe4ed9 100644 --- a/Telegram/SourceFiles/history/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/history_admin_log_section.cpp @@ -21,6 +21,7 @@ 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 "history/history_admin_log_filter.h" #include "profile/profile_back_button.h" #include "styles/style_history.h" #include "styles/style_window.h" @@ -36,16 +37,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace AdminLog { +// If we require to support more admins we'll have to rewrite this anyway. +constexpr auto kMaxChannelAdmins = 200; + class FixedBar final : public TWidget, private base::Subscriber { public: - FixedBar(QWidget *parent); + FixedBar(QWidget *parent, gsl::not_null channel, base::lambda showFilterCallback); // 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 applyFilter(const FilterValue &value); void goBack(); protected: @@ -54,6 +57,7 @@ protected: int resizeGetHeight(int newWidth) override; private: + gsl::not_null _channel; object_ptr _backButton; object_ptr _filter; @@ -67,17 +71,17 @@ object_ptr SectionMemento::createWidget(QWidget *parent, return std::move(result); } -FixedBar::FixedBar(QWidget *parent) : TWidget(parent) +FixedBar::FixedBar(QWidget *parent, gsl::not_null channel, base::lambda showFilterCallback) : TWidget(parent) +, _channel(channel) , _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] {}); - _filter->hide(); + _filter->setClickedCallback([this, showFilterCallback] { showFilterCallback(); }); } -void FixedBar::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins) { - auto hasFilter = (flags != 0) || !admins.empty(); +void FixedBar::applyFilter(const FilterValue &value) { + auto hasFilter = (value.flags != 0) || !value.allUsers; _backButton->setText(lang(hasFilter ? lng_admin_log_title_selected : lng_admin_log_title_all)); } @@ -89,7 +93,7 @@ int FixedBar::resizeGetHeight(int newWidth) { auto newHeight = 0; auto buttonLeft = newWidth; - //buttonLeft -= _filter->width(); _filter->moveToLeft(buttonLeft, 0); + buttonLeft -= _filter->width(); _filter->moveToLeft(buttonLeft, 0); _backButton->resizeToWidth(buttonLeft); _backButton->moveToLeft(0, 0); newHeight += _backButton->height(); @@ -107,7 +111,6 @@ void FixedBar::setAnimatingMode(bool enabled) { } else { setAttribute(Qt::WA_OpaquePaintEvent); showChildren(); - _filter->hide(); } show(); } @@ -130,7 +133,7 @@ void FixedBar::mousePressEvent(QMouseEvent *e) { Widget::Widget(QWidget *parent, gsl::not_null controller, gsl::not_null channel) : Window::SectionWidget(parent, controller) , _scroll(this, st::historyScroll, false) -, _fixedBar(this) +, _fixedBar(this, channel, [this] { showFilter(); }) , _fixedBarShadow(this, st::shadowFg) , _whatIsThis(this, lang(lng_admin_log_about).toUpper(), st::historyComposeButton) { _fixedBar->move(0, 0); @@ -151,6 +154,40 @@ Widget::Widget(QWidget *parent, gsl::not_null controller, g _whatIsThis->setClickedCallback([this] { Ui::show(Box(lang(lng_admin_log_about_text))); }); } +void Widget::showFilter() { + if (_admins.empty()) { + request(MTPchannels_GetParticipants(_inner->channel()->inputChannel, MTP_channelParticipantsAdmins(), MTP_int(0), MTP_int(kMaxChannelAdmins))).done([this](const MTPchannels_ChannelParticipants &result) { + Expects(result.type() == mtpc_channels_channelParticipants); + auto &participants = result.c_channels_channelParticipants(); + App::feedUsers(participants.vusers); + for (auto &participant : participants.vparticipants.v) { + auto getUserId = [&participant] { + switch (participant.type()) { + case mtpc_channelParticipant: return participant.c_channelParticipant().vuser_id.v; + case mtpc_channelParticipantSelf: return participant.c_channelParticipantSelf().vuser_id.v; + case mtpc_channelParticipantAdmin: return participant.c_channelParticipantAdmin().vuser_id.v; + case mtpc_channelParticipantCreator: return participant.c_channelParticipantCreator().vuser_id.v; + case mtpc_channelParticipantBanned: return participant.c_channelParticipantBanned().vuser_id.v; + default: Unexpected("Type in AdminLog::Widget::showFilter()"); + } + }; + if (auto user = App::userLoaded(getUserId())) { + _admins.push_back(user); + } + } + if (_admins.empty()) { + _admins.push_back(App::self()); + } + showFilter(); + }).send(); + } else { + Ui::show(Box(_inner->channel(), _admins, _inner->filter(), [this](FilterValue &&filter) { + applyFilter(std::move(filter)); + Ui::hideLayer(); + })); + } +} + void Widget::updateAdaptiveLayout() { _fixedBarShadow->moveToLeft(Adaptive::OneColumn() ? 0 : st::lineWidth, _fixedBar->height()); } @@ -194,11 +231,13 @@ std::unique_ptr Widget::createMemento() { void Widget::saveState(gsl::not_null memento) { memento->setScrollTop(_scroll->scrollTop()); + memento->setAdmins(std::move(_admins)); _inner->saveState(memento); } void Widget::restoreState(gsl::not_null memento) { _inner->restoreState(memento); + _admins = memento->takeAdmins(); auto scrollTop = memento->getScrollTop(); _scroll->scrollToY(scrollTop); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); @@ -309,9 +348,9 @@ QRect Widget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerC return mapToGlobal(_scroll->geometry()); } -void Widget::applyFilter(MTPDchannelAdminLogEventsFilter::Flags flags, const std::vector> &admins) { - _inner->applyFilter(flags, admins); - _fixedBar->applyFilter(flags, admins); +void Widget::applyFilter(FilterValue &&value) { + _fixedBar->applyFilter(value); + _inner->applyFilter(std::move(value)); } } // namespace AdminLog diff --git a/Telegram/SourceFiles/history/history_admin_log_section.h b/Telegram/SourceFiles/history/history_admin_log_section.h index 6a8c29663a..31784feed7 100644 --- a/Telegram/SourceFiles/history/history_admin_log_section.h +++ b/Telegram/SourceFiles/history/history_admin_log_section.h @@ -23,7 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "window/section_widget.h" #include "window/section_memento.h" #include "history/history_admin_log_item.h" -#include "history/history_admin_log_inner.h" +#include "mtproto/sender.h" namespace Notify { struct PeerUpdate; @@ -45,7 +45,34 @@ class FixedBar; class InnerWidget; class SectionMemento; -class Widget final : public Window::SectionWidget { +struct FilterValue { + // Empty "flags" means all events. + MTPDchannelAdminLogEventsFilter::Flags flags = 0; + std::vector> admins; + bool allUsers = true; +}; + +class LocalIdManager { +public: + LocalIdManager() = default; + LocalIdManager(const LocalIdManager &other) = delete; + LocalIdManager &operator=(const LocalIdManager &other) = delete; + LocalIdManager(LocalIdManager &&other) : _counter(std::exchange(other._counter, ServerMaxMsgId)) { + } + LocalIdManager &operator=(LocalIdManager &&other) { + _counter = std::exchange(other._counter, ServerMaxMsgId); + return *this; + } + MsgId next() { + return ++_counter; + } + +private: + MsgId _counter = ServerMaxMsgId; + +}; + +class Widget final : public Window::SectionWidget, private MTP::Sender { public: Widget(QWidget *parent, gsl::not_null controller, gsl::not_null channel); @@ -69,8 +96,7 @@ public: 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); + void applyFilter(FilterValue &&value); protected: void resizeEvent(QResizeEvent *e) override; @@ -81,6 +107,7 @@ protected: void doSetInnerFocus() override; private: + void showFilter(); void onScroll(); void updateAdaptiveLayout(); void saveState(gsl::not_null memento); @@ -91,6 +118,7 @@ private: object_ptr _fixedBar; object_ptr _fixedBarShadow; object_ptr _whatIsThis; + std::vector> _admins; }; @@ -111,12 +139,22 @@ public: return _scrollTop; } + void setAdmins(std::vector> admins) { + _admins = std::move(admins); + } + std::vector> takeAdmins() { + return std::move(_admins); + } + void setItems(std::vector &&items, std::map &&itemsByIds, bool upLoaded, bool downLoaded) { _items = std::move(items); _itemsByIds = std::move(itemsByIds); _upLoaded = upLoaded; _downLoaded = downLoaded; } + void setFilter(FilterValue &&filter) { + _filter = std::move(filter); + } void setIdManager(LocalIdManager &&manager) { _idManager = std::move(manager); } @@ -135,15 +173,20 @@ public: bool downLoaded() const { return _downLoaded; } + FilterValue takeFilter() { + return std::move(_filter); + } private: gsl::not_null _channel; int _scrollTop = 0; + std::vector> _admins; std::vector _items; std::map _itemsByIds; bool _upLoaded = false; bool _downLoaded = true; LocalIdManager _idManager; + FilterValue _filter; }; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index bfc18fdced..fbe63ec54a 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -143,6 +143,8 @@ <(src_loc)/dialogs/dialogs_row.h <(src_loc)/history/history.cpp <(src_loc)/history/history.h +<(src_loc)/history/history_admin_log_filter.cpp +<(src_loc)/history/history_admin_log_filter.h <(src_loc)/history/history_admin_log_inner.cpp <(src_loc)/history/history_admin_log_inner.h <(src_loc)/history/history_admin_log_item.cpp