Allow inviting contacts to voice chats.

This commit is contained in:
John Preston 2020-12-21 23:43:23 +04:00
parent 16c7ec5b05
commit 92bc278052
32 changed files with 989 additions and 230 deletions

View File

@ -235,6 +235,8 @@ PRIVATE
boxes/peer_list_box.h
boxes/peer_list_controllers.cpp
boxes/peer_list_controllers.h
boxes/peer_lists_box.cpp
boxes/peer_lists_box.h
boxes/passcode_box.cpp
boxes/passcode_box.h
boxes/photo_crop_box.cpp

View File

@ -1835,6 +1835,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_invited_status" = "invited";
"lng_group_call_invite_title" = "Invite members";
"lng_group_call_invite_button" = "Invite";
"lng_group_call_add_to_group_one" = "{user} isn't a member of «{group}» yet. Add them to the group?";
"lng_group_call_add_to_group_some" = "Some of those users aren't members of «{group}» yet. Add them to the group?";
"lng_group_call_add_to_group_all" = "Those users aren't members of «{group}» yet. Add them to the group?";
"lng_group_call_invite_members" = "Group members";
"lng_group_call_invite_search_results" = "Search results";
"lng_group_call_new_muted" = "Mute new members";
"lng_group_call_speakers" = "Speakers";
"lng_group_call_microphone" = "Microphone";

View File

