Add context menu support to info members list.

This commit is contained in:
John Preston 2017-10-22 20:06:57 +03:00
parent 856ca22aad
commit fb46c33d7f
15 changed files with 366 additions and 76 deletions

View File

@ -308,7 +308,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
} break; } break;
} }
} }
auto newPhotoId = 0; auto newPhotoId = PhotoId(0);
if (auto photo = App::feedPhoto(f.vchat_photo)) { if (auto photo = App::feedPhoto(f.vchat_photo)) {
newPhotoId = photo->id; newPhotoId = photo->id;
photo->peer = chat; photo->peer = chat;

View File

@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once #pragma once
#include <rpl/event_stream.h> #include <rpl/event_stream.h>
#include <rpl/filter.h>
#include "base/timer.h" #include "base/timer.h"
namespace Storage { namespace Storage {
@ -94,6 +95,45 @@ public:
rpl::producer<not_null<const HistoryItem*>> itemRemoved() const { rpl::producer<not_null<const HistoryItem*>> itemRemoved() const {
return _itemRemoved.events(); return _itemRemoved.events();
} }
using MegagroupParticipant = std::tuple<
not_null<ChannelData*>,
not_null<UserData*>>;
void removeMegagroupParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user) {
_megagroupParticipantRemoved.fire({ channel, user });
}
auto megagroupParticipantRemoved() const {
return _megagroupParticipantRemoved.events();
}
auto megagroupParticipantRemoved(
not_null<ChannelData*> channel) const {
return megagroupParticipantRemoved()
| rpl::filter([channel](auto updateChannel, auto user) {
return (updateChannel == channel);
})
| rpl::map([](auto updateChannel, auto user) {
return user;
});
}
void addNewMegagroupParticipant(
not_null<ChannelData*> channel,
not_null<UserData*> user) {
_megagroupParticipantAdded.fire({ channel, user });
}
auto megagroupParticipantAdded() const {
return _megagroupParticipantAdded.events();
}
auto megagroupParticipantAdded(
not_null<ChannelData*> channel) const {
return megagroupParticipantAdded()
| rpl::filter([channel](auto updateChannel, auto user) {
return (updateChannel == channel);
})
| rpl::map([](auto updateChannel, auto user) {
return user;
});
}
void copyFrom(const AuthSessionData &other) { void copyFrom(const AuthSessionData &other) {
_variables = other._variables; _variables = other._variables;
@ -207,6 +247,8 @@ private:
rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanged; rpl::event_stream<not_null<const HistoryItem*>> _itemLayoutChanged;
rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest; rpl::event_stream<not_null<const HistoryItem*>> _itemRepaintRequest;
rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved; rpl::event_stream<not_null<const HistoryItem*>> _itemRemoved;
rpl::event_stream<MegagroupParticipant> _megagroupParticipantRemoved;
rpl::event_stream<MegagroupParticipant> _megagroupParticipantAdded;
rpl::event_stream<bool> _thirdSectionInfoEnabledValue; rpl::event_stream<bool> _thirdSectionInfoEnabledValue;
bool _tabbedReplacedWithInfo = false; bool _tabbedReplacedWithInfo = false;

View File

@ -29,6 +29,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/multi_select.h" #include "ui/widgets/multi_select.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "ui/widgets/popup_menu.h"
#include "ui/effects/round_checkbox.h" #include "ui/effects/round_checkbox.h"
#include "ui/effects/ripple_animation.h" #include "ui/effects/ripple_animation.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
@ -863,7 +864,10 @@ void PeerListContent::leaveEventHook(QEvent *e) {
} }
void PeerListContent::mouseMoveEvent(QMouseEvent *e) { void PeerListContent::mouseMoveEvent(QMouseEvent *e) {
auto position = e->globalPos(); handleMouseMove(e->globalPos());
}
void PeerListContent::handleMouseMove(QPoint position) {
if (_mouseSelection || _lastMousePosition != position) { if (_mouseSelection || _lastMousePosition != position) {
_lastMousePosition = position; _lastMousePosition = position;
_mouseSelection = true; _mouseSelection = true;
@ -872,6 +876,7 @@ void PeerListContent::mouseMoveEvent(QMouseEvent *e) {
} }
void PeerListContent::mousePressEvent(QMouseEvent *e) { void PeerListContent::mousePressEvent(QMouseEvent *e) {
_pressButton = e->button();
_mouseSelection = true; _mouseSelection = true;
_lastMousePosition = e->globalPos(); _lastMousePosition = e->globalPos();
updateSelection(); updateSelection();
@ -896,12 +901,16 @@ void PeerListContent::mousePressEvent(QMouseEvent *e) {
} }
void PeerListContent::mouseReleaseEvent(QMouseEvent *e) { void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
mousePressReleased(e->button());
}
void PeerListContent::mousePressReleased(Qt::MouseButton button) {
updateRow(_pressed.index); updateRow(_pressed.index);
updateRow(_selected.index); updateRow(_selected.index);
auto pressed = _pressed; auto pressed = _pressed;
setPressed(Selected()); setPressed(Selected());
if (e->button() == Qt::LeftButton && pressed == _selected) { if (button == Qt::LeftButton && pressed == _selected) {
if (auto row = getRow(pressed.index)) { if (auto row = getRow(pressed.index)) {
if (pressed.action) { if (pressed.action) {
_controller->rowActionClicked(row); _controller->rowActionClicked(row);
@ -912,6 +921,41 @@ void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
} }
} }
void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
if (_menu) {
_menu->deleteLater();
_menu = nullptr;
}
if (_context.index.value >= 0) {
updateRow(_context.index);
_context = Selected();
}
if (e->reason() == QContextMenuEvent::Mouse) {
handleMouseMove(e->globalPos());
}
_context = _selected;
if (_pressButton != Qt::LeftButton) {
mousePressReleased(_pressButton);
}
if (auto row = getRow(_context.index)) {
_menu = _controller->rowContextMenu(row);
if (_menu) {
_menu->setDestroyedCallback(base::lambda_guarded(
this,
[this] {
updateRow(_context.index);
_context = Selected();
handleMouseMove(QCursor::pos());
}));
_menu->popup(e->globalPos());
e->accept();
}
}
}
void PeerListContent::setPressed(Selected pressed) { void PeerListContent::setPressed(Selected pressed) {
if (auto row = getRow(_pressed.index)) { if (auto row = getRow(_pressed.index)) {
row->stopLastRipple(); row->stopLastRipple();
@ -933,7 +977,11 @@ TimeMs PeerListContent::paintRow(Painter &p, TimeMs ms, RowIndex index) {
auto peer = row->peer(); auto peer = row->peer();
auto user = peer->asUser(); auto user = peer->asUser();
auto active = (_pressed.index.value >= 0) ? _pressed : _selected; auto active = (_context.index.value >= 0)
? _context
: (_pressed.index.value >= 0)
? _pressed
: _selected;
auto selected = (active.index == index); auto selected = (active.index == index);
auto actionSelected = (selected && active.action); auto actionSelected = (selected && active.action);

View File

@ -39,6 +39,7 @@ template <typename Widget>
class SlideWrap; class SlideWrap;
class FlatLabel; class FlatLabel;
struct ScrollToRequest; struct ScrollToRequest;
class PopupMenu;
} // namespace Ui } // namespace Ui
namespace Notify { namespace Notify {
@ -238,8 +239,8 @@ public:
virtual void peerListScrollToTop() = 0; virtual void peerListScrollToTop() = 0;
virtual int peerListFullRowsCount() = 0; virtual int peerListFullRowsCount() = 0;
virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0; virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0;
virtual void peerListSortRows(base::lambda<bool(PeerListRow &a, PeerListRow &b)> compare) = 0; virtual void peerListSortRows(base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
virtual int peerListPartitionRows(base::lambda<bool(PeerListRow &a)> border) = 0; virtual int peerListPartitionRows(base::lambda<bool(const PeerListRow &a)> border) = 0;
template <typename PeerDataRange> template <typename PeerDataRange>
void peerListAddSelectedRows(PeerDataRange &&range) { void peerListAddSelectedRows(PeerDataRange &&range) {
@ -306,6 +307,10 @@ public:
} }
virtual void itemDeselectedHook(not_null<PeerData*> peer) { virtual void itemDeselectedHook(not_null<PeerData*> peer) {
} }
virtual Ui::PopupMenu *rowContextMenu(
not_null<PeerListRow*> row) {
return nullptr;
}
bool isSearchLoading() const { bool isSearchLoading() const {
return _searchController ? _searchController->isLoading() : false; return _searchController ? _searchController->isLoading() : false;
} }
@ -429,6 +434,7 @@ protected:
void mouseMoveEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override;
void mousePressEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
private: private:
void refreshIndices(); void refreshIndices();
@ -505,6 +511,8 @@ private:
int labelHeight() const; int labelHeight() const;
void clearSearchRows(); void clearSearchRows();
void handleMouseMove(QPoint position);
void mousePressReleased(Qt::MouseButton button);
const style::PeerList &_st; const style::PeerList &_st;
not_null<PeerListController*> _controller; not_null<PeerListController*> _controller;
@ -516,7 +524,9 @@ private:
Selected _selected; Selected _selected;
Selected _pressed; Selected _pressed;
Selected _context;
bool _mouseSelection = false; bool _mouseSelection = false;
Qt::MouseButton _pressButton = Qt::LeftButton;
rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests; rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
@ -540,6 +550,7 @@ private:
std::vector<std::unique_ptr<PeerListRow>> _searchRows; std::vector<std::unique_ptr<PeerListRow>> _searchRows;
base::Timer _repaintByStatus; base::Timer _repaintByStatus;
QPointer<Ui::PopupMenu> _menu;
}; };
@ -615,7 +626,7 @@ public:
_content->setSearchMode(mode); _content->setSearchMode(mode);
} }
void peerListSortRows( void peerListSortRows(
base::lambda<bool(PeerListRow &a, PeerListRow &b)> compare) override { base::lambda<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
_content->reorderRows([compare = std::move(compare)]( _content->reorderRows([compare = std::move(compare)](
auto &&begin, auto &&begin,
auto &&end) { auto &&end) {
@ -625,7 +636,7 @@ public:
}); });
} }
int peerListPartitionRows( int peerListPartitionRows(
base::lambda<bool(PeerListRow &a)> border) override { base::lambda<bool(const PeerListRow &a)> border) override {
auto result = 0; auto result = 0;
_content->reorderRows([border = std::move(border), &result]( _content->reorderRows([border = std::move(border), &result](
auto &&begin, auto &&begin,

View File

@ -243,8 +243,8 @@ void ChatsListBoxController::rebuildRows() {
added += appendList(App::main()->contactsNoDialogsList()); added += appendList(App::main()->contactsNoDialogsList());
if (!wasEmpty && added > 0) { if (!wasEmpty && added > 0) {
// Place dialogs list before contactsNoDialogs list. // Place dialogs list before contactsNoDialogs list.
delegate()->peerListPartitionRows([](PeerListRow &a) { delegate()->peerListPartitionRows([](const PeerListRow &a) {
auto history = static_cast<Row&>(a).history(); auto history = static_cast<const Row&>(a).history();
return history->inChatList(Dialogs::Mode::All); return history->inChatList(Dialogs::Mode::All);
}); });
} }

View File

@ -303,8 +303,11 @@ bool BoxController::insertRow(
(way == InsertWay::Append) (way == InsertWay::Append)
? delegate()->peerListAppendRow(createRow(item)) ? delegate()->peerListAppendRow(createRow(item))
: delegate()->peerListPrependRow(createRow(item)); : delegate()->peerListPrependRow(createRow(item));
delegate()->peerListSortRows([](PeerListRow &a, PeerListRow &b) { delegate()->peerListSortRows([](
return static_cast<Row&>(a).maxItemId() > static_cast<Row&>(b).maxItemId(); const PeerListRow &a,
const PeerListRow &b) {
return static_cast<const Row&>(a).maxItemId()
> static_cast<const Row&>(b).maxItemId();
}); });
return true; return true;
} }

View File

@ -915,6 +915,7 @@ void ChannelData::applyEditBanned(not_null<UserData*> user, const MTPChannelBann
} }
} }
flags |= Notify::PeerUpdate::Flag::MembersChanged; flags |= Notify::PeerUpdate::Flag::MembersChanged;
Auth().data().removeMegagroupParticipant(this, user);
} }
} }
} }

View File

@ -99,7 +99,7 @@ template <
typename = typename PeerType::FullFlags::Change> typename = typename PeerType::FullFlags::Change>
inline auto PeerFullFlagsValue(PeerType *peer) { inline auto PeerFullFlagsValue(PeerType *peer) {
Expects(peer != nullptr); Expects(peer != nullptr);
return peer->flagsValue(); return peer->fullFlagsValue();
} }
template < template <

View File

@ -933,14 +933,17 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
switch (action.type()) { switch (action.type()) {
case mtpc_messageActionChatAddUser: { case mtpc_messageActionChatAddUser: {
auto &d = action.c_messageActionChatAddUser(); auto &d = action.c_messageActionChatAddUser();
if (peer->isMegagroup()) { if (auto megagroup = peer->asMegagroup()) {
auto mgInfo = megagroup->mgInfo.get();
Assert(mgInfo != nullptr);
auto &v = d.vusers.v; auto &v = d.vusers.v;
for (auto i = 0, l = v.size(); i != l; ++i) { for (auto i = 0, l = v.size(); i != l; ++i) {
if (auto user = App::userLoaded(peerFromUser(v[i]))) { if (auto user = App::userLoaded(peerFromUser(v[i]))) {
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(user) < 0) { if (mgInfo->lastParticipants.indexOf(user) < 0) {
peer->asChannel()->mgInfo->lastParticipants.push_front(user); mgInfo->lastParticipants.push_front(user);
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
Auth().data().addNewMegagroupParticipant(megagroup, user);
} }
if (user->botInfo) { if (user->botInfo) {
peer->asChannel()->mgInfo->bots.insert(user); peer->asChannel()->mgInfo->bots.insert(user);
@ -955,16 +958,19 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
case mtpc_messageActionChatJoinedByLink: { case mtpc_messageActionChatJoinedByLink: {
auto &d = action.c_messageActionChatJoinedByLink(); auto &d = action.c_messageActionChatJoinedByLink();
if (peer->isMegagroup()) { if (auto megagroup = peer->asMegagroup()) {
if (result->from()->isUser()) { auto mgInfo = megagroup->mgInfo.get();
if (peer->asChannel()->mgInfo->lastParticipants.indexOf(result->from()->asUser()) < 0) { Assert(mgInfo != nullptr);
peer->asChannel()->mgInfo->lastParticipants.push_front(result->from()->asUser()); if (auto user = result->from()->asUser()) {
if (mgInfo->lastParticipants.indexOf(user) < 0) {
mgInfo->lastParticipants.push_front(user);
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
Auth().data().addNewMegagroupParticipant(megagroup, user);
} }
if (result->from()->asUser()->botInfo) { if (user->botInfo) {
peer->asChannel()->mgInfo->bots.insert(result->from()->asUser()); mgInfo->bots.insert(user);
if (peer->asChannel()->mgInfo->botStatus != 0 && peer->asChannel()->mgInfo->botStatus < 2) { if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) {
peer->asChannel()->mgInfo->botStatus = 2; mgInfo->botStatus = 2;
} }
} }
} }
@ -982,32 +988,32 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
if (lastKeyboardFrom == uid) { if (lastKeyboardFrom == uid) {
clearLastKeyboard(); clearLastKeyboard();
} }
if (peer->isMegagroup()) { if (auto megagroup = peer->asMegagroup()) {
if (auto user = App::userLoaded(uid)) { if (auto user = App::userLoaded(uid)) {
auto channel = peer->asChannel(); auto mgInfo = megagroup->mgInfo.get();
auto &megagroupInfo = channel->mgInfo; Assert(mgInfo != nullptr);
auto index = mgInfo->lastParticipants.indexOf(user);
auto index = megagroupInfo->lastParticipants.indexOf(user);
if (index >= 0) { if (index >= 0) {
megagroupInfo->lastParticipants.removeAt(index); mgInfo->lastParticipants.removeAt(index);
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
} }
if (peer->asChannel()->membersCount() > 1) { Auth().data().removeMegagroupParticipant(megagroup, user);
peer->asChannel()->setMembersCount(channel->membersCount() - 1); if (megagroup->membersCount() > 1) {
megagroup->setMembersCount(megagroup->membersCount() - 1);
} else { } else {
megagroupInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated;
megagroupInfo->lastParticipantsCount = 0; mgInfo->lastParticipantsCount = 0;
} }
if (megagroupInfo->lastAdmins.contains(user)) { if (mgInfo->lastAdmins.contains(user)) {
megagroupInfo->lastAdmins.remove(user); mgInfo->lastAdmins.remove(user);
if (channel->adminsCount() > 1) { if (megagroup->adminsCount() > 1) {
channel->setAdminsCount(channel->adminsCount() - 1); megagroup->setAdminsCount(megagroup->adminsCount() - 1);
} }
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::AdminsChanged);
} }
megagroupInfo->bots.remove(user); mgInfo->bots.remove(user);
if (megagroupInfo->bots.isEmpty() && megagroupInfo->botStatus > 0) { if (mgInfo->bots.isEmpty() && mgInfo->botStatus > 0) {
megagroupInfo->botStatus = -1; mgInfo->botStatus = -1;
} }
} }
} }
@ -1294,26 +1300,29 @@ HistoryItem *History::addNewItem(HistoryItem *adding, bool newMsg) {
} }
return nullptr; return nullptr;
}; };
if (auto channel = peer->asMegagroup()) { if (auto megagroup = peer->asMegagroup()) {
if (adding->from()->asUser()->botInfo) { if (user->botInfo) {
channel->mgInfo->bots.insert(adding->from()->asUser()); auto mgInfo = megagroup->mgInfo.get();
if (channel->mgInfo->botStatus != 0 && channel->mgInfo->botStatus < 2) { Assert(mgInfo != nullptr);
channel->mgInfo->botStatus = 2; mgInfo->bots.insert(user);
if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) {
mgInfo->botStatus = 2;
} }
} }
} }
if (auto lastAuthors = getLastAuthors()) { if (auto lastAuthors = getLastAuthors()) {
int prev = lastAuthors->indexOf(adding->from()->asUser()); int prev = lastAuthors->indexOf(user);
if (prev > 0) { if (prev > 0) {
lastAuthors->removeAt(prev); lastAuthors->removeAt(prev);
} else if (prev < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering } else if (prev < 0 && peer->isMegagroup()) { // nothing is outdated if just reordering
peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated; peer->asChannel()->mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsAdminsOutdated;
} }
if (prev) { if (prev) {
lastAuthors->push_front(adding->from()->asUser()); lastAuthors->push_front(user);
} }
if (peer->isMegagroup()) { if (auto megagroup = peer->asMegagroup()) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged); Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::MembersChanged);
Auth().data().addNewMegagroupParticipant(megagroup, user);
} }
} }
} }

View File

@ -21,11 +21,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/profile/info_profile_members_controllers.h" #include "info/profile/info_profile_members_controllers.h"
#include <rpl/variable.h> #include <rpl/variable.h>
#include "base/weak_unique_ptr.h"
#include "profile/profile_channel_controllers.h" #include "profile/profile_channel_controllers.h"
#include "ui/widgets/popup_menu.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "auth_session.h" #include "auth_session.h"
#include "mainwidget.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "boxes/confirm_box.h"
#include "window/window_controller.h" #include "window/window_controller.h"
namespace Info { namespace Info {
@ -36,7 +40,8 @@ constexpr auto kSortByOnlineDelay = TimeMs(1000);
class ChatMembersController class ChatMembersController
: public PeerListController : public PeerListController
, private base::Subscriber { , private base::Subscriber
, public base::enable_weak_from_this {
public: public:
ChatMembersController( ChatMembersController(
not_null<Window::Controller*> window, not_null<Window::Controller*> window,
@ -44,6 +49,8 @@ public:
void prepare() override; void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
Ui::PopupMenu *rowContextMenu(
not_null<PeerListRow*> row) override;
rpl::producer<int> onlineCountValue() const override { rpl::producer<int> onlineCountValue() const override {
return _onlineCount.value(); return _onlineCount.value();
@ -55,6 +62,7 @@ private:
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user); std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user);
void sortByOnline(); void sortByOnline();
void sortByOnlineDelayed(); void sortByOnlineDelayed();
void removeMember(not_null<UserData*> user);
not_null<Window::Controller*> _window; not_null<Window::Controller*> _window;
not_null<ChatData*> _chat; not_null<ChatData*> _chat;
@ -119,27 +127,33 @@ void ChatMembersController::sortByOnline() {
void ChatMembersController::rebuildRows() { void ChatMembersController::rebuildRows() {
if (_chat->participants.empty()) { if (_chat->participants.empty()) {
while (delegate()->peerListFullRowsCount() > 0) {
delegate()->peerListRemoveRow(
delegate()->peerListRowAt(0));
}
return; return;
} }
std::vector<not_null<UserData*>> users;
auto &participants = _chat->participants; auto &participants = _chat->participants;
for (auto i = participants.cbegin(), e = participants.cend(); for (auto i = 0, count = delegate()->peerListFullRowsCount();
i != e; i != count;) {
++i) { auto row = delegate()->peerListRowAt(i);
users.push_back(i.key()); auto user = row->peer()->asUser();
if (participants.contains(user)) {
++i;
} else {
delegate()->peerListRemoveRow(row);
--count;
}
} }
auto now = unixtime(); for (auto i = participants.cbegin(), e = participants.cend();
base::sort(users, [now](auto a, auto b) { i != e;
return App::onlineForSort(a, now) ++i) {
> App::onlineForSort(b, now); if (auto row = createRow(i.key())) {
});
base::for_each(users, [this](not_null<UserData*> user) {
if (auto row = createRow(user)) {
delegate()->peerListAppendRow(std::move(row)); delegate()->peerListAppendRow(std::move(row));
} }
}); }
refreshOnlineCount(); sortByOnline();
delegate()->peerListRefreshRows(); delegate()->peerListRefreshRows();
} }
@ -167,6 +181,52 @@ void ChatMembersController::rowClicked(not_null<PeerListRow*> row) {
_window->showPeerInfo(row->peer()); _window->showPeerInfo(row->peer());
} }
Ui::PopupMenu *ChatMembersController::rowContextMenu(
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
auto user = row->peer()->asUser();
auto isCreator = (peerFromUser(_chat->creator) == user->id);
auto isAdmin = _chat->adminsEnabled() && _chat->admins.contains(user);
auto canRemoveMember = (user->id == Auth().userPeerId())
? false
: _chat->amCreator()
? true
: (_chat->amAdmin() && !isCreator && !isAdmin)
? true
: (_chat->invitedByMe.contains(user) && !isCreator && !isAdmin)
? true
: false;
auto result = new Ui::PopupMenu(nullptr);
result->addAction(
lang(lng_context_view_profile),
[weak = base::make_weak_unique(this), user] {
if (weak) {
weak->_window->showPeerInfo(user);
}
});
if (canRemoveMember) {
result->addAction(
lang(lng_context_remove_from_group),
[weak = base::make_weak_unique(this), user] {
if (weak) {
weak->removeMember(user);
}
});
}
return result;
}
void ChatMembersController::removeMember(not_null<UserData*> user) {
auto text = lng_profile_sure_kick(lt_user, user->firstName);
Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [user, chat = _chat] {
Ui::hideLayer();
if (App::main()) App::main()->kickParticipant(chat, user);
}));
}
} // namespace } // namespace
std::unique_ptr<PeerListController> CreateMembersController( std::unique_ptr<PeerListController> CreateMembersController(

View File

@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "observer_peer.h" #include "observer_peer.h"
#include "messenger.h" #include "messenger.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "data/data_peer_values.h"
#include "history/history_shared_media.h" #include "history/history_shared_media.h"
namespace Info { namespace Info {
@ -174,9 +175,13 @@ rpl::producer<int> MembersCountValue(
: 0; : 0;
}); });
} else if (auto channel = peer->asChannel()) { } else if (auto channel = peer->asChannel()) {
return PeerUpdateValue( return rpl::combine(
peer, PeerUpdateValue(
Notify::PeerUpdate::Flag::MembersChanged) channel,
Notify::PeerUpdate::Flag::MembersChanged),
Data::PeerFullFlagValue(
channel,
MTPDchannelFull::Flag::f_can_view_participants))
| rpl::map([channel] { | rpl::map([channel] {
auto canViewCount = channel->canViewMembers() auto canViewCount = channel->canViewMembers()
|| !channel->isMegagroup(); || !channel->isMegagroup();

View File

@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h" #include "mainwidget.h"
#include "observer_peer.h" #include "observer_peer.h"
#include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_indexed_list.h"
#include "ui/widgets/popup_menu.h"
#include "window/window_controller.h" #include "window/window_controller.h"
namespace Profile { namespace Profile {
@ -54,6 +55,7 @@ ParticipantsBoxController::ParticipantsBoxController(
} }
if (_role == Role::Profile) { if (_role == Role::Profile) {
setupSortByOnline(); setupSortByOnline();
setupListChangeViewers();
} }
} }
@ -71,6 +73,33 @@ void ParticipantsBoxController::setupSortByOnline() {
})); }));
} }
void ParticipantsBoxController::setupListChangeViewers() {
Auth().data().megagroupParticipantAdded(_channel)
| rpl::start_with_next([this](not_null<UserData*> user) {
if (delegate()->peerListFullRowsCount() > 0) {
if (delegate()->peerListRowAt(0)->peer() == user) {
return;
}
}
if (auto row = delegate()->peerListFindRow(user->id)) {
delegate()->peerListPartitionRows([user](const PeerListRow &row) {
return (row.peer() == user);
});
} else {
delegate()->peerListPrependRow(createRow(user));
delegate()->peerListRefreshRows();
sortByOnline();
}
}, lifetime());
Auth().data().megagroupParticipantRemoved(_channel)
| rpl::start_with_next([this](not_null<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id)) {
delegate()->peerListRemoveRow(row);
}
delegate()->peerListRefreshRows();
}, lifetime());
}
void ParticipantsBoxController::sortByOnlineDelayed() { void ParticipantsBoxController::sortByOnlineDelayed() {
if (!_sortByOnlineTimer.isActive()) { if (!_sortByOnlineTimer.isActive()) {
_sortByOnlineTimer.callOnce(kSortByOnlineDelay); _sortByOnlineTimer.callOnce(kSortByOnlineDelay);
@ -366,6 +395,7 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
} }
auto info = megagroup->mgInfo.get(); auto info = megagroup->mgInfo.get();
if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) { if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) {
_channel->updateFull();
return false; return false;
} }
if (info->lastParticipants.isEmpty()) { if (info->lastParticipants.isEmpty()) {
@ -430,6 +460,53 @@ void ParticipantsBoxController::rowActionClicked(not_null<PeerListRow*> row) {
} }
} }
Ui::PopupMenu *ParticipantsBoxController::rowContextMenu(
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
auto user = row->peer()->asUser();
auto result = new Ui::PopupMenu(nullptr);
result->addAction(
lang(lng_context_view_profile),
[weak = base::make_weak_unique(this), user] {
if (weak) {
weak->_window->showPeerInfo(user);
}
});
if (_channel->canEditAdmin(user)) {
auto it = _additional.adminRights.find(user);
auto isCreator = (user == _additional.creator);
auto notAdmin = !isCreator && (it == _additional.adminRights.cend());
auto label = lang(notAdmin
? lng_context_promote_admin
: lng_context_edit_permissions);
result->addAction(
label,
[weak = base::make_weak_unique(this), user] {
if (weak) {
weak->showAdmin(user);
}
});
}
if (_channel->canRestrictUser(user)) {
result->addAction(
lang(lng_context_restrict_user),
[weak = base::make_weak_unique(this), user]{
if (weak) {
weak->showRestricted(user);
}
});
result->addAction(
lang(lng_context_remove_from_group),
[weak = base::make_weak_unique(this), user] {
if (weak) {
weak->kickMember(user);
}
});
}
return result;
}
void ParticipantsBoxController::showAdmin(not_null<UserData*> user) { void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
auto it = _additional.adminRights.find(user); auto it = _additional.adminRights.find(user);
auto isCreator = (user == _additional.creator); auto isCreator = (user == _additional.creator);
@ -480,7 +557,7 @@ void ParticipantsBoxController::editAdminDone(not_null<UserData*> user, const MT
_additional.restrictedBy.erase(user); _additional.restrictedBy.erase(user);
if (_role == Role::Admins) { if (_role == Role::Admins) {
prependRow(user); prependRow(user);
} else { } else if (_role == Role::Kicked || _role == Role::Restricted) {
removeRow(user); removeRow(user);
} }
} }
@ -489,12 +566,12 @@ void ParticipantsBoxController::editAdminDone(not_null<UserData*> user, const MT
void ParticipantsBoxController::showRestricted(not_null<UserData*> user) { void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
auto it = _additional.restrictedRights.find(user); auto it = _additional.restrictedRights.find(user);
if (it == _additional.restrictedRights.cend()) { auto restrictedRights = (it == _additional.restrictedRights.cend())
return; ? MTP_channelBannedRights(MTP_flags(0), MTP_int(0))
} : it->second;
auto weak = base::make_weak_unique(this); auto weak = base::make_weak_unique(this);
auto hasAdminRights = false; auto hasAdminRights = false;
auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, it->second); auto box = Box<EditRestrictedBox>(_channel, user, hasAdminRights, restrictedRights);
if (_channel->canBanMembers()) { if (_channel->canBanMembers()) {
box->setSaveCallback([megagroup = _channel.get(), user, weak](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { box->setSaveCallback([megagroup = _channel.get(), user, weak](const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) {
MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, newRights), rpcDone([megagroup, user, weak, oldRights, newRights](const MTPUpdates &result) { MTP::send(MTPchannels_EditBanned(megagroup->inputChannel, user->inputUser, newRights), rpcDone([megagroup, user, weak, oldRights, newRights](const MTPUpdates &result) {
@ -522,7 +599,7 @@ void ParticipantsBoxController::editRestrictedDone(not_null<UserData*> user, con
_additional.kicked.erase(user); _additional.kicked.erase(user);
_additional.restrictedRights.erase(user); _additional.restrictedRights.erase(user);
_additional.restrictedBy.erase(user); _additional.restrictedBy.erase(user);
if (_role != Role::Admins) { if (_role == Role::Kicked || _role == Role::Restricted) {
removeRow(user); removeRow(user);
} }
} else { } else {
@ -535,7 +612,9 @@ void ParticipantsBoxController::editRestrictedDone(not_null<UserData*> user, con
_additional.restrictedRights.erase(user); _additional.restrictedRights.erase(user);
if (_role == Role::Kicked) { if (_role == Role::Kicked) {
prependRow(user); prependRow(user);
} else { } else if (_role == Role::Admins
|| _role == Role::Restricted
|| _role == Role::Members) {
removeRow(user); removeRow(user);
} }
} else { } else {
@ -543,7 +622,9 @@ void ParticipantsBoxController::editRestrictedDone(not_null<UserData*> user, con
_additional.kicked.erase(user); _additional.kicked.erase(user);
if (_role == Role::Restricted) { if (_role == Role::Restricted) {
prependRow(user); prependRow(user);
} else { } else if (_role == Role::Kicked
|| _role == Role::Admins
|| _role == Role::Members) {
removeRow(user); removeRow(user);
} }
} }

View File

@ -73,6 +73,8 @@ public:
void prepare() override; void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override; void rowClicked(not_null<PeerListRow*> row) override;
void rowActionClicked(not_null<PeerListRow*> row) override; void rowActionClicked(not_null<PeerListRow*> row) override;
Ui::PopupMenu *rowContextMenu(
not_null<PeerListRow*> row) override;
void loadMoreRows() override; void loadMoreRows() override;
void peerListSearchAddRow(not_null<PeerData*> peer) override; void peerListSearchAddRow(not_null<PeerData*> peer) override;
@ -93,6 +95,7 @@ private:
static std::unique_ptr<PeerListSearchController> CreateSearchController(not_null<ChannelData*> channel, Role role, not_null<Additional*> additional); static std::unique_ptr<PeerListSearchController> CreateSearchController(not_null<ChannelData*> channel, Role role, not_null<Additional*> additional);
void setupSortByOnline(); void setupSortByOnline();
void setupListChangeViewers();
void sortByOnlineDelayed(); void sortByOnlineDelayed();
void sortByOnline(); void sortByOnline();
void showAdmin(not_null<UserData*> user); void showAdmin(not_null<UserData*> user);

View File

@ -66,6 +66,14 @@ template <
true_t test_callable_tuple( true_t test_callable_tuple(
Method &&, Method &&,
std::tuple<Types...> &&) noexcept; std::tuple<Types...> &&) noexcept;
template <
typename Method,
typename ...Types,
typename = decltype(std::declval<Method>()(
const_ref_val<Types>()...))>
true_t test_callable_tuple(
Method &&,
const std::tuple<Types...> &) noexcept;
false_t test_callable_tuple(...) noexcept; false_t test_callable_tuple(...) noexcept;
template <typename Method, typename Arg> template <typename Method, typename Arg>

View File

@ -214,6 +214,25 @@ TEST_CASE("basic operators tests", "[rpl::operators]") {
REQUIRE(*sum == "1 1 3 "); REQUIRE(*sum == "1 1 3 ");
} }
SECTION("filter tuple test") {
auto sum = std::make_shared<std::string>("");
{
auto lifetime = single(std::make_tuple(1, 2))
| then(single(std::make_tuple(1, 2)))
| then(single(std::make_tuple(2, 3)))
| then(single(std::make_tuple(2, 3)))
| then(single(std::make_tuple(3, 4)))
| filter([](auto first, auto second) { return first != 2; })
| map([](auto first, auto second) {
return std::to_string(second);
})
| start_with_next([=](std::string &&value) {
*sum += std::move(value) + ' ';
});
}
REQUIRE(*sum == "2 2 4 ");
}
SECTION("distinct_until_changed test") { SECTION("distinct_until_changed test") {
auto sum = std::make_shared<std::string>(""); auto sum = std::make_shared<std::string>("");
{ {