@ -3458,7 +3458,8 @@ void ApiWrap::checkForUnreadMentions(
void ApiWrap::addChatParticipants(
not_null<PeerData*> peer,
const std::vector<not_null<UserData*>> &users) {
const std::vector<not_null<UserData*>> &users,
Fn<void(bool)> done) {
if (const auto chat = peer->asChat()) {
for (const auto user : users) {
request(MTPmessages_AddChatUser(
@ -3467,8 +3468,10 @@ void ApiWrap::addChatParticipants(
MTP_int(kForwardMessagesOnAdd)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
if (done) done(true);
}).fail([=](const RPCError &error) {
ShowAddParticipantsError(error.type(), peer, { 1, user });
if (done) done(false);
}).afterDelay(crl::time(5)).send();
}
} else if (const auto channel = peer->asChannel()) {
@ -3480,14 +3483,17 @@ void ApiWrap::addChatParticipants(
auto list = QVector<MTPInputUser>();
list.reserve(qMin(int(users.size()), int(kMaxUsersPerInvite)));
const auto send = [&] {
const auto callback = base::take(done);
request(MTPchannels_InviteToChannel(
channel->inputChannel,
MTP_vector<MTPInputUser>(list)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
requestParticipantsCountDelayed(channel);
if (callback) callback(true);
}).fail([=](const RPCError &error) {
ShowAddParticipantsError(error.type(), peer, users);
if (callback) callback(false);
}).afterDelay(crl::time(5)).send();
};
for (const auto user : users) {

View File

@ -366,7 +366,8 @@ public:
Fn<void()> callbackNotModified = nullptr);
void addChatParticipants(
not_null<PeerData*> peer,
const std::vector<not_null<UserData*>> &users);
const std::vector<not_null<UserData*>> &users,
Fn<void(bool)> done = nullptr);
rpl::producer<SendAction> sendActions() const {
return _sendActions.events();

View File

@ -632,7 +632,7 @@ void GroupInfoBox::submit() {
not_null<PeerListBox*> box) {
auto create = [box, title, weak] {
if (weak) {
auto rows = box->peerListCollectSelectedRows();
auto rows = box->collectSelectedRows();
if (!rows.empty()) {
weak->createGroup(box, title, rows);
}
@ -643,7 +643,8 @@ void GroupInfoBox::submit() {
};
Ui::show(
Box<PeerListBox>(
std::make_unique<AddParticipantsBoxController>(_navigation),
std::make_unique<AddParticipantsBoxController>(
&_navigation->session()),
std::move(initBox)),
Ui::LayerOption::KeepOther);
}

View File

@ -33,38 +33,36 @@ namespace {
class PrivacyExceptionsBoxController : public ChatsListBoxController {
public:
PrivacyExceptionsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
const std::vector<not_null<PeerData*>> &selected);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
std::vector<not_null<PeerData*>> getResult() const;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(not_null<History*> history) override;
private:
not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
rpl::producer<QString> _title;
std::vector<not_null<PeerData*>> _selected;
};
PrivacyExceptionsBoxController::PrivacyExceptionsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
const std::vector<not_null<PeerData*>> &selected)
: ChatsListBoxController(navigation)
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _title(std::move(title))
, _selected(selected) {
}
Main::Session &PrivacyExceptionsBoxController::session() const {
return _navigation->session();
return *_session;
}
void PrivacyExceptionsBoxController::prepareViewHook() {
@ -72,10 +70,6 @@ void PrivacyExceptionsBoxController::prepareViewHook() {
delegate()->peerListAddSelectedPeers(_selected);
}
std::vector<not_null<PeerData*>> PrivacyExceptionsBoxController::getResult() const {
return delegate()->peerListCollectSelectedRows();
}
void PrivacyExceptionsBoxController::rowClicked(not_null<PeerListRow*> row) {
const auto peer = row->peer();
@ -146,13 +140,13 @@ void EditPrivacyBox::editExceptions(
Exception exception,
Fn<void()> done) {
auto controller = std::make_unique<PrivacyExceptionsBoxController>(
_window,
&_window->session(),
_controller->exceptionBoxTitle(exception),
exceptions(exception));
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_settings_save(), crl::guard(this, [=] {
exceptions(exception) = controller->getResult();
exceptions(exception) = box->collectSelectedRows();
const auto type = [&] {
switch (exception) {
case Exception::Always: return Exception::Never;

View File

@ -320,7 +320,7 @@ void EditExceptions(
const auto include = (options & Flag::Contacts) != Flags(0);
const auto rules = data->current();
auto controller = std::make_unique<EditFilterChatsListController>(
window,
&window->session(),
(include
? tr::lng_filters_include_title()
: tr::lng_filters_exclude_title()),
@ -331,7 +331,7 @@ void EditExceptions(
auto initBox = [=](not_null<PeerListBox*> box) {
box->setCloseByOutsideClick(false);
box->addButton(tr::lng_settings_save(), crl::guard(context, [=] {
const auto peers = box->peerListCollectSelectedRows();
const auto peers = box->collectSelectedRows();
const auto rules = data->current();
auto &&histories = ranges::view::all(
peers

View File

@ -68,7 +68,6 @@ public:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
@ -209,11 +208,6 @@ int TypeDelegate::peerListSelectedRowsCount() {
return 0;
}
auto TypeDelegate::peerListCollectSelectedRows()
-> std::vector<not_null<PeerData*>> {
return {};
}
void TypeDelegate::peerListScrollToTop() {
}
@ -347,13 +341,13 @@ void PaintFilterChatsTypeIcon(
}
EditFilterChatsListController::EditFilterChatsListController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
Flags options,
Flags selected,
const base::flat_set<not_null<History*>> &peers)
: ChatsListBoxController(navigation)
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _title(std::move(title))
, _peers(peers)
, _options(options)
@ -361,7 +355,7 @@ EditFilterChatsListController::EditFilterChatsListController(
}
Main::Session &EditFilterChatsListController::session() const {
return _navigation->session();
return *_session;
}
void EditFilterChatsListController::rowClicked(not_null<PeerListRow*> row) {

View File

@ -41,7 +41,7 @@ public:
using Flags = Data::ChatFilter::Flags;
EditFilterChatsListController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
rpl::producer<QString> title,
Flags options,
Flags selected,
@ -64,7 +64,7 @@ private:
void updateTitle();
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
rpl::producer<QString> _title;
base::flat_set<not_null<History*>> _peers;
Flags _options;

View File

@ -399,7 +399,7 @@ int PeerListBox::peerListSelectedRowsCount() {
return _select ? _select->entity()->getItemsCount() : 0;
}
auto PeerListBox::peerListCollectSelectedRows()
auto PeerListBox::collectSelectedRows()
-> std::vector<not_null<PeerData*>> {
auto result = std::vector<not_null<PeerData*>>();
auto items = _select
@ -982,6 +982,18 @@ void PeerListContent::setAboveWidget(object_ptr<TWidget> widget) {
}
}
void PeerListContent::setAboveSearchWidget(object_ptr<TWidget> widget) {
_aboveSearchWidget = std::move(widget);
if (_aboveSearchWidget) {
_aboveSearchWidget->setParent(this);
}
}
void PeerListContent::setHideEmpty(bool hide) {
_hideEmpty = hide;
resizeToWidth(width());
}
void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
_belowWidget = std::move(widget);
if (_belowWidget) {
@ -990,6 +1002,9 @@ void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
}
int PeerListContent::labelHeight() const {
if (_hideEmpty && !shownRowsCount()) {
return 0;
}
auto computeLabelHeight = [](auto &label) {
if (!label) {
return 0;
@ -1082,34 +1097,45 @@ void PeerListContent::paintEvent(QPaintEvent *e) {
}
int PeerListContent::resizeGetHeight(int newWidth) {
const auto rowsCount = shownRowsCount();
const auto hideAll = !rowsCount && _hideEmpty;
_aboveHeight = 0;
if (_aboveWidget) {
_aboveWidget->resizeToWidth(newWidth);
_aboveWidget->moveToLeft(0, 0, newWidth);
if (showingSearch()) {
if (hideAll || showingSearch()) {
_aboveWidget->hide();
} else {
_aboveWidget->show();
_aboveHeight = _aboveWidget->height();
}
}
const auto rowsCount = shownRowsCount();
if (_aboveSearchWidget) {
_aboveSearchWidget->resizeToWidth(newWidth);
_aboveSearchWidget->moveToLeft(0, 0, newWidth);
if (hideAll || !showingSearch()) {
_aboveSearchWidget->hide();
} else {
_aboveSearchWidget->show();
_aboveHeight = _aboveSearchWidget->height();
}
}
const auto labelTop = rowsTop() + qMax(1, shownRowsCount()) * _rowHeight;
const auto labelWidth = newWidth - 2 * st::contactsPadding.left();
if (_description) {
_description->resizeToWidth(labelWidth);
_description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
_description->setVisible(!showingSearch());
_description->setVisible(!hideAll && !showingSearch());
}
if (_searchNoResults) {
_searchNoResults->resizeToWidth(labelWidth);
_searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
_searchNoResults->setVisible(showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
_searchNoResults->setVisible(!hideAll && showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
}
if (_searchLoading) {
_searchLoading->resizeToWidth(labelWidth);
_searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
_searchLoading->setVisible(showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
_searchLoading->setVisible(!hideAll && showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
}
const auto label = labelHeight();
const auto belowTop = (label > 0 || rowsCount > 0)
@ -1119,7 +1145,7 @@ int PeerListContent::resizeGetHeight(int newWidth) {
if (_belowWidget) {
_belowWidget->resizeToWidth(newWidth);
_belowWidget->moveToLeft(0, belowTop, newWidth);
if (showingSearch()) {
if (hideAll || showingSearch()) {
_belowWidget->hide();
} else {
_belowWidget->show();
@ -1351,15 +1377,18 @@ crl::time PeerListContent::paintRow(
return (refreshStatusAt - ms);
}
void PeerListContent::selectSkip(int direction) {
if (_pressed.index.value >= 0) {
return;
PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
if (hasPressed()) {
return { _selected.index.value, _selected.index.value };
}
_mouseSelection = false;
_lastMousePosition = std::nullopt;
auto newSelectedIndex = _selected.index.value + direction;
auto result = SkipResult();
result.shouldMoveTo = newSelectedIndex;
auto rowsCount = shownRowsCount();
auto index = 0;
auto firstEnabled = -1, lastEnabled = -1;
@ -1415,14 +1444,36 @@ void PeerListContent::selectSkip(int direction) {
}
update();
_selectedIndex = _selected.index.value;
result.reallyMovedTo = _selected.index.value;
return result;
}
void PeerListContent::selectSkipPage(int height, int direction) {
auto rowsToSkip = height / _rowHeight;
if (!rowsToSkip) return;
if (!rowsToSkip) {
return;
}
selectSkip(rowsToSkip * direction);
}
rpl::producer<int> PeerListContent::selectedIndexValue() const {
return _selectedIndex.value();
}
bool PeerListContent::hasSelection() const {
return _selected.index.value >= 0;
}
bool PeerListContent::hasPressed() const {
return _pressed.index.value >= 0;
}
void PeerListContent::clearSelection() {
setSelected(Selected());
}
void PeerListContent::loadProfilePhotos() {
if (_visibleTop >= _visibleBottom) return;
@ -1569,14 +1620,17 @@ void PeerListContent::setSearchQuery(
clearSearchRows();
}
void PeerListContent::submitted() {
bool PeerListContent::submitted() {
if (const auto row = getRow(_selected.index)) {
_controller->rowClicked(row);
return true;
} else if (showingSearch()) {
if (const auto row = getRow(RowIndex(0))) {
_controller->rowClicked(row);
return true;
}
}
return false;
}
void PeerListContent::visibleTopBottomUpdated(
@ -1590,11 +1644,14 @@ void PeerListContent::visibleTopBottomUpdated(
void PeerListContent::setSelected(Selected selected) {
updateRow(_selected.index);
if (_selected != selected) {
_selected = selected;
updateRow(_selected.index);
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
if (_selected == selected) {
return;
}
_selected = selected;
updateRow(_selected.index);
setCursor(_selected.action ? style::cur_pointer : style::cur_default);
_selectedIndex = _selected.index.value;
}
void PeerListContent::setContexted(Selected contexted) {

View File

@ -254,10 +254,12 @@ class PeerListDelegate {
public:
virtual void peerListSetTitle(rpl::producer<QString> title) = 0;
virtual void peerListSetAdditionalTitle(rpl::producer<QString> title) = 0;
virtual void peerListSetHideEmpty(bool hide) = 0;
virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0;
virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
virtual void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) = 0;
virtual void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) = 0;
virtual void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) = 0;
virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
@ -299,7 +301,6 @@ public:
}
virtual int peerListSelectedRowsCount() = 0;
virtual std::vector<not_null<PeerData*>> peerListCollectSelectedRows() = 0;
virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
virtual void peerListRestoreState(
std::unique_ptr<PeerListState> state) = 0;
@ -499,13 +500,20 @@ public:
QWidget *parent,
not_null<PeerListController*> controller);
void selectSkip(int direction);
struct SkipResult {
int shouldMoveTo = 0;
int reallyMovedTo = 0;
};
SkipResult selectSkip(int direction);
void selectSkipPage(int height, int direction);
[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
[[nodiscard]] bool hasSelection() const;
[[nodiscard]] bool hasPressed() const;
void clearSelection();
void searchQueryChanged(QString query);
void submitted();
bool submitted();
// Interface for the controller.
void appendRow(std::unique_ptr<PeerListRow> row);
@ -525,7 +533,9 @@ public:
void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
void setAboveWidget(object_ptr<TWidget> widget);
void setAboveSearchWidget(object_ptr<TWidget> widget);
void setBelowWidget(object_ptr<TWidget> width);
void setHideEmpty(bool hide);
void refreshRows();
void setSearchMode(PeerListSearchMode mode);
@ -667,6 +677,7 @@ private:
Selected _selected;
Selected _pressed;
Selected _contexted;
rpl::variable<int> _selectedIndex = -1;
bool _mouseSelection = false;
std::optional<QPoint> _lastMousePosition;
Qt::MouseButton _pressButton = Qt::LeftButton;
@ -685,7 +696,9 @@ private:
int _aboveHeight = 0;
int _belowHeight = 0;
bool _hideEmpty = false;
object_ptr<TWidget> _aboveWidget = { nullptr };
object_ptr<TWidget> _aboveSearchWidget = { nullptr };
object_ptr<TWidget> _belowWidget = { nullptr };
object_ptr<Ui::FlatLabel> _description = { nullptr };
object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
@ -703,6 +716,9 @@ public:
_content = content;
}
void peerListSetHideEmpty(bool hide) override {
_content->setHideEmpty(hide);
}
void peerListAppendRow(
std::unique_ptr<PeerListRow> row) override {
_content->appendRow(std::move(row));
@ -767,6 +783,9 @@ public:
void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) override {
_content->setAboveWidget(std::move(aboveWidget));
}
void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) override {
_content->setAboveSearchWidget(std::move(aboveWidget));
}
void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) override {
_content->setBelowWidget(std::move(belowWidget));
}
@ -824,6 +843,8 @@ public:
std::unique_ptr<PeerListController> controller,
Fn<void(not_null<PeerListBox*>)> init);
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
void peerListSetTitle(rpl::producer<QString> title) override {
setTitle(std::move(title));
}
@ -840,7 +861,6 @@ public:
anim::type animated) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
protected:

View File

@ -23,7 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "history/history.h"
#include "dialogs/dialogs_main_list.h"
#include "window/window_session_controller.h"
#include "window/window_session_controller.h" // onShowAddContact()
#include "facades.h"
#include "styles/style_boxes.h"
#include "styles/style_profile.h"
@ -115,7 +115,8 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
[=] { controller->widget()->onShowAddContact(); });
};
return Box<PeerListBox>(
std::make_unique<ContactsBoxController>(controller),
std::make_unique<ContactsBoxController>(
&sessionController->session()),
std::move(delegate));
}
@ -159,9 +160,9 @@ void PeerListRowWithLink::paintAction(
}
PeerListGlobalSearchController::PeerListGlobalSearchController(
not_null<Window::SessionNavigation*> navigation)
: _navigation(navigation)
, _api(&_navigation->session().mtp()) {
not_null<Main::Session*> session)
: _session(session)
, _api(&session->mtp()) {
_timer.setCallback([this] { searchOnServer(); });
}
@ -210,8 +211,8 @@ void PeerListGlobalSearchController::searchDone(
auto &contacts = result.c_contacts_found();
auto query = _query;
if (requestId) {
_navigation->session().data().processUsers(contacts.vusers());
_navigation->session().data().processChats(contacts.vchats());
_session->data().processUsers(contacts.vusers());
_session->data().processChats(contacts.vchats());
auto it = _queries.find(requestId);
if (it != _queries.cend()) {
query = it->second;
@ -221,7 +222,7 @@ void PeerListGlobalSearchController::searchDone(
}
const auto feedList = [&](const MTPVector<MTPPeer> &list) {
for (const auto &mtpPeer : list.v) {
const auto peer = _navigation->session().data().peerLoaded(
const auto peer = _session->data().peerLoaded(
peerFromMTP(mtpPeer));
if (peer) {
delegate()->peerListSearchAddRow(peer);
@ -246,9 +247,9 @@ ChatsListBoxController::Row::Row(not_null<History*> history)
}
ChatsListBoxController::ChatsListBoxController(
not_null<Window::SessionNavigation*> navigation)
not_null<Main::Session*> session)
: ChatsListBoxController(
std::make_unique<PeerListGlobalSearchController>(navigation)) {
std::make_unique<PeerListGlobalSearchController>(session)) {
}
ChatsListBoxController::ChatsListBoxController(
@ -354,21 +355,21 @@ bool ChatsListBoxController::appendRow(not_null<History*> history) {
}
ContactsBoxController::ContactsBoxController(
not_null<Window::SessionNavigation*> navigation)
: PeerListController(
std::make_unique<PeerListGlobalSearchController>(navigation))
, _navigation(navigation) {
not_null<Main::Session*> session)
: ContactsBoxController(
session,
std::make_unique<PeerListGlobalSearchController>(session)) {
}
ContactsBoxController::ContactsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
std::unique_ptr<PeerListSearchController> searchController)
: PeerListController(std::move(searchController))
, _navigation(navigation) {
, _session(session) {
}
Main::Session &ContactsBoxController::session() const {
return _navigation->session();
return *_session;
}
void ContactsBoxController::prepare() {
@ -435,26 +436,24 @@ bool ContactsBoxController::appendRow(not_null<UserData*> user) {
return false;
}
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(not_null<UserData*> user) {
std::unique_ptr<PeerListRow> ContactsBoxController::createRow(
not_null<UserData*> user) {
return std::make_unique<PeerListRow>(user);
}
void AddBotToGroupBoxController::Start(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot) {
void AddBotToGroupBoxController::Start(not_null<UserData*> bot) {
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
};
Ui::show(Box<PeerListBox>(
std::make_unique<AddBotToGroupBoxController>(navigation, bot),
std::make_unique<AddBotToGroupBoxController>(bot),
std::move(initBox)));
}
AddBotToGroupBoxController::AddBotToGroupBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot)
: ChatsListBoxController(SharingBotGame(bot)
? std::make_unique<PeerListGlobalSearchController>(navigation)
? std::make_unique<PeerListGlobalSearchController>(&bot->session())
: nullptr)
, _bot(bot) {
}
@ -572,15 +571,15 @@ void AddBotToGroupBoxController::prepareViewHook() {
}
ChooseRecipientBoxController::ChooseRecipientBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
FnMut<void(not_null<PeerData*>)> callback)
: ChatsListBoxController(navigation)
, _navigation(navigation)
: ChatsListBoxController(session)
, _session(session)
, _callback(std::move(callback)) {
}
Main::Session &ChooseRecipientBoxController::session() const {
return _navigation->session();
return *_session;
}
void ChooseRecipientBoxController::prepareViewHook() {

View File

@ -32,7 +32,6 @@ class History;
namespace Window {
class SessionController;
class SessionNavigation;
} // namespace Window
[[nodiscard]] object_ptr<Ui::BoxContent> PrepareContactsBox(
@ -65,8 +64,7 @@ private:
class PeerListGlobalSearchController : public PeerListSearchController {
public:
PeerListGlobalSearchController(
not_null<Window::SessionNavigation*> navigation);
explicit PeerListGlobalSearchController(not_null<Main::Session*> session);
void searchQuery(const QString &query) override;
bool isLoading() override;
@ -79,7 +77,7 @@ private:
void searchOnServer();
void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId);
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
MTP::Sender _api;
base::Timer _timer;
QString _query;
@ -104,7 +102,7 @@ public:
};
ChatsListBoxController(not_null<Window::SessionNavigation*> navigation);
ChatsListBoxController(not_null<Main::Session*> session);
ChatsListBoxController(
std::unique_ptr<PeerListSearchController> searchController);
@ -127,15 +125,15 @@ private:
class ContactsBoxController : public PeerListController {
public:
explicit ContactsBoxController(not_null<Main::Session*> session);
ContactsBoxController(
not_null<Window::SessionNavigation*> navigation);
ContactsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
std::unique_ptr<PeerListSearchController> searchController);
Main::Session &session() const override;
[[nodiscard]] Main::Session &session() const override;
void prepare() override final;
std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final;
[[nodiscard]] std::unique_ptr<PeerListRow> createSearchRow(
not_null<PeerData*> peer) override final;
void rowClicked(not_null<PeerListRow*> row) override;
protected:
@ -150,7 +148,7 @@ private:
void checkForEmptyRows();
bool appendRow(not_null<UserData*> user);
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
};
@ -158,13 +156,9 @@ class AddBotToGroupBoxController
: public ChatsListBoxController
, public base::has_weak_ptr {
public:
static void Start(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot);
static void Start(not_null<UserData*> bot);
AddBotToGroupBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<UserData*> bot);
explicit AddBotToGroupBoxController(not_null<UserData*> bot);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@ -186,7 +180,7 @@ private:
void shareBotGame(not_null<PeerData*> chat);
void addBotToGroup(not_null<PeerData*> chat);
not_null<UserData*> _bot;
const not_null<UserData*> _bot;
};
@ -195,7 +189,7 @@ class ChooseRecipientBoxController
, public base::has_weak_ptr {
public:
ChooseRecipientBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<Main::Session*> session,
FnMut<void(not_null<PeerData*>)> callback);
Main::Session &session() const override;
@ -210,7 +204,7 @@ protected:
std::unique_ptr<Row> createRow(not_null<History*> history) override;
private:
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
FnMut<void(not_null<PeerData*>)> _callback;
};

View File

@ -0,0 +1,429 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/peer_lists_box.h"
#include "lang/lang_keys.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_peer.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
PeerListsBox::PeerListsBox(
QWidget*,
std::vector<std::unique_ptr<PeerListController>> controllers,
Fn<void(not_null<PeerListsBox*>)> init)
: _lists(makeLists(std::move(controllers)))
, _init(std::move(init)) {
Expects(!_lists.empty());
}
auto PeerListsBox::collectSelectedRows()
-> std::vector<not_null<PeerData*>> {
auto result = std::vector<not_null<PeerData*>>();
auto items = _select
? _select->entity()->getItems()
: QVector<uint64>();
if (!items.empty()) {
result.reserve(items.size());
const auto session = &firstController()->session();
for (const auto itemId : items) {
const auto foreign = [&] {
for (const auto &list : _lists) {
if (list.controller->isForeignRow(itemId)) {
return true;
}
}
return false;
}();
if (!foreign) {
result.push_back(session->data().peer(itemId));
}
}
}
return result;
}
PeerListsBox::List PeerListsBox::makeList(
std::unique_ptr<PeerListController> controller) {
auto delegate = std::make_unique<Delegate>(this, controller.get());
return {
std::move(controller),
std::move(delegate),
};
}
std::vector<PeerListsBox::List> PeerListsBox::makeLists(
std::vector<std::unique_ptr<PeerListController>> controllers) {
auto result = std::vector<List>();
result.reserve(controllers.size());
for (auto &controller : controllers) {
result.push_back(makeList(std::move(controller)));
}
return result;
}
not_null<PeerListController*> PeerListsBox::firstController() const {
return _lists.front().controller.get();
}
void PeerListsBox::createMultiSelect() {
Expects(_select == nullptr);
auto entity = object_ptr<Ui::MultiSelect>(
this,
(firstController()->selectSt()
? *firstController()->selectSt()
: st::defaultMultiSelect),
tr::lng_participant_filter());
_select.create(this, std::move(entity));
_select->heightValue(
) | rpl::start_with_next(
[this] { updateScrollSkips(); },
lifetime());
_select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) {
for (const auto &list : _lists) {
if (list.content->submitted()) {
break;
}
}
});
_select->entity()->setQueryChangedCallback([=](const QString &query) {
searchQueryChanged(query);
});
_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
for (const auto &list : _lists) {
if (list.controller->handleDeselectForeignRow(itemId)) {
return;
}
}
const auto session = &firstController()->session();
if (const auto peer = session->data().peerLoaded(itemId)) {
const auto id = peer->id;
for (const auto &list : _lists) {
if (const auto row = list.delegate->peerListFindRow(id)) {
list.content->changeCheckState(
row,
false,
anim::type::normal);
update();
}
list.controller->itemDeselectedHook(peer);
}
}
});
_select->resizeToWidth(firstController()->contentWidth());
_select->moveToLeft(0, 0);
}
int PeerListsBox::getTopScrollSkip() const {
auto result = 0;
if (_select && !_select->isHidden()) {
result += _select->height();
}
return result;
}
void PeerListsBox::updateScrollSkips() {
// If we show / hide the search field scroll top is fixed.
// If we resize search field by bubbles scroll bottom is fixed.
setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
if (!_select->animating()) {
_scrollBottomFixed = true;
}
}
void PeerListsBox::prepare() {
auto rows = setInnerWidget(
object_ptr<Ui::VerticalLayout>(this),
st::boxScroll);
for (auto &list : _lists) {
const auto content = rows->add(object_ptr<PeerListContent>(
rows,
list.controller.get()));
list.content = content;
list.delegate->setContent(content);
list.controller->setDelegate(list.delegate.get());
content->scrollToRequests(
) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
const auto skip = content->y();
onScrollToY(
skip + request.ymin,
(request.ymax >= 0) ? (skip + request.ymax) : request.ymax);
}, lifetime());
content->selectedIndexValue(
) | rpl::filter([=](int index) {
return (index >= 0);
}) | rpl::start_with_next([=] {
for (const auto &list : _lists) {
if (list.content && list.content != content) {
list.content->clearSelection();
}
}
}, lifetime());
}
rows->resizeToWidth(firstController()->contentWidth());
setDimensions(firstController()->contentWidth(), st::boxMaxListHeight);
if (_select) {
_select->finishAnimating();
Ui::SendPendingMoveResizeEvents(_select);
_scrollBottomFixed = true;
onScrollToY(0);
}
if (_init) {
_init(this);
}
}
void PeerListsBox::keyPressEvent(QKeyEvent *e) {
const auto skipRows = [&](int rows) {
if (rows == 0) {
return;
}
for (const auto &list : _lists) {
if (list.content->hasPressed()) {
return;
}
}
const auto from = begin(_lists), till = end(_lists);
auto i = from;
for (; i != till; ++i) {
if (i->content->hasSelection()) {
break;
}
}
if (i == till && rows < 0) {
return;
}
if (rows > 0) {
if (i == till) {
i = from;
}
for (; i != till; ++i) {
const auto result = i->content->selectSkip(rows);
if (result.shouldMoveTo - result.reallyMovedTo >= rows) {
continue;
} else if (result.reallyMovedTo >= result.shouldMoveTo) {
return;
} else {
rows = result.shouldMoveTo - result.reallyMovedTo;
}
}
} else {
for (++i; i != from;) {
const auto result = (--i)->content->selectSkip(rows);
if (result.shouldMoveTo - result.reallyMovedTo <= rows) {
continue;
} else if (result.reallyMovedTo <= result.shouldMoveTo) {
return;
} else {
rows = result.shouldMoveTo - result.reallyMovedTo;
}
}
}
};
const auto rowsInPage = [&] {
const auto rowHeight = firstController()->computeListSt().item.height;
return height() / rowHeight;
};
if (e->key() == Qt::Key_Down) {
skipRows(1);
} else if (e->key() == Qt::Key_Up) {
skipRows(-1);
} else if (e->key() == Qt::Key_PageDown) {
skipRows(rowsInPage());
} else if (e->key() == Qt::Key_PageUp) {
skipRows(-rowsInPage());
} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
_select->entity()->clearQuery();
} else {
BoxContent::keyPressEvent(e);
}
}
void PeerListsBox::searchQueryChanged(const QString &query) {
onScrollToY(0);
for (const auto &list : _lists) {
list.content->searchQueryChanged(query);
}
}
void PeerListsBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
if (_select) {
_select->resizeToWidth(width());
_select->moveToLeft(0, 0);
updateScrollSkips();
}
for (const auto &list : _lists) {
list.content->resizeToWidth(width());
}
}
void PeerListsBox::paintEvent(QPaintEvent *e) {
Painter p(this);
const auto &bg = (firstController()->listSt()
? *firstController()->listSt()
: st::peerListBox).bg;
for (const auto rect : e->region()) {
p.fillRect(rect, bg);
}
}
void PeerListsBox::setInnerFocus() {
if (!_select || !_select->toggled()) {
_lists.front().content->setFocus();
} else {
_select->entity()->setInnerFocus();
}
}
PeerListsBox::Delegate::Delegate(
not_null<PeerListsBox*> box,
not_null<PeerListController*> controller)
: _box(box)
, _controller(controller) {
}
void PeerListsBox::Delegate::peerListSetTitle(rpl::producer<QString> title) {
}
void PeerListsBox::Delegate::peerListSetAdditionalTitle(
rpl::producer<QString> title) {
}
void PeerListsBox::Delegate::peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) {
if (checked) {
_box->addSelectItem(row, anim::type::normal);
PeerListContentDelegate::peerListSetRowChecked(row, checked);
peerListUpdateRow(row);
// This call deletes row from _searchRows.
_box->_select->entity()->clearQuery();
} else {
// The itemRemovedCallback will call changeCheckState() here.
_box->_select->entity()->removeItem(row->id());
peerListUpdateRow(row);
}
}
void PeerListsBox::Delegate::peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) {
if (checked) {
_box->addSelectItem(row, animated);
// This call deletes row from _searchRows.
_box->_select->entity()->clearQuery();
} else {
// The itemRemovedCallback will call changeCheckState() here.
_box->_select->entity()->removeItem(row->id());
}
}
void PeerListsBox::Delegate::peerListScrollToTop() {
_box->onScrollToY(0);
}
void PeerListsBox::Delegate::peerListSetSearchMode(PeerListSearchMode mode) {
PeerListContentDelegate::peerListSetSearchMode(mode);
_box->setSearchMode(mode);
}
void PeerListsBox::setSearchMode(PeerListSearchMode mode) {
auto selectVisible = (mode != PeerListSearchMode::Disabled);
if (selectVisible && !_select) {
createMultiSelect();
_select->toggle(!selectVisible, anim::type::instant);
}
if (_select) {
_select->toggle(selectVisible, anim::type::normal);
_scrollBottomFixed = false;
setInnerFocus();
}
}
void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
Expects(_box->_select != nullptr);
_box->_select->entity()->finishItemsBunch();
}
bool PeerListsBox::Delegate::peerListIsRowChecked(
not_null<PeerListRow*> row) {
return _box->_select
? _box->_select->entity()->hasItem(row->id())
: false;
}
int PeerListsBox::Delegate::peerListSelectedRowsCount() {
return _box->_select ? _box->_select->entity()->getItemsCount() : 0;
}
void PeerListsBox::addSelectItem(
not_null<PeerData*> peer,
anim::type animated) {
addSelectItem(
peer->id,
peer->shortName(),
PaintUserpicCallback(peer, false),
animated);
}
void PeerListsBox::addSelectItem(
not_null<PeerListRow*> row,
anim::type animated) {
addSelectItem(
row->id(),
row->generateShortName(),
row->generatePaintUserpicCallback(),
animated);
}
void PeerListsBox::addSelectItem(
uint64 itemId,
const QString &text,
Ui::MultiSelect::PaintRoundImage paintUserpic,
anim::type animated) {
if (!_select) {
createMultiSelect();
_select->hide(anim::type::instant);
}
const auto &activeBg = (firstController()->selectSt()
? *firstController()->selectSt()
: st::defaultMultiSelect).item.textActiveBg;
if (animated == anim::type::instant) {
_select->entity()->addItemInBunch(
itemId,
text,
activeBg,
std::move(paintUserpic));
} else {
_select->entity()->addItem(
itemId,
text,
activeBg,
std::move(paintUserpic));
}
}

View File

@ -0,0 +1,101 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "boxes/peer_list_box.h"
class PeerListsBox : public Ui::BoxContent {
public:
PeerListsBox(
QWidget*,
std::vector<std::unique_ptr<PeerListController>> controllers,
Fn<void(not_null<PeerListsBox*>)> init);
[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
protected:
void prepare() override;
void setInnerFocus() override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
private:
class Delegate final : public PeerListContentDelegate {
public:
Delegate(
not_null<PeerListsBox*> box,
not_null<PeerListController*> controller);
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
void peerListSetSearchMode(PeerListSearchMode mode) override;
void peerListSetRowChecked(
not_null<PeerListRow*> row,
bool checked) override;
void peerListSetForeignRowChecked(
not_null<PeerListRow*> row,
bool checked,
anim::type animated) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) override {
_box->addSelectItem(peer, anim::type::instant);
}
void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
_box->addSelectItem(row, anim::type::instant);
}
void peerListFinishSelectedRowsBunch() override;
private:
const not_null<PeerListsBox*> _box;
const not_null<PeerListController*> _controller;
};
struct List {
std::unique_ptr<PeerListController> controller;
std::unique_ptr<Delegate> delegate;
PeerListContent *content = nullptr;
};
friend class Delegate;
[[nodiscard]] List makeList(
std::unique_ptr<PeerListController> controller);
[[nodiscard]] std::vector<List> makeLists(
std::vector<std::unique_ptr<PeerListController>> controllers);
[[nodiscard]] not_null<PeerListController*> firstController() const;
void addSelectItem(
not_null<PeerData*> peer,
anim::type animated);
void addSelectItem(
not_null<PeerListRow*> row,
anim::type animated);
void addSelectItem(
uint64 itemId,
const QString &text,
PaintRoundImageCallback paintUserpic,
anim::type animated);
void setSearchMode(PeerListSearchMode mode);
void createMultiSelect();
int getTopScrollSkip() const;
void updateScrollSkips();
void searchQueryChanged(const QString &query);
object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
std::vector<List> _lists;
Fn<void(PeerListsBox*)> _init;
bool _scrollBottomFixed = false;
};

View File

@ -51,28 +51,21 @@ base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
} // namespace
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation)
: ContactsBoxController(
navigation,
std::make_unique<PeerListGlobalSearchController>(navigation)) {
not_null<Main::Session*> session)
: ContactsBoxController(session) {
}
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer)
: AddParticipantsBoxController(
navigation,
peer,
GetAlreadyInFromPeer(peer)) {
}
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> &&alreadyIn)
: ContactsBoxController(
navigation,
std::make_unique<PeerListGlobalSearchController>(navigation))
: ContactsBoxController(&peer->session())
, _peer(peer)
, _alreadyIn(std::move(alreadyIn)) {
subscribeToMigration();
@ -179,7 +172,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers(
not_null<PeerListBox*> box) const {
Expects(_peer != nullptr);
const auto rows = box->peerListCollectSelectedRows();
const auto rows = box->collectSelectedRows();
const auto users = ranges::view::all(
rows
) | ranges::view::transform([](not_null<PeerData*> peer) {
@ -198,9 +191,7 @@ bool AddParticipantsBoxController::inviteSelectedUsers(
void AddParticipantsBoxController::Start(
not_null<Window::SessionNavigation*> navigation,
not_null<ChatData*> chat) {
auto controller = std::make_unique<AddParticipantsBoxController>(
navigation,
chat);
auto controller = std::make_unique<AddParticipantsBoxController>(chat);
const auto weak = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(tr::lng_participant_invite(), [=] {
@ -223,7 +214,6 @@ void AddParticipantsBoxController::Start(
base::flat_set<not_null<UserData*>> &&alreadyIn,
bool justCreated) {
auto controller = std::make_unique<AddParticipantsBoxController>(
navigation,
channel,
std::move(alreadyIn));
const auto weak = controller.get();

View File

@ -27,16 +27,16 @@ public:
not_null<ChannelData*> channel,
base::flat_set<not_null<UserData*>> &&alreadyIn);
explicit AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation);
explicit AddParticipantsBoxController(not_null<Main::Session*> session);
explicit AddParticipantsBoxController(not_null<PeerData*> peer);
AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer);
AddParticipantsBoxController(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> &&alreadyIn);
[[nodiscard]] not_null<PeerData*> peer() const {
return _peer;
}
void rowClicked(not_null<PeerListRow*> row) override;
void itemDeselectedHook(not_null<PeerData*> peer) override;

View File

@ -477,11 +477,17 @@ groupCallMembersListItem: PeerListItem(defaultPeerListItem) {
groupCallMembersList: PeerList(defaultPeerList) {
bg: groupCallMembersBg;
about: FlatLabel(defaultPeerListAbout) {
textFg: groupCallMemberInactiveStatus;
textFg: groupCallMemberNotJoinedStatus;
}
item: groupCallMembersListItem;
}
groupCallInviteDividerLabel: FlatLabel(defaultFlatLabel) {
textFg: groupCallMemberNotJoinedStatus;
}
groupCallInviteDividerPadding: margins(17px, 7px, 17px, 7px);
groupCallInviteMembersList: PeerList(groupCallMembersList) {
padding: margins(0px, 10px, 0px, 10px);
item: PeerListItem(groupCallMembersListItem) {
statusFg: groupCallMemberNotJoinedStatus;
statusFgOver: groupCallMemberNotJoinedStatus;

View File

@ -1410,6 +1410,9 @@ void GroupMembers::peerListSetTitle(rpl::producer<QString> title) {
void GroupMembers::peerListSetAdditionalTitle(rpl::producer<QString> title) {
}
void GroupMembers::peerListSetHideEmpty(bool hide) {
}
bool GroupMembers::peerListIsRowChecked(not_null<PeerListRow*> row) {
return false;
}
@ -1421,10 +1424,6 @@ int GroupMembers::peerListSelectedRowsCount() {
return 0;
}
std::vector<not_null<PeerData*>> GroupMembers::peerListCollectSelectedRows() {
return {};
}
void GroupMembers::peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) {
Unexpected("Item selection in Calls::GroupMembers.");
}

View File

@ -57,10 +57,10 @@ private:
// PeerListContentDelegate interface.
void peerListSetTitle(rpl::producer<QString> title) override;
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
void peerListSetHideEmpty(bool hide) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
void peerListScrollToTop() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
void peerListAddSelectedRowInBunch(

View File

@ -29,6 +29,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "base/event_filter.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/add_participants_box.h"
#include "boxes/peer_lists_box.h"
#include "boxes/confirm_box.h"
#include "app.h"
#include "apiwrap.h" // api().kickParticipant.
#include "styles/style_calls.h"
@ -51,8 +54,7 @@ class InviteController final : public ParticipantsBoxController {
public:
InviteController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
int fullInCount);
base::flat_set<not_null<UserData*>> alreadyIn);
void prepare() override;
@ -63,34 +65,81 @@ public:
void itemDeselectedHook(not_null<PeerData*> peer) override;
std::variant<int, not_null<UserData*>> inviteSelectedUsers(
not_null<PeerListBox*> box,
not_null<GroupCall*> call) const;
[[nodiscard]] auto peersWithRows() const
-> not_null<const base::flat_set<not_null<UserData*>>*>;
[[nodiscard]] rpl::producer<not_null<UserData*>> rowAdded() const;
[[nodiscard]] bool hasRowFor(not_null<PeerData*> peer) const;
private:
[[nodiscard]] int alreadyInCount() const;
[[nodiscard]] bool isAlreadyIn(not_null<UserData*> user) const;
[[nodiscard]] int fullCount() const;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) const override;
not_null<PeerData*> _peer;
const base::flat_set<not_null<UserData*>> _alreadyIn;
const int _fullInCount = 0;
mutable base::flat_set<not_null<UserData*>> _skippedUsers;
mutable base::flat_set<not_null<UserData*>> _inGroup;
rpl::event_stream<not_null<UserData*>> _rowAdded;
};
class InviteContactsController final : public AddParticipantsBoxController {
public:
InviteContactsController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
rpl::producer<not_null<UserData*>> discoveredInGroup);
private:
void prepareViewHook() override;
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override;
const not_null<const base::flat_set<not_null<UserData*>>*> _inGroup;
rpl::producer<not_null<UserData*>> _discoveredInGroup;
rpl::lifetime _lifetime;
};
[[nodiscard]] object_ptr<Ui::RpWidget> CreateSectionSubtitle(
QWidget *parent,
rpl::producer<QString> text) {
auto result = object_ptr<Ui::FixedHeightWidget>(
parent,
st::searchedBarHeight);
const auto raw = result.data();
raw->paintRequest(
) | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(raw);
p.fillRect(clip, st::groupCallMembersBgOver);
}, raw->lifetime());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
std::move(text),
st::groupCallBoxLabel);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto padding = st::groupCallInviteDividerPadding;
const auto available = width - padding.left() - padding.right();
label->resizeToNaturalWidth(available);
label->moveToLeft(padding.left(), padding.top(), width);
}, label->lifetime());
return result;
}
InviteController::InviteController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
int fullInCount)
base::flat_set<not_null<UserData*>> alreadyIn)
: ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members)
, _peer(peer)
, _alreadyIn(std::move(alreadyIn))
, _fullInCount(std::max(fullInCount, int(_alreadyIn.size()))) {
_skippedUsers.emplace(peer->session().user());
, _alreadyIn(std::move(alreadyIn)) {
SubscribeToMigration(
_peer,
lifetime(),
@ -98,8 +147,14 @@ InviteController::InviteController(
}
void InviteController::prepare() {
delegate()->peerListSetHideEmpty(true);
ParticipantsBoxController::prepare();
delegate()->peerListSetTitle(tr::lng_group_call_invite_title());
delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
nullptr,
tr::lng_group_call_invite_members()));
delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
nullptr,
tr::lng_group_call_invite_members()));
}
void InviteController::rowClicked(not_null<PeerListRow*> row) {
@ -115,44 +170,71 @@ base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
void InviteController::itemDeselectedHook(not_null<PeerData*> peer) {
}
int InviteController::alreadyInCount() const {
return std::max(_fullInCount, int(_alreadyIn.size()));
bool InviteController::hasRowFor(not_null<PeerData*> peer) const {
return (delegate()->peerListFindRow(peer->id) != nullptr);
}
bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
return _alreadyIn.contains(user);
}
int InviteController::fullCount() const {
return alreadyInCount() + delegate()->peerListSelectedRowsCount();
}
std::unique_ptr<PeerListRow> InviteController::createRow(
not_null<UserData*> user) const {
if (user->isSelf() || user->isBot()) {
_skippedUsers.emplace(user);
return nullptr;
}
auto result = std::make_unique<PeerListRow>(user);
_rowAdded.fire_copy(user);
_inGroup.emplace(user);
if (isAlreadyIn(user)) {
result->setDisabledState(PeerListRow::State::DisabledChecked);
}
return result;
}
std::variant<int, not_null<UserData*>> InviteController::inviteSelectedUsers(
not_null<PeerListBox*> box,
not_null<GroupCall*> call) const {
const auto rows = box->peerListCollectSelectedRows();
const auto users = ranges::view::all(
rows
) | ranges::view::transform([](not_null<PeerData*> peer) {
Expects(peer->isUser());
Expects(!peer->isSelf());
auto InviteController::peersWithRows() const
-> not_null<const base::flat_set<not_null<UserData*>>*> {
return &_inGroup;
}
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
return call->inviteUsers(users);
rpl::producer<not_null<UserData*>> InviteController::rowAdded() const {
return _rowAdded.events();
}
InviteContactsController::InviteContactsController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> alreadyIn,
not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
rpl::producer<not_null<UserData*>> discoveredInGroup)
: AddParticipantsBoxController(peer, std::move(alreadyIn))
, _inGroup(inGroup)
, _discoveredInGroup(std::move(discoveredInGroup)) {
}
void InviteContactsController::prepareViewHook() {
AddParticipantsBoxController::prepareViewHook();
delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
nullptr,
tr::lng_contacts_header()));
delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
nullptr,
tr::lng_group_call_invite_search_results()));
std::move(
_discoveredInGroup
) | rpl::start_with_next([=](not_null<UserData*> user) {
if (auto row = delegate()->peerListFindRow(user->id)) {
delegate()->peerListRemoveRow(row);
}
}, _lifetime);
}
std::unique_ptr<PeerListRow> InviteContactsController::createRow(
not_null<UserData*> user) {
return _inGroup->contains(user)
? nullptr
: AddParticipantsBoxController::createRow(user);
}
} // namespace
@ -197,6 +279,21 @@ void LeaveGroupCallBox(
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
void GroupCallConfirmBox(
not_null<Ui::GenericBox*> box,
const QString &text,
rpl::producer<QString> button,
Fn<void()> callback) {
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
text,
st::groupCallBoxLabel),
st::boxPadding);
box->addButton(std::move(button), callback);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
}
GroupPanel::GroupPanel(not_null<GroupCall*> call)
: _call(call)
, _peer(call->peer())
@ -362,7 +459,7 @@ void GroupPanel::initControls() {
});
_settings->setText(tr::lng_menu_settings());
_hangup->setText(tr::lng_box_leave());
_hangup->setText(tr::lng_group_call_leave());
_members->desiredHeightValue(
) | rpl::start_with_next([=] {
@ -460,52 +557,131 @@ void GroupPanel::addMembers() {
alreadyIn.emplace(_peer->session().user());
auto controller = std::make_unique<InviteController>(
_peer,
std::move(alreadyIn),
real->fullCount());
alreadyIn);
controller->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
const auto weak = base::make_weak(_call);
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
box->addButton(tr::lng_group_call_invite_button(), [=] {
if (const auto call = weak.get()) {
const auto result = controller->inviteSelectedUsers(box, call);
auto contactsController = std::make_unique<InviteContactsController>(
_peer,
std::move(alreadyIn),
controller->peersWithRows(),
controller->rowAdded());
contactsController->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
.st = &st::defaultToast,
});
}
} else {
Unexpected("Result in GroupCall::inviteUsers.");
}
const auto weak = base::make_weak(_call);
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
const auto call = weak.get();
if (!call) {
return;
}
const auto result = call->inviteUsers(users);
if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold((*user)->firstName),
Ui::Text::WithEntities),
.st = &st::defaultToast,
});
} else if (const auto count = std::get_if<int>(&result)) {
if (*count > 0) {
Ui::Toast::Show(
widget(),
Ui::Toast::Config{
.text = tr::lng_group_call_invite_done_many(
tr::now,
lt_count,
*count,
Ui::Text::RichLangValue),
.st = &st::defaultToast,
});
}
box->closeBox();
} else {
Unexpected("Result in GroupCall::inviteUsers.");
}
};
const auto inviteWithAdd = [=](
const std::vector<not_null<UserData*>> &users,
const std::vector<not_null<UserData*>> &nonMembers,
Fn<void()> finish) {
_peer->session().api().addChatParticipants(
_peer,
nonMembers,
[=](bool) { invite(users); finish(); });
};
const auto inviteWithConfirmation = [=](
const std::vector<not_null<UserData*>> &users,
const std::vector<not_null<UserData*>> &nonMembers,
Fn<void()> finish) {
if (nonMembers.empty()) {
invite(users);
finish();
return;
}
const auto name = _peer->name;
const auto text = (nonMembers.size() == 1)
? tr::lng_group_call_add_to_group_one(
tr::now,
lt_user,
nonMembers.front()->shortName(),
lt_group,
name)
: (nonMembers.size() < users.size())
? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name)
: tr::lng_group_call_add_to_group_all(tr::now, lt_group, name);
const auto shared = std::make_shared<QPointer<Ui::GenericBox>>();
const auto finishWithConfirm = [=] {
if (*shared) {
(*shared)->closeBox();
}
finish();
};
auto box = Box(
GroupCallConfirmBox,
text,
tr::lng_participant_invite(),
[=] { inviteWithAdd(users, nonMembers, finishWithConfirm); });
*shared = box.data();
_layerBg->showBox(std::move(box));
};
auto initBox = [=, controller = controller.get()](
not_null<PeerListsBox*> box) {
box->setTitle(tr::lng_group_call_invite_title());
box->addButton(tr::lng_group_call_invite_button(), [=] {
const auto rows = box->collectSelectedRows();
const auto users = ranges::view::all(
rows
) | ranges::view::transform([](not_null<PeerData*> peer) {
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
const auto nonMembers = ranges::view::all(
users
) | ranges::view::filter([&](not_null<UserData*> user) {
return !controller->hasRowFor(user);
}) | ranges::to_vector;
const auto finish = [box = Ui::MakeWeak(box)]() {
if (box) {
box->closeBox();
}
};
inviteWithConfirmation(users, nonMembers, finish);
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
};
_layerBg->showBox(Box<PeerListBox>(std::move(controller), initBox));
auto controllers = std::vector<std::unique_ptr<PeerListController>>();
controllers.push_back(std::move(controller));
controllers.push_back(std::move(contactsController));
_layerBg->showBox(Box<PeerListsBox>(std::move(controllers), initBox));
}
void GroupPanel::kickMember(not_null<UserData*> user) {

View File

@ -33,7 +33,7 @@ GroupCall::GroupCall(
uint64 accessHash)
: _id(id)
, _accessHash(accessHash)
, _peer(peer) // #TODO calls migration
, _peer(peer)
, _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) {
}

View File

@ -258,10 +258,6 @@ int InnerWidget::peerListSelectedRowsCount() {
return 0;
}
std::vector<not_null<PeerData*>> InnerWidget::peerListCollectSelectedRows() {
return {};
}
void InnerWidget::peerListScrollToTop() {
_scrollToRequests.fire({ -1, -1 });
}

View File

@ -52,7 +52,6 @@ private:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;

View File

@ -57,7 +57,6 @@ public:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;
@ -130,11 +129,6 @@ int ListDelegate::peerListSelectedRowsCount() {
return 0;
}
auto ListDelegate::peerListCollectSelectedRows()
-> std::vector<not_null<PeerData*>> {
return {};
}
void ListDelegate::peerListScrollToTop() {
}

View File

@ -493,7 +493,7 @@ void ActionsFiller::addInviteToGroupAction(
_wrap,
tr::lng_profile_invite_to_group(),
CanInviteBotToGroupValue(user),
[=] { AddBotToGroupBoxController::Start(controller, user); });
[=] { AddBotToGroupBoxController::Start(user); });
}
void ActionsFiller::addShareContactAction(not_null<UserData*> user) {

View File

@ -423,10 +423,6 @@ int Members::peerListSelectedRowsCount() {
return 0;
}
std::vector<not_null<PeerData*>> Members::peerListCollectSelectedRows() {
return {};
}
void Members::peerListScrollToTop() {
_scrollToRequests.fire({ -1, -1 });
}

View File

@ -63,7 +63,6 @@ private:
void peerListSetAdditionalTitle(rpl::producer<QString> title) override;
bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
int peerListSelectedRowsCount() override;
std::vector<not_null<PeerData*>> peerListCollectSelectedRows() override;
void peerListScrollToTop() override;
void peerListAddSelectedPeerInBunch(
not_null<PeerData*> peer) override;

View File

@ -42,6 +42,7 @@ void init() {
u"rstrtmgr.dll"_q,
u"psapi.dll"_q,
u"user32.dll"_q,
u"thai.dll"_q,
};
for (const auto &lib : list) {
SafeLoadLibrary(lib);

View File

@ -51,8 +51,7 @@ class BlockPeerBoxController
: public ChatsListBoxController
, private base::Subscriber {
public:
explicit BlockPeerBoxController(
not_null<Window::SessionNavigation*> navigation);
explicit BlockPeerBoxController(not_null<Main::Session*> session);
Main::Session &session() const override;
void rowClicked(not_null<PeerListRow*> row) override;
@ -72,19 +71,19 @@ protected:
private:
void updateIsBlocked(not_null<PeerListRow*> row, PeerData *peer) const;
const not_null<Window::SessionNavigation*> _navigation;
const not_null<Main::Session*> _session;
Fn<void(not_null<PeerData*> peer)> _blockPeerCallback;
};
BlockPeerBoxController::BlockPeerBoxController(
not_null<Window::SessionNavigation*> navigation)
: ChatsListBoxController(navigation)
, _navigation(navigation) {
not_null<Main::Session*> session)
: ChatsListBoxController(session)
, _session(session) {
}
Main::Session &BlockPeerBoxController::session() const {
return _navigation->session();
return *_session;
}
void BlockPeerBoxController::prepareViewHook() {
@ -294,7 +293,8 @@ void BlockedBoxController::handleBlockedEvent(not_null<PeerData*> user) {
void BlockedBoxController::BlockNewPeer(
not_null<Window::SessionController*> window) {
auto controller = std::make_unique<BlockPeerBoxController>(window);
auto controller = std::make_unique<BlockPeerBoxController>(
&window->session());
auto initBox = [=, controller = controller.get()](
not_null<PeerListBox*> box) {
controller->setBlockPeerCallback([=](not_null<PeerData*> peer) {

View File

@ -483,7 +483,7 @@ void Filler::addUserActions(not_null<UserData*> user) {
using AddBotToGroup = AddBotToGroupBoxController;
_addAction(
tr::lng_profile_invite_to_group(tr::now),
[=] { AddBotToGroup::Start(controller, user); });
[=] { AddBotToGroup::Start(user); });
}
addPollAction(user);
if (user->canExportChatHistory()) {
@ -803,7 +803,7 @@ void PeerMenuShareContactBox(
};
*weak = Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
navigation,
&navigation->session(),
std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(tr::lng_cancel(), [=] {
@ -1010,7 +1010,7 @@ QPointer<Ui::RpWidget> ShowForwardMessagesBox(
};
*weak = Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(
navigation,
&navigation->session(),
std::move(callback)),
std::move(initBox)), Ui::LayerOption::KeepOther);
return weak->data();

View File

@ -194,7 +194,7 @@ void SessionNavigation::showPeerByLinkResolved(
const auto user = peer->asUser();
if (user && user->isBot() && !info.startToken.isEmpty()) {
user->botInfo->shareGameShortName = info.startToken;
AddBotToGroupBoxController::Start(this, user);
AddBotToGroupBoxController::Start(user);
} else {
crl::on_main(this, [=] {
showPeerHistory(peer->id, params);
@ -207,7 +207,7 @@ void SessionNavigation::showPeerByLinkResolved(
&& !user->botInfo->cantJoinGroups
&& !info.startToken.isEmpty()) {
user->botInfo->startGroupToken = info.startToken;
AddBotToGroupBoxController::Start(this, user);
AddBotToGroupBoxController::Start(user);
} else if (user && user->isBot()) {
// Always open bot chats, even from mention links.
crl::on_main(this, [=] {