Support auto-migrate to supergroups.

This commit is contained in:
John Preston 2019-01-13 17:28:05 +04:00
parent b236844c94
commit 3c44bdb6b7
23 changed files with 505 additions and 285 deletions

View File

@ -1168,6 +1168,69 @@ void ApiWrap::requestPeer(not_null<PeerData*> peer) {
_peerRequests.insert(peer, requestId); _peerRequests.insert(peer, requestId);
} }
void ApiWrap::migrateChat(
not_null<ChatData*> chat,
FnMut<void(not_null<ChannelData*>)> done,
FnMut<void(const RPCError &)> fail) {
const auto callback = [&] {
return MigrateCallbacks{ std::move(done), std::move(fail) };
};
const auto i = _migrateCallbacks.find(chat);
if (i != end(_migrateCallbacks)) {
i->second.push_back(callback());
return;
} else if (const auto channel = chat->migrateTo()) {
Notify::peerUpdatedDelayed(
chat,
Notify::PeerUpdate::Flag::MigrationChanged);
crl::on_main([=, done = std::move(done)]() mutable {
Notify::peerUpdatedSendDelayed();
done(channel);
});
} else if (chat->isDeactivated()) {
crl::on_main([fail = std::move(fail)]() mutable {
fail(RPCError::Local(
"BAD_MIGRATION",
"Chat is already deactivated"));
});
return;
} else if (!chat->amCreator()) {
crl::on_main([fail = std::move(fail)]() mutable {
fail(RPCError::Local(
"BAD_MIGRATION",
"Current user is not the creator of that chat"));
});
return;
}
_migrateCallbacks.emplace(chat).first->second.push_back(callback());
request(MTPmessages_MigrateChat(
chat->inputChat
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
Notify::peerUpdatedSendDelayed();
const auto channel = chat->migrateTo();
if (auto handlers = _migrateCallbacks.take(chat)) {
for (auto &handler : *handlers) {
if (channel) {
handler.done(channel);
} else {
handler.fail(RPCError::Local(
"MIGRATION_FAIL",
"No channel"));
}
}
}
}).fail([=](const RPCError &error) {
if (auto handlers = _migrateCallbacks.take(chat)) {
for (auto &handler : *handlers) {
handler.fail(error);
}
}
}).send();
}
void ApiWrap::markMediaRead( void ApiWrap::markMediaRead(
const base::flat_set<not_null<HistoryItem*>> &items) { const base::flat_set<not_null<HistoryItem*>> &items) {
auto markedIds = QVector<MTPint>(); auto markedIds = QVector<MTPint>();

View File

@ -137,6 +137,11 @@ public:
not_null<UserData*> user, not_null<UserData*> user,
const MTPUserFull &result); const MTPUserFull &result);
void migrateChat(
not_null<ChatData*> chat,
FnMut<void(not_null<ChannelData*>)> done,
FnMut<void(const RPCError &)> fail = nullptr);
void markMediaRead(const base::flat_set<not_null<HistoryItem*>> &items); void markMediaRead(const base::flat_set<not_null<HistoryItem*>> &items);
void markMediaRead(not_null<HistoryItem*> item); void markMediaRead(not_null<HistoryItem*> item);
@ -727,6 +732,14 @@ private:
FnMut<void(const MTPChatInvite &result)> _checkInviteDone; FnMut<void(const MTPChatInvite &result)> _checkInviteDone;
FnMut<void(const RPCError &error)> _checkInviteFail; FnMut<void(const RPCError &error)> _checkInviteFail;
struct MigrateCallbacks {
FnMut<void(not_null<ChannelData*>)> done;
FnMut<void(const RPCError&)> fail;
};
base::flat_map<
not_null<ChatData*>,
std::vector<MigrateCallbacks>> _migrateCallbacks;
std::vector<FnMut<void(const MTPUser &)>> _supportContactCallbacks; std::vector<FnMut<void(const MTPUser &)>> _supportContactCallbacks;
base::flat_map<FullMsgId, not_null<PeerData*>> _peerPhotoUploads; base::flat_map<FullMsgId, not_null<PeerData*>> _peerPhotoUploads;

View File

@ -608,8 +608,7 @@ void GroupInfoBox::submit() {
}; };
Ui::show( Ui::show(
Box<PeerListBox>( Box<PeerListBox>(
std::make_unique<AddParticipantsBoxController>( std::make_unique<AddParticipantsBoxController>(),
nullptr),
std::move(initBox)), std::move(initBox)),
LayerOption::KeepOther); LayerOption::KeepOther);
} }

View File

@ -318,12 +318,15 @@ int PeerListBox::peerListSelectedRowsCount() {
return _select ? _select->entity()->getItemsCount() : 0; return _select ? _select->entity()->getItemsCount() : 0;
} }
std::vector<not_null<PeerData*>> PeerListBox::peerListCollectSelectedRows() { auto PeerListBox::peerListCollectSelectedRows()
auto result = std::vector<not_null<PeerData*>> {}; -> std::vector<not_null<PeerData*>> {
auto items = _select ? _select->entity()->getItems() : QVector<uint64> {}; auto result = std::vector<not_null<PeerData*>>();
auto items = _select
? _select->entity()->getItems()
: QVector<uint64>();
if (!items.empty()) { if (!items.empty()) {
result.reserve(items.size()); result.reserve(items.size());
for_const (auto itemId, items) { for (const auto itemId : items) {
result.push_back(App::peer(itemId)); result.push_back(App::peer(itemId));
} }
} }

View File

@ -303,6 +303,10 @@ public:
std::unique_ptr<SavedStateBase> state) { std::unique_ptr<SavedStateBase> state) {
} }
rpl::lifetime &lifetime() {
return _lifetime;
}
protected: protected:
not_null<PeerListSearchDelegate*> delegate() const { not_null<PeerListSearchDelegate*> delegate() const {
return _delegate; return _delegate;
@ -310,6 +314,7 @@ protected:
private: private:
PeerListSearchDelegate *_delegate = nullptr; PeerListSearchDelegate *_delegate = nullptr;
rpl::lifetime _lifetime;
}; };
@ -320,7 +325,8 @@ public:
}; };
// Search works only with RowId == peer->id. // Search works only with RowId == peer->id.
PeerListController(std::unique_ptr<PeerListSearchController> searchController = nullptr); PeerListController(
std::unique_ptr<PeerListSearchController> searchController = {});
void setDelegate(not_null<PeerListDelegate*> delegate) { void setDelegate(not_null<PeerListDelegate*> delegate) {
_delegate = delegate; _delegate = delegate;

View File

@ -307,7 +307,9 @@ bool ChatsListBoxController::appendRow(not_null<History*> history) {
return false; return false;
} }
ContactsBoxController::ContactsBoxController(std::unique_ptr<PeerListSearchController> searchController) : PeerListController(std::move(searchController)) { ContactsBoxController::ContactsBoxController(
std::unique_ptr<PeerListSearchController> searchController)
: PeerListController(std::move(searchController)) {
} }
void ContactsBoxController::prepare() { void ContactsBoxController::prepare() {

View File

@ -114,9 +114,13 @@ private:
}; };
class ContactsBoxController : public PeerListController, protected base::Subscriber { class ContactsBoxController
: public PeerListController
, protected base::Subscriber {
public: public:
ContactsBoxController(std::unique_ptr<PeerListSearchController> searchController = std::make_unique<PeerListGlobalSearchController>()); ContactsBoxController(
std::unique_ptr<PeerListSearchController> searchController
= std::make_unique<PeerListGlobalSearchController>());
void prepare() override final; void prepare() override final;
std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final; std::unique_ptr<PeerListRow> createSearchRow(not_null<PeerData*> peer) override final;

View File

@ -40,61 +40,63 @@ base::flat_set<not_null<UserData*>> GetAlreadyInFromPeer(PeerData *peer) {
return {}; return {};
} }
bool InviteSelectedUsers(
not_null<PeerListBox*> box,
not_null<PeerData*> chat) {
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());
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
if (users.empty()) {
return false;
}
chat->session().api().addChatParticipants(chat, users);
return true;
}
} // namespace } // namespace
AddParticipantsBoxController::AddParticipantsBoxController(PeerData *peer) AddParticipantsBoxController::AddParticipantsBoxController()
: ContactsBoxController(std::make_unique<PeerListGlobalSearchController>()) : ContactsBoxController(
, _peer(peer) std::make_unique<PeerListGlobalSearchController>()) {
, _alreadyIn(GetAlreadyInFromPeer(peer)) {
} }
AddParticipantsBoxController::AddParticipantsBoxController( AddParticipantsBoxController::AddParticipantsBoxController(
not_null<ChannelData*> channel, not_null<PeerData*> peer)
: AddParticipantsBoxController(peer, GetAlreadyInFromPeer(peer)) {
}
AddParticipantsBoxController::AddParticipantsBoxController(
not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> &&alreadyIn) base::flat_set<not_null<UserData*>> &&alreadyIn)
: ContactsBoxController(std::make_unique<PeerListGlobalSearchController>()) : ContactsBoxController(std::make_unique<PeerListGlobalSearchController>())
, _peer(channel) , _peer(peer)
, _alreadyIn(std::move(alreadyIn)) { , _alreadyIn(std::move(alreadyIn)) {
subscribeToMigration();
}
void AddParticipantsBoxController::subscribeToMigration() {
Expects(_peer != nullptr);
SubscribeToMigration(
_peer,
lifetime(),
[=](not_null<ChannelData*> channel) { _peer = channel; });
} }
void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) { void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
auto count = fullCount(); auto count = fullCount();
auto limit = (_peer && _peer->isMegagroup()) ? Global::MegagroupSizeMax() : Global::ChatSizeMax(); auto limit = _peer && (_peer->isChat() || _peer->isMegagroup())
? Global::MegagroupSizeMax()
: Global::ChatSizeMax();
if (count < limit || row->checked()) { if (count < limit || row->checked()) {
delegate()->peerListSetRowChecked(row, !row->checked()); delegate()->peerListSetRowChecked(row, !row->checked());
updateTitle(); updateTitle();
} else if (auto channel = _peer ? _peer->asChannel() : nullptr) { } else if (const auto channel = _peer ? _peer->asChannel() : nullptr) {
if (!_peer->isMegagroup()) { if (!_peer->isMegagroup()) {
Ui::show( Ui::show(
Box<MaxInviteBox>(_peer->asChannel()), Box<MaxInviteBox>(_peer->asChannel()),
LayerOption::KeepOther); LayerOption::KeepOther);
} }
} else if (count >= Global::ChatSizeMax() && count < Global::MegagroupSizeMax()) { } else if (count >= Global::ChatSizeMax()
&& count < Global::MegagroupSizeMax()) {
// #TODO groups new error about "after creating"
Ui::show( Ui::show(
Box<InformBox>(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), Box<InformBox>(lng_profile_add_more_after_upgrade(
lt_count,
Global::MegagroupSizeMax())),
LayerOption::KeepOther); LayerOption::KeepOther);
} }
} }
void AddParticipantsBoxController::itemDeselectedHook(not_null<PeerData*> peer) { void AddParticipantsBoxController::itemDeselectedHook(
not_null<PeerData*> peer) {
updateTitle(); updateTitle();
} }
@ -106,23 +108,26 @@ int AddParticipantsBoxController::alreadyInCount() const {
if (!_peer) { if (!_peer) {
return 1; // self return 1; // self
} }
if (auto chat = _peer->asChat()) { if (const auto chat = _peer->asChat()) {
return qMax(chat->count, 1); return qMax(chat->count, 1);
} else if (auto channel = _peer->asChannel()) { } else if (const auto channel = _peer->asChannel()) {
return qMax(channel->membersCount(), int(_alreadyIn.size())); return qMax(channel->membersCount(), int(_alreadyIn.size()));
} }
Unexpected("User in AddParticipantsBoxController::alreadyInCount"); Unexpected("User in AddParticipantsBoxController::alreadyInCount");
} }
bool AddParticipantsBoxController::isAlreadyIn(not_null<UserData*> user) const { bool AddParticipantsBoxController::isAlreadyIn(
not_null<UserData*> user) const {
if (!_peer) { if (!_peer) {
return false; return false;
} }
if (auto chat = _peer->asChat()) { if (const auto chat = _peer->asChat()) {
return chat->participants.contains(user);
} else if (auto channel = _peer->asChannel()) {
return _alreadyIn.contains(user) return _alreadyIn.contains(user)
|| (channel->isMegagroup() && base::contains(channel->mgInfo->lastParticipants, user)); || chat->participants.contains(user);
} else if (const auto channel = _peer->asChannel()) {
return _alreadyIn.contains(user)
|| (channel->isMegagroup()
&& base::contains(channel->mgInfo->lastParticipants, user));
} }
Unexpected("User in AddParticipantsBoxController::isAlreadyIn"); Unexpected("User in AddParticipantsBoxController::isAlreadyIn");
} }
@ -144,25 +149,49 @@ std::unique_ptr<PeerListRow> AddParticipantsBoxController::createRow(
} }
void AddParticipantsBoxController::updateTitle() { void AddParticipantsBoxController::updateTitle() {
auto additional = (_peer && _peer->isChannel() && !_peer->isMegagroup()) const auto additional = (_peer
&& _peer->isChannel()
&& !_peer->isMegagroup())
? QString() : ? QString() :
QString("%1 / %2").arg(fullCount()).arg(Global::MegagroupSizeMax()); QString("%1 / %2").arg(fullCount()).arg(Global::MegagroupSizeMax());
delegate()->peerListSetTitle(langFactory(lng_profile_add_participant)); delegate()->peerListSetTitle(langFactory(lng_profile_add_participant));
delegate()->peerListSetAdditionalTitle([additional] { return additional; }); delegate()->peerListSetAdditionalTitle([=] { return additional; });
}
bool AddParticipantsBoxController::inviteSelectedUsers(
not_null<PeerListBox*> box) const {
Expects(_peer != nullptr);
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());
return not_null<UserData*>(peer->asUser());
}) | ranges::to_vector;
if (users.empty()) {
return false;
}
_peer->session().api().addChatParticipants(_peer, users);
return true;
} }
void AddParticipantsBoxController::Start(not_null<ChatData*> chat) { void AddParticipantsBoxController::Start(not_null<ChatData*> chat) {
auto controller = std::make_unique<AddParticipantsBoxController>(chat);
const auto weak = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) { auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_participant_invite), [=] { box->addButton(langFactory(lng_participant_invite), [=] {
if (InviteSelectedUsers(box, chat)) { if (weak->inviteSelectedUsers(box)) {
Ui::showPeerHistory(chat, ShowAtTheEndMsgId); Ui::showPeerHistory(chat, ShowAtTheEndMsgId);
} }
}); });
box->addButton(langFactory(lng_cancel), [box] { box->closeBox(); }); box->addButton(langFactory(lng_cancel), [=] { box->closeBox(); });
}; };
Ui::show( Ui::show(
Box<PeerListBox>( Box<PeerListBox>(
std::make_unique<AddParticipantsBoxController>(chat), std::move(controller),
std::move(initBox)), std::move(initBox)),
LayerOption::KeepOther); LayerOption::KeepOther);
} }
@ -171,10 +200,13 @@ void AddParticipantsBoxController::Start(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
base::flat_set<not_null<UserData*>> &&alreadyIn, base::flat_set<not_null<UserData*>> &&alreadyIn,
bool justCreated) { bool justCreated) {
auto initBox = [channel, justCreated](not_null<PeerListBox*> box) { auto controller = std::make_unique<AddParticipantsBoxController>(
auto subscription = std::make_shared<rpl::lifetime>(); channel,
box->addButton(langFactory(lng_participant_invite), [=, copy = subscription] { std::move(alreadyIn));
if (InviteSelectedUsers(box, channel)) { const auto weak = controller.get();
auto initBox = [=](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_participant_invite), [=] {
if (weak->inviteSelectedUsers(box)) {
if (channel->isMegagroup()) { if (channel->isMegagroup()) {
Ui::showPeerHistory(channel, ShowAtTheEndMsgId); Ui::showPeerHistory(channel, ShowAtTheEndMsgId);
} else { } else {
@ -182,18 +214,18 @@ void AddParticipantsBoxController::Start(
} }
} }
}); });
box->addButton(langFactory(justCreated ? lng_create_group_skip : lng_cancel), [box] { box->closeBox(); }); box->addButton(
langFactory(justCreated ? lng_create_group_skip : lng_cancel),
[=] { box->closeBox(); });
if (justCreated) { if (justCreated) {
box->boxClosing() | rpl::start_with_next([=] { box->boxClosing() | rpl::start_with_next([=] {
Ui::showPeerHistory(channel, ShowAtTheEndMsgId); Ui::showPeerHistory(channel, ShowAtTheEndMsgId);
}, *subscription); }, box->lifetime());
} }
}; };
Ui::show( Ui::show(
Box<PeerListBox>( Box<PeerListBox>(
std::make_unique<AddParticipantsBoxController>( std::move(controller),
channel,
std::move(alreadyIn)),
std::move(initBox)), std::move(initBox)),
LayerOption::KeepOther); LayerOption::KeepOther);
} }
@ -221,9 +253,23 @@ AddSpecialBoxController::AddSpecialBoxController(
, _additional(peer, Role::Members) , _additional(peer, Role::Members)
, _adminDoneCallback(std::move(adminDoneCallback)) , _adminDoneCallback(std::move(adminDoneCallback))
, _bannedDoneCallback(std::move(bannedDoneCallback)) { , _bannedDoneCallback(std::move(bannedDoneCallback)) {
subscribeToMigration();
} }
std::unique_ptr<PeerListRow> AddSpecialBoxController::createSearchRow(not_null<PeerData*> peer) { void AddSpecialBoxController::subscribeToMigration() {
SubscribeToMigration(
_peer,
lifetime(),
[=](not_null<ChannelData*> channel) { migrate(channel); });
}
void AddSpecialBoxController::migrate(not_null<ChannelData*> channel) {
_peer = channel;
_additional.migrate(channel);
}
std::unique_ptr<PeerListRow> AddSpecialBoxController::createSearchRow(
not_null<PeerData*> peer) {
if (peer->isSelf()) { if (peer->isSelf()) {
return nullptr; return nullptr;
} }
@ -237,9 +283,12 @@ void AddSpecialBoxController::prepare() {
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled); delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
const auto title = [&] { const auto title = [&] {
switch (_role) { switch (_role) {
case Role::Admins: return langFactory(lng_channel_add_admin); case Role::Admins:
case Role::Restricted: return langFactory(lng_channel_add_restricted); return langFactory(lng_channel_add_admin);
case Role::Kicked: return langFactory(lng_channel_add_banned); case Role::Restricted:
return langFactory(lng_channel_add_restricted);
case Role::Kicked:
return langFactory(lng_channel_add_banned);
} }
Unexpected("Role in AddSpecialBoxController::prepare()"); Unexpected("Role in AddSpecialBoxController::prepare()");
}(); }();
@ -723,6 +772,14 @@ AddSpecialBoxSearchController::AddSpecialBoxSearchController(
: _peer(peer) : _peer(peer)
, _additional(additional) , _additional(additional)
, _timer([=] { searchOnServer(); }) { , _timer([=] { searchOnServer(); }) {
subscribeToMigration();
}
void AddSpecialBoxSearchController::subscribeToMigration() {
SubscribeToMigration(
_peer,
lifetime(),
[=](not_null<ChannelData*> channel) { _peer = channel; });
} }
void AddSpecialBoxSearchController::searchQuery(const QString &query) { void AddSpecialBoxSearchController::searchQuery(const QString &query) {
@ -752,10 +809,13 @@ bool AddSpecialBoxSearchController::isLoading() {
} }
bool AddSpecialBoxSearchController::searchParticipantsInCache() { bool AddSpecialBoxSearchController::searchParticipantsInCache() {
auto it = _participantsCache.find(_query); const auto i = _participantsCache.find(_query);
if (it != _participantsCache.cend()) { if (i != _participantsCache.cend()) {
_requestId = 0; _requestId = 0;
searchParticipantsDone(_requestId, it->second.result, it->second.requestedCount); searchParticipantsDone(
_requestId,
i->second.result,
i->second.requestedCount);
return true; return true;
} }
return false; return false;
@ -837,8 +897,7 @@ void AddSpecialBoxSearchController::searchParticipantsDone(
const auto channel = _peer->asChannel(); const auto channel = _peer->asChannel();
auto query = _query; auto query = _query;
if (requestId) { if (requestId) {
auto &session = channel->session(); const auto addToCache = [&](auto&&...) {
session.api().parseChannelParticipants(channel, result, [&](auto&&...) {
auto it = _participantsQueries.find(requestId); auto it = _participantsQueries.find(requestId);
if (it != _participantsQueries.cend()) { if (it != _participantsQueries.cend()) {
query = it->second.text; query = it->second.text;
@ -849,7 +908,11 @@ void AddSpecialBoxSearchController::searchParticipantsDone(
} }
_participantsQueries.erase(it); _participantsQueries.erase(it);
} }
}); };
channel->session().api().parseChannelParticipants(
channel,
result,
addToCache);
} }
if (_requestId != requestId) { if (_requestId != requestId) {
@ -857,14 +920,14 @@ void AddSpecialBoxSearchController::searchParticipantsDone(
} }
_requestId = 0; _requestId = 0;
result.match([&](const MTPDchannels_channelParticipants &data) { result.match([&](const MTPDchannels_channelParticipants &data) {
auto &list = data.vparticipants.v; const auto &list = data.vparticipants.v;
if (list.size() < requestedCount) { if (list.size() < requestedCount) {
// We want cache to have full information about a query with small // We want cache to have full information about a query with
// results count (if we don't need the second request). So we don't // small results count (that we don't need the second request).
// wait for an empty results list unlike the non-search peer list. // So we don't wait for empty list unlike the non-search case.
_participantsLoaded = true; _participantsLoaded = true;
if (list.empty() && _offset == 0) { if (list.empty() && _offset == 0) {
// No results, so we want to request global search immediately. // No results, request global search immediately.
loadMoreRows(); loadMoreRows();
} }
} }
@ -946,7 +1009,6 @@ void AddSpecialBoxSearchController::addChatMembers(
if (chat->participants.empty()) { if (chat->participants.empty()) {
return; return;
} }
_participantsLoaded = true;
const auto wordList = TextUtilities::PrepareSearchWords(_query); const auto wordList = TextUtilities::PrepareSearchWords(_query);
if (wordList.empty()) { if (wordList.empty()) {
@ -1024,7 +1086,8 @@ void AddSpecialBoxSearchController::addChatsContacts() {
return result; return result;
}; };
const auto dialogsIndex = getSmallestIndex(App::main()->dialogsList()); const auto dialogsIndex = getSmallestIndex(App::main()->dialogsList());
const auto contactsIndex = getSmallestIndex(App::main()->contactsNoDialogsList()); const auto contactsIndex = getSmallestIndex(
App::main()->contactsNoDialogsList());
const auto filterAndAppend = [&](const Dialogs::List *list) { const auto filterAndAppend = [&](const Dialogs::List *list) {
if (!list) { if (!list) {

View File

@ -18,9 +18,10 @@ public:
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
base::flat_set<not_null<UserData*>> &&alreadyIn); base::flat_set<not_null<UserData*>> &&alreadyIn);
AddParticipantsBoxController(PeerData *peer); AddParticipantsBoxController();
AddParticipantsBoxController(not_null<PeerData*> peer);
AddParticipantsBoxController( AddParticipantsBoxController(
not_null<ChannelData*> channel, not_null<PeerData*> peer,
base::flat_set<not_null<UserData*>> &&alreadyIn); base::flat_set<not_null<UserData*>> &&alreadyIn);
using ContactsBoxController::ContactsBoxController; using ContactsBoxController::ContactsBoxController;
@ -39,11 +40,12 @@ private:
base::flat_set<not_null<UserData*>> &&alreadyIn, base::flat_set<not_null<UserData*>> &&alreadyIn,
bool justCreated); bool justCreated);
bool inviteSelectedUsers(not_null<PeerListBox*> box) const;
void subscribeToMigration();
int alreadyInCount() const; int alreadyInCount() const;
bool isAlreadyIn(not_null<UserData*> user) const; bool isAlreadyIn(not_null<UserData*> user) const;
int fullCount() const; int fullCount() const;
void updateTitle(); void updateTitle();
bool inviteSelectedUsers(not_null<PeerData*> chat) const;
PeerData *_peer = nullptr; PeerData *_peer = nullptr;
base::flat_set<not_null<UserData*>> _alreadyIn; base::flat_set<not_null<UserData*>> _alreadyIn;
@ -103,6 +105,9 @@ private:
bool prependRow(not_null<UserData*> user); bool prependRow(not_null<UserData*> user);
std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const; std::unique_ptr<PeerListRow> createRow(not_null<UserData*> user) const;
void subscribeToMigration();
void migrate(not_null<ChannelData*> channel);
not_null<PeerData*> _peer; not_null<PeerData*> _peer;
Role _role = Role::Admins; Role _role = Role::Admins;
int _offset = 0; int _offset = 0;
@ -119,7 +124,8 @@ private:
// Finds chat/channel members, then contacts, then global search results. // Finds chat/channel members, then contacts, then global search results.
class AddSpecialBoxSearchController class AddSpecialBoxSearchController
: public PeerListSearchController : public PeerListSearchController
, private MTP::Sender { , private MTP::Sender
, private base::Subscriber {
public: public:
using Role = ParticipantsBoxController::Role; using Role = ParticipantsBoxController::Role;
@ -156,6 +162,8 @@ private:
void addChatsContacts(); void addChatsContacts();
void requestGlobal(); void requestGlobal();
void subscribeToMigration();
not_null<PeerData*> _peer; not_null<PeerData*> _peer;
not_null<ParticipantsAdditionalData*> _additional; not_null<ParticipantsAdditionalData*> _additional;

View File

@ -142,7 +142,16 @@ Fn<void(
const MTPChatAdminRights &oldRights, const MTPChatAdminRights &oldRights,
const MTPChatAdminRights &newRights) { const MTPChatAdminRights &newRights) {
const auto done = [=] { onDone(newRights); }; const auto done = [=] { onDone(newRights); };
if (const auto chat = peer->asChat()) { const auto saveForChannel = [=](not_null<ChannelData*> channel) {
SaveChannelAdmin(
channel,
user,
oldRights,
newRights,
done,
onFail);
};
if (const auto chat = peer->asChatNotMigrated()) {
const auto saveChatAdmin = [&](bool isAdmin) { const auto saveChatAdmin = [&](bool isAdmin) {
SaveChatAdmin(chat, user, isAdmin, done, onFail); SaveChatAdmin(chat, user, isAdmin, done, onFail);
}; };
@ -155,16 +164,10 @@ Fn<void(
} else if (!flags) { } else if (!flags) {
saveChatAdmin(false); saveChatAdmin(false);
} else { } else {
// #TODO groups autoconv peer->session().api().migrateChat(chat, saveForChannel);
} }
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannelOrMigrated()) {
SaveChannelAdmin( saveForChannel(channel);
channel,
user,
oldRights,
newRights,
done,
onFail);
} else { } else {
Unexpected("Peer in SaveAdminCallback."); Unexpected("Peer in SaveAdminCallback.");
} }
@ -182,17 +185,7 @@ Fn<void(
const MTPChatBannedRights &oldRights, const MTPChatBannedRights &oldRights,
const MTPChatBannedRights &newRights) { const MTPChatBannedRights &newRights) {
const auto done = [=] { onDone(newRights); }; const auto done = [=] { onDone(newRights); };
if (const auto chat = peer->asChat()) { const auto saveForChannel = [=](not_null<ChannelData*> channel) {
const auto flags = newRights.match([](
const MTPDchatBannedRights &data) {
return data.vflags.v;
});
if (!flags) {
done();
} else {
// #TODO groups autoconv
}
} else if (const auto channel = peer->asChannel()) {
SaveChannelRestriction( SaveChannelRestriction(
channel, channel,
user, user,
@ -200,12 +193,51 @@ Fn<void(
newRights, newRights,
done, done,
onFail); onFail);
};
if (const auto chat = peer->asChatNotMigrated()) {
const auto flags = newRights.match([](
const MTPDchatBannedRights &data) {
return data.vflags.v;
});
if (!flags) {
done();
} else {
peer->session().api().migrateChat(chat, saveForChannel);
}
} else if (const auto channel = peer->asChannelOrMigrated()) {
saveForChannel(channel);
} else { } else {
Unexpected("Peer in SaveAdminCallback."); Unexpected("Peer in SaveAdminCallback.");
} }
}; };
} }
void SubscribeToMigration(
not_null<PeerData*> peer,
rpl::lifetime &lifetime,
Fn<void(not_null<ChannelData*>)> migrate) {
if (const auto chat = peer->asChat()) {
if (const auto channel = peer->migrateTo()) {
migrate(channel);
} else if (!chat->isDeactivated()) {
const auto alive = lifetime.make_state<base::Subscription>();
const auto handler = [=](const Notify::PeerUpdate &update) {
if (update.peer == peer) {
if (const auto channel = peer->migrateTo()) {
const auto onstack = base::duplicate(migrate);
*alive = base::Subscription();
onstack(channel);
}
}
};
*alive = Notify::PeerUpdated().add_subscription(
Notify::PeerUpdatedHandler(
Notify::PeerUpdate::Flag::MigrationChanged,
handler));
}
}
}
ParticipantsAdditionalData::ParticipantsAdditionalData( ParticipantsAdditionalData::ParticipantsAdditionalData(
not_null<PeerData*> peer, not_null<PeerData*> peer,
Role role) Role role)
@ -214,7 +246,8 @@ ParticipantsAdditionalData::ParticipantsAdditionalData(
fillFromPeer(); fillFromPeer();
} }
bool ParticipantsAdditionalData::infoLoaded(not_null<UserData*> user) const { bool ParticipantsAdditionalData::infoLoaded(
not_null<UserData*> user) const {
return _peer->isChat() return _peer->isChat()
|| (_infoNotLoaded.find(user) == end(_infoNotLoaded)); || (_infoNotLoaded.find(user) == end(_infoNotLoaded));
} }
@ -286,7 +319,8 @@ bool ParticipantsAdditionalData::isCreator(not_null<UserData*> user) const {
return (_creator == user); return (_creator == user);
} }
bool ParticipantsAdditionalData::isExternal(not_null<UserData*> user) const { bool ParticipantsAdditionalData::isExternal(
not_null<UserData*> user) const {
return _peer->isChat() return _peer->isChat()
? !_members.contains(user) ? !_members.contains(user)
: _external.find(user) != end(_external); : _external.find(user) != end(_external);
@ -531,6 +565,23 @@ UserData *ParticipantsAdditionalData::applyBanned(
return user; return user;
} }
void ParticipantsAdditionalData::migrate(not_null<ChannelData*> channel) {
_peer = channel;
fillFromChannel(channel);
for (const auto user : _admins) {
_adminRights.emplace(
user,
MTP_chatAdminRights(MTP_flags(ChatData::DefaultAdminRights())));
if (channel->amCreator()) {
_adminCanEdit.emplace(user);
}
if (_creator) {
_adminPromotedBy.emplace(user, _creator);
}
}
}
ParticipantsOnlineSorter::ParticipantsOnlineSorter( ParticipantsOnlineSorter::ParticipantsOnlineSorter(
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<PeerListDelegate*> delegate) not_null<PeerListDelegate*> delegate)
@ -608,6 +659,7 @@ ParticipantsBoxController::ParticipantsBoxController(
, _peer(peer) , _peer(peer)
, _role(role) , _role(role)
, _additional(peer, _role) { , _additional(peer, _role) {
subscribeToMigration();
if (_role == Role::Profile) { if (_role == Role::Profile) {
setupListChangeViewers(); setupListChangeViewers();
} }
@ -1183,13 +1235,14 @@ bool ParticipantsBoxController::feedMegagroupLastParticipants() {
} }
const auto info = megagroup->mgInfo.get(); const auto info = megagroup->mgInfo.get();
// //
// channelFull and channels_channelParticipants members count is desynced // channelFull and channels_channelParticipants members count desynced
// so we almost always have LastParticipantsCountOutdated that is set // so we almost always have LastParticipantsCountOutdated that is set
// inside setMembersCount() and so we almost never use lastParticipants. // inside setMembersCount() and so we almost never use lastParticipants.
// //
// => disable this check temporarily. // => disable this check temporarily.
// //
//if (info->lastParticipantsStatus != MegagroupInfo::LastParticipantsUpToDate) { //if (info->lastParticipantsStatus
// != MegagroupInfo::LastParticipantsUpToDate) {
// _channel->updateFull(); // _channel->updateFull();
// return false; // return false;
//} //}
@ -1227,7 +1280,8 @@ void ParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
} }
} }
void ParticipantsBoxController::rowActionClicked(not_null<PeerListRow*> row) { void ParticipantsBoxController::rowActionClicked(
not_null<PeerListRow*> row) {
Expects(row->peer()->isUser()); Expects(row->peer()->isUser());
const auto user = row->peer()->asUser(); const auto user = row->peer()->asUser();
@ -1599,7 +1653,8 @@ void ParticipantsBoxController::recomputeTypeFor(
} }
} }
void ParticipantsBoxController::refreshCustomStatus(not_null<PeerListRow*> row) const { void ParticipantsBoxController::refreshCustomStatus(
not_null<PeerListRow*> row) const {
const auto user = row->peer()->asUser(); const auto user = row->peer()->asUser();
if (_role == Role::Admins) { if (_role == Role::Admins) {
if (const auto by = _additional.adminPromotedBy(user)) { if (const auto by = _additional.adminPromotedBy(user)) {
@ -1623,6 +1678,18 @@ void ParticipantsBoxController::refreshCustomStatus(not_null<PeerListRow*> row)
} }
} }
void ParticipantsBoxController::subscribeToMigration() {
SubscribeToMigration(
_peer,
lifetime(),
[=](not_null<ChannelData*> channel) { migrate(channel); });
}
void ParticipantsBoxController::migrate(not_null<ChannelData*> channel) {
_peer = channel;
_additional.migrate(channel);
}
ParticipantsBoxSearchController::ParticipantsBoxSearchController( ParticipantsBoxSearchController::ParticipantsBoxSearchController(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
Role role, Role role,
@ -1686,10 +1753,13 @@ bool ParticipantsBoxSearchController::isLoading() {
} }
bool ParticipantsBoxSearchController::searchInCache() { bool ParticipantsBoxSearchController::searchInCache() {
auto it = _cache.find(_query); const auto i = _cache.find(_query);
if (it != _cache.cend()) { if (i != _cache.cend()) {
_requestId = 0; _requestId = 0;
searchDone(_requestId, it->second.result, it->second.requestedCount); searchDone(
_requestId,
i->second.result,
i->second.requestedCount);
return true; return true;
} }
return false; return false;
@ -1706,11 +1776,14 @@ bool ParticipantsBoxSearchController::loadMoreRows() {
switch (_role) { switch (_role) {
case Role::Admins: // Search for members, appoint as admin on found. case Role::Admins: // Search for members, appoint as admin on found.
case Role::Profile: case Role::Profile:
case Role::Members: return MTP_channelParticipantsSearch(MTP_string(_query)); case Role::Members:
case Role::Restricted: return MTP_channelParticipantsBanned(MTP_string(_query)); return MTP_channelParticipantsSearch(MTP_string(_query));
case Role::Kicked: return MTP_channelParticipantsKicked(MTP_string(_query)); case Role::Restricted:
return MTP_channelParticipantsBanned(MTP_string(_query));
case Role::Kicked:
return MTP_channelParticipantsKicked(MTP_string(_query));
} }
Unexpected("Role in ParticipantsBoxSearchController::loadMoreRows()"); Unexpected("Role in ParticipantsBoxSearchController.");
}(); }();
// For search we request a lot of rows from the first query. // For search we request a lot of rows from the first query.
@ -1725,9 +1798,11 @@ bool ParticipantsBoxSearchController::loadMoreRows() {
MTP_int(_offset), MTP_int(_offset),
MTP_int(perPage), MTP_int(perPage),
MTP_int(participantsHash) MTP_int(participantsHash)
)).done([this, perPage](const MTPchannels_ChannelParticipants &result, mtpRequestId requestId) { )).done([=](
const MTPchannels_ChannelParticipants &result,
mtpRequestId requestId) {
searchDone(requestId, result, perPage); searchDone(requestId, result, perPage);
}).fail([this](const RPCError &error, mtpRequestId requestId) { }).fail([=](const RPCError &error, mtpRequestId requestId) {
if (_requestId == requestId) { if (_requestId == requestId) {
_requestId = 0; _requestId = 0;
_allLoaded = true; _allLoaded = true;
@ -1748,7 +1823,7 @@ void ParticipantsBoxSearchController::searchDone(
int requestedCount) { int requestedCount) {
auto query = _query; auto query = _query;
if (requestId) { if (requestId) {
_channel->session().api().parseChannelParticipants(_channel, result, [&](auto&&...) { const auto addToCache = [&](auto&&...) {
auto it = _queries.find(requestId); auto it = _queries.find(requestId);
if (it != _queries.cend()) { if (it != _queries.cend()) {
query = it->second.text; query = it->second.text;
@ -1759,20 +1834,23 @@ void ParticipantsBoxSearchController::searchDone(
} }
_queries.erase(it); _queries.erase(it);
} }
}); };
_channel->session().api().parseChannelParticipants(
_channel,
result,
addToCache);
} }
if (_requestId != requestId) { if (_requestId != requestId) {
return; return;
} }
_requestId = 0; _requestId = 0;
result.match([&](const MTPDchannels_channelParticipants &data) { result.match([&](const MTPDchannels_channelParticipants &data) {
auto &list = data.vparticipants.v; const auto &list = data.vparticipants.v;
if (list.size() < requestedCount) { if (list.size() < requestedCount) {
// We want cache to have full information about a query with small // We want cache to have full information about a query with
// results count (if we don't need the second request). So we don't // small results count (that we don't need the second request).
// wait for an empty results list unlike the non-search peer list. // So we don't wait for empty list unlike the non-search case.
_allLoaded = true; _allLoaded = true;
} }
const auto overrideRole = (_role == Role::Admins) const auto overrideRole = (_role == Role::Admins)

View File

@ -36,6 +36,11 @@ Fn<void(
Fn<void(const MTPChatBannedRights &newRights)> onDone, Fn<void(const MTPChatBannedRights &newRights)> onDone,
Fn<void()> onFail); Fn<void()> onFail);
void SubscribeToMigration(
not_null<PeerData*> peer,
rpl::lifetime &lifetime,
Fn<void(not_null<ChannelData*>)> migrate);
enum class ParticipantsRole { enum class ParticipantsRole {
Profile, Profile,
Members, Members,
@ -92,6 +97,8 @@ public:
[[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const; [[nodiscard]] UserData *adminPromotedBy(not_null<UserData*> user) const;
[[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const; [[nodiscard]] UserData *restrictedBy(not_null<UserData*> user) const;
void migrate(not_null<ChannelData*> channel);
private: private:
UserData *applyCreator(const MTPDchannelParticipantCreator &data); UserData *applyCreator(const MTPDchannelParticipantCreator &data);
UserData *applyAdmin(const MTPDchannelParticipantAdmin &data); UserData *applyAdmin(const MTPDchannelParticipantAdmin &data);
@ -216,6 +223,9 @@ private:
Type computeType(not_null<UserData*> user) const; Type computeType(not_null<UserData*> user) const;
void recomputeTypeFor(not_null<UserData*> user); void recomputeTypeFor(not_null<UserData*> user);
void subscribeToMigration();
void migrate(not_null<ChannelData*> channel);
not_null<Window::Navigation*> _navigation; not_null<Window::Navigation*> _navigation;
not_null<PeerData*> _peer; not_null<PeerData*> _peer;
Role _role = Role::Admins; Role _role = Role::Admins;

View File

@ -1109,8 +1109,17 @@ void Controller::saveUsername() {
if (!_savingData.username || *_savingData.username == username) { if (!_savingData.username || *_savingData.username == username) {
return continueSave(); return continueSave();
} else if (!channel) { } else if (!channel) {
// #TODO groups autoconv const auto saveForChannel = [=](not_null<ChannelData*> channel) {
return continueSave(); if (_peer->asChannel() == channel) {
saveUsername();
} else {
cancelSave();
}
};
_peer->session().api().migrateChat(
_peer->asChat(),
crl::guard(this, saveForChannel));
return;
} }
request(MTPchannels_UpdateUsername( request(MTPchannels_UpdateUsername(
@ -1122,7 +1131,7 @@ void Controller::saveUsername() {
*_savingData.username); *_savingData.username);
continueSave(); continueSave();
}).fail([=](const RPCError &error) { }).fail([=](const RPCError &error) {
const auto type = error.type(); const auto &type = error.type();
if (type == qstr("USERNAME_NOT_MODIFIED")) { if (type == qstr("USERNAME_NOT_MODIFIED")) {
channel->setName( channel->setName(
TextUtilities::SingleLine(channel->name), TextUtilities::SingleLine(channel->name),
@ -1156,7 +1165,7 @@ void Controller::saveTitle() {
continueSave(); continueSave();
}; };
const auto onFail = [=](const RPCError &error) { const auto onFail = [=](const RPCError &error) {
const auto type = error.type(); const auto &type = error.type();
if (type == qstr("CHAT_NOT_MODIFIED") if (type == qstr("CHAT_NOT_MODIFIED")
|| type == qstr("CHAT_TITLE_NOT_MODIFIED")) { || type == qstr("CHAT_TITLE_NOT_MODIFIED")) {
if (const auto channel = _peer->asChannel()) { if (const auto channel = _peer->asChannel()) {
@ -1209,7 +1218,7 @@ void Controller::saveDescription() {
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
successCallback(); successCallback();
}).fail([=](const RPCError &error) { }).fail([=](const RPCError &error) {
auto type = error.type(); const auto &type = error.type();
if (type == qstr("CHAT_ABOUT_NOT_MODIFIED")) { if (type == qstr("CHAT_ABOUT_NOT_MODIFIED")) {
successCallback(); successCallback();
return; return;
@ -1226,8 +1235,17 @@ void Controller::saveHistoryVisibility() {
|| *_savingData.hiddenPreHistory == hidden) { || *_savingData.hiddenPreHistory == hidden) {
return continueSave(); return continueSave();
} else if (!channel) { } else if (!channel) {
// #TODO groups autoconv const auto saveForChannel = [=](not_null<ChannelData*> channel) {
return continueSave(); if (_peer->asChannel() == channel) {
saveHistoryVisibility();
} else {
cancelSave();
}
};
_peer->session().api().migrateChat(
_peer->asChat(),
crl::guard(this, saveForChannel));
return;
} }
request(MTPchannels_TogglePreHistoryHidden( request(MTPchannels_TogglePreHistoryHidden(
channel->inputChannel, channel->inputChannel,

View File

@ -489,6 +489,34 @@ const ChannelData *PeerData::asMegagroup() const {
: nullptr; : nullptr;
} }
ChatData *PeerData::asChatNotMigrated() {
if (const auto chat = asChat()) {
return chat->migrateTo() ? nullptr : chat;
}
return nullptr;
}
const ChatData *PeerData::asChatNotMigrated() const {
if (const auto chat = asChat()) {
return chat->migrateTo() ? nullptr : chat;
}
return nullptr;
}
ChannelData *PeerData::asChannelOrMigrated() {
if (const auto channel = asChannel()) {
return channel;
}
return migrateTo();
}
const ChannelData *PeerData::asChannelOrMigrated() const {
if (const auto channel = asChannel()) {
return channel;
}
return migrateTo();
}
ChatData *PeerData::migrateFrom() const { ChatData *PeerData::migrateFrom() const {
if (const auto megagroup = asMegagroup()) { if (const auto megagroup = asMegagroup()) {
return megagroup->amIn() return megagroup->amIn()

View File

@ -110,6 +110,10 @@ public:
[[nodiscard]] const ChannelData *asChannel() const; [[nodiscard]] const ChannelData *asChannel() const;
[[nodiscard]] ChannelData *asMegagroup(); [[nodiscard]] ChannelData *asMegagroup();
[[nodiscard]] const ChannelData *asMegagroup() const; [[nodiscard]] const ChannelData *asMegagroup() const;
[[nodiscard]] ChatData *asChatNotMigrated();
[[nodiscard]] const ChatData *asChatNotMigrated() const;
[[nodiscard]] ChannelData *asChannelOrMigrated();
[[nodiscard]] const ChannelData *asChannelOrMigrated() const;
[[nodiscard]] ChatData *migrateFrom() const; [[nodiscard]] ChatData *migrateFrom() const;
[[nodiscard]] ChannelData *migrateTo() const; [[nodiscard]] ChannelData *migrateTo() const;

View File

@ -28,25 +28,20 @@ void ShowSearchFromBox(
peer, peer,
callback = std::move(callback) callback = std::move(callback)
]() -> std::unique_ptr<PeerListController> { ]() -> std::unique_ptr<PeerListController> {
if (peer) { if (peer && (peer->isChat() || peer->isMegagroup())) {
if (auto chat = peer->asChat()) { return std::make_unique<Dialogs::SearchFromController>(
return std::make_unique<Dialogs::ChatSearchFromController>( navigation,
navigation, peer,
chat, std::move(callback));
std::move(callback));
} else if (auto group = peer->asMegagroup()) {
return std::make_unique<Dialogs::ChannelSearchFromController>(
navigation,
group,
std::move(callback));
}
} }
return nullptr; return nullptr;
}; };
if (auto controller = createController()) { if (auto controller = createController()) {
auto subscription = std::make_shared<rpl::lifetime>(); auto subscription = std::make_shared<rpl::lifetime>();
auto box = Ui::show(Box<PeerListBox>(std::move(controller), [subscription](not_null<PeerListBox*> box) { auto box = Ui::show(Box<PeerListBox>(std::move(controller), [subscription](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box, subscription] { box->closeBox(); }); box->addButton(langFactory(lng_cancel), [box, subscription] {
box->closeBox();
});
}), LayerOption::KeepOther); }), LayerOption::KeepOther);
box->boxClosing() | rpl::start_with_next( box->boxClosing() | rpl::start_with_next(
std::move(closedCallback), std::move(closedCallback),
@ -54,106 +49,32 @@ void ShowSearchFromBox(
} }
} }
ChatSearchFromController::ChatSearchFromController( SearchFromController::SearchFromController(
not_null<Window::Navigation*> navigation, not_null<Window::Navigation*> navigation,
not_null<ChatData*> chat, not_null<PeerData*> peer,
Fn<void(not_null<UserData*>)> callback)
: PeerListController()
, _chat(chat)
, _callback(std::move(callback)) {
}
void ChatSearchFromController::prepare() {
setSearchNoResultsText(lang(lng_blocked_list_not_found));
delegate()->peerListSetSearchMode(PeerListSearchMode::Enabled);
delegate()->peerListSetTitle(langFactory(lng_search_messages_from));
rebuildRows();
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::MembersChanged, [this](const Notify::PeerUpdate &update) {
if (update.peer == _chat) {
rebuildRows();
}
}));
}
void ChatSearchFromController::rowClicked(not_null<PeerListRow*> row) {
Expects(row->peer()->isUser());
const auto onstack = _callback;
onstack(row->peer()->asUser());
}
void ChatSearchFromController::rebuildRows() {
auto ms = getms();
auto wasEmpty = !delegate()->peerListFullRowsCount();
auto now = unixtime();
const auto byOnline = [&](not_null<UserData*> user) {
return Data::SortByOnlineValue(user, now);
};
auto ordered = QMultiMap<TimeId, not_null<UserData*>>();
if (_chat->noParticipantInfo()) {
Auth().api().requestFullPeer(_chat);
} else if (!_chat->participants.empty()) {
for (const auto user : _chat->participants) {
ordered.insertMulti(byOnline(user), user);
}
}
for_const (auto user, _chat->lastAuthors) {
if (user->isInaccessible()) continue;
appendRow(user);
if (!ordered.isEmpty()) {
ordered.remove(byOnline(user), user);
}
}
if (!ordered.isEmpty()) {
for (auto i = ordered.cend(), b = ordered.cbegin(); i != b;) {
appendRow(*(--i));
}
}
checkForEmptyRows();
delegate()->peerListRefreshRows();
}
void ChatSearchFromController::checkForEmptyRows() {
if (delegate()->peerListFullRowsCount()) {
setDescriptionText(QString());
} else {
setDescriptionText(lang(lng_contacts_loading));
}
}
void ChatSearchFromController::appendRow(not_null<UserData*> user) {
if (!delegate()->peerListFindRow(user->id)) {
delegate()->peerListAppendRow(std::make_unique<PeerListRow>(user));
}
}
ChannelSearchFromController::ChannelSearchFromController(
not_null<Window::Navigation*> navigation,
not_null<ChannelData*> channel,
Fn<void(not_null<UserData*>)> callback) Fn<void(not_null<UserData*>)> callback)
: ParticipantsBoxController( : ParticipantsBoxController(
navigation, navigation,
channel, peer,
ParticipantsBoxController::Role::Members) ParticipantsBoxController::Role::Members)
, _callback(std::move(callback)) { , _callback(std::move(callback)) {
} }
void ChannelSearchFromController::prepare() { void SearchFromController::prepare() {
ParticipantsBoxController::prepare(); ParticipantsBoxController::prepare();
delegate()->peerListSetTitle(langFactory(lng_search_messages_from)); delegate()->peerListSetTitle(langFactory(lng_search_messages_from));
} }
void ChannelSearchFromController::rowClicked(not_null<PeerListRow*> row) { void SearchFromController::rowClicked(not_null<PeerListRow*> row) {
Expects(row->peer()->isUser()); Expects(row->peer()->isUser());
const auto onstack = _callback; if (const auto onstack = base::duplicate(_callback)) {
onstack(row->peer()->asUser()); onstack(row->peer()->asUser());
}
} }
std::unique_ptr<PeerListRow> ChannelSearchFromController::createRow(not_null<UserData*> user) const { std::unique_ptr<PeerListRow> SearchFromController::createRow(
not_null<UserData*> user) const {
return std::make_unique<PeerListRow>(user); return std::make_unique<PeerListRow>(user);
} }

View File

@ -18,31 +18,11 @@ void ShowSearchFromBox(
Fn<void(not_null<UserData*>)> callback, Fn<void(not_null<UserData*>)> callback,
Fn<void()> closedCallback); Fn<void()> closedCallback);
class ChatSearchFromController : public PeerListController, protected base::Subscriber { class SearchFromController : public ParticipantsBoxController {
public: public:
ChatSearchFromController( SearchFromController(
not_null<Window::Navigation*> navigation, not_null<Window::Navigation*> navigation,
not_null<ChatData*> chat, not_null<PeerData*> peer,
Fn<void(not_null<UserData*>)> callback);
void prepare() override;
void rowClicked(not_null<PeerListRow*> row) override;
private:
void rebuildRows();
void checkForEmptyRows();
void appendRow(not_null<UserData*> user);
not_null<ChatData*> _chat;
Fn<void(not_null<UserData*>)> _callback;
};
class ChannelSearchFromController : public ParticipantsBoxController {
public:
ChannelSearchFromController(
not_null<Window::Navigation*> navigation,
not_null<ChannelData*> channel,
Fn<void(not_null<UserData*>)> callback); Fn<void(not_null<UserData*>)> callback);
void prepare() override; void prepare() override;

View File

@ -9,7 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtp_instance.h" #include "mtproto/mtp_instance.h"
#include "mtproto/rpc_sender.h" #include "mtproto/rpc_sender.h"
#include "mtproto/session.h"
namespace MTP { namespace MTP {
@ -180,11 +179,13 @@ void ConcurrentSender::senderRequestDone(
bytes::const_span result) { bytes::const_span result) {
if (auto handlers = _requests.take(requestId)) { if (auto handlers = _requests.take(requestId)) {
try { try {
std::move(handlers->done)(requestId, result); handlers->done(requestId, result);
} catch (Exception &e) { } catch (Exception &e) {
std::move(handlers->fail)(requestId, internal::rpcClientError( handlers->fail(
"RESPONSE_PARSE_FAILED", requestId,
QString("exception text: ") + e.what())); RPCError::Local(
"RESPONSE_PARSE_FAILED",
QString("exception text: ") + e.what()));
} }
} }
} }
@ -193,7 +194,7 @@ void ConcurrentSender::senderRequestFail(
mtpRequestId requestId, mtpRequestId requestId,
RPCError &&error) { RPCError &&error) {
if (auto handlers = _requests.take(requestId)) { if (auto handlers = _requests.take(requestId)) {
std::move(handlers->fail)(requestId, std::move(error)); handlers->fail(requestId, std::move(error));
} }
} }

View File

@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "mtproto/dedicated_file_loader.h" #include "mtproto/dedicated_file_loader.h"
#include "mtproto/session.h"
#include "auth_session.h" #include "auth_session.h"
#include "messenger.h" #include "messenger.h"
@ -110,7 +109,9 @@ void WeakInstance::die() {
if (instance) { if (instance) {
instance->cancel(requestId); instance->cancel(requestId);
} }
fail(MTP::internal::rpcClientError("UNAVAILABLE")); fail(RPCError::Local(
"UNAVAILABLE",
"MTP instance is not available."));
} }
} }
@ -125,7 +126,9 @@ bool WeakInstance::removeRequest(mtpRequestId requestId) {
void WeakInstance::reportUnavailable( void WeakInstance::reportUnavailable(
Fn<void(const RPCError &error)> callback) { Fn<void(const RPCError &error)> callback) {
InvokeQueued(this, [=] { InvokeQueued(this, [=] {
callback(MTP::internal::rpcClientError("UNAVAILABLE")); callback(RPCError::Local(
"UNAVAILABLE",
"MTP instance is not available."));
}); });
} }

View File

@ -956,7 +956,14 @@ void Instance::Private::clearCallbacks(mtpRequestId requestId, int32 errorCode)
"Request: %1, error code: %2" "Request: %1, error code: %2"
).arg(requestId ).arg(requestId
).arg(errorCode)); ).arg(errorCode));
rpcErrorOccured(requestId, h, internal::rpcClientError("CLEAR_CALLBACK", QString("did not handle request %1, error code %2").arg(requestId).arg(errorCode))); rpcErrorOccured(
requestId,
h,
RPCError::Local(
"CLEAR_CALLBACK",
QString("did not handle request %1, error code %2"
).arg(requestId
).arg(errorCode)));
} }
} }
@ -1017,14 +1024,13 @@ void Instance::Private::execCallback(
} }
} }
if (h.onDone || h.onFail) { if (h.onDone || h.onFail) {
const auto handleError = [&](const MTPRpcError &error) { const auto handleError = [&](const RPCError &error) {
const auto wrapped = RPCError(error);
DEBUG_LOG(("RPC Info: " DEBUG_LOG(("RPC Info: "
"error received, code %1, type %2, description: %3" "error received, code %1, type %2, description: %3"
).arg(wrapped.code() ).arg(error.code()
).arg(wrapped.type() ).arg(error.type()
).arg(wrapped.description())); ).arg(error.description()));
if (rpcErrorOccured(requestId, h, wrapped)) { if (rpcErrorOccured(requestId, h, error)) {
unregisterRequest(requestId); unregisterRequest(requestId);
} else { } else {
QMutexLocker locker(&_parserMapLock); QMutexLocker locker(&_parserMapLock);
@ -1045,7 +1051,7 @@ void Instance::Private::execCallback(
unregisterRequest(requestId); unregisterRequest(requestId);
} }
} catch (Exception &e) { } catch (Exception &e) {
handleError(internal::rpcClientError( handleError(RPCError::Local(
"RESPONSE_PARSE_FAILED", "RESPONSE_PARSE_FAILED",
QString("exception text: ") + e.what())); QString("exception text: ") + e.what()));
} }
@ -1103,7 +1109,10 @@ void Instance::Private::importDone(const MTPauth_Authorization &result, mtpReque
// //
// Don't log out on export/import problems, perhaps this is a server side error. // Don't log out on export/import problems, perhaps this is a server side error.
// //
//RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find import request in requestsByDC, request %1").arg(requestId))); //const auto error = RPCError::Local(
// "AUTH_IMPORT_FAIL",
// QString("did not find import request in requestsByDC, "
// "request %1").arg(requestId));
//if (_globalHandler.onFail && hasAuthorization()) { //if (_globalHandler.onFail && hasAuthorization()) {
// (*_globalHandler.onFail)(requestId, error); // auth failed in main dc // (*_globalHandler.onFail)(requestId, error); // auth failed in main dc
//} //}
@ -1156,7 +1165,10 @@ void Instance::Private::exportDone(const MTPauth_ExportedAuthorization &result,
// //
// Don't log out on export/import problems, perhaps this is a server side error. // Don't log out on export/import problems, perhaps this is a server side error.
// //
//RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find target dcWithShift, request %1").arg(requestId))); //const auto error = RPCError::Local(
// "AUTH_IMPORT_FAIL",
// QString("did not find target dcWithShift, request %1"
// ).arg(requestId));
//if (_globalHandler.onFail && hasAuthorization()) { //if (_globalHandler.onFail && hasAuthorization()) {
// (*_globalHandler.onFail)(requestId, error); // auth failed in main dc // (*_globalHandler.onFail)(requestId, error); // auth failed in main dc
//} //}

View File

@ -32,10 +32,21 @@ public:
TimeoutError TimeoutError
}; };
private: static RPCError Local(const QString &type, const QString &description) {
return MTP_rpc_error(
MTP_int(0),
MTP_bytes(
("CLIENT_"
+ type
+ (description.length()
? (": " + description)
: QString())).toUtf8()));
}
private:
int32 _code; int32 _code;
QString _type, _description; QString _type, _description;
}; };
namespace MTP { namespace MTP {

View File

@ -156,8 +156,11 @@ void Session::createDcData() {
connect(dc.get(), SIGNAL(connectionWasInited()), this, SLOT(connectionWasInitedForDC()), Qt::QueuedConnection); connect(dc.get(), SIGNAL(connectionWasInited()), this, SLOT(connectionWasInitedForDC()), Qt::QueuedConnection);
} }
bool Session::rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { // return true if need to clean request data bool Session::rpcErrorOccured(
return _instance->rpcErrorOccured(requestId, onFail, err); mtpRequestId requestId,
const RPCFailHandlerPtr &onFail,
const RPCError &error) { // return true if need to clean request data
return _instance->rpcErrorOccured(requestId, onFail, error);
} }
void Session::restart() { void Session::restart() {
@ -618,9 +621,5 @@ Session::~Session() {
Assert(_connection == nullptr); Assert(_connection == nullptr);
} }
MTPrpcError rpcClientError(const QString &type, const QString &description) {
return MTP_rpc_error(MTP_int(0), MTP_string(("CLIENT_" + type + (description.length() ? (": " + description) : "")).toUtf8().constData()));
}
} // namespace internal } // namespace internal
} // namespace MTP } // namespace MTP

View File

@ -402,7 +402,5 @@ inline not_null<QReadWriteLock*> SessionData::keyMutex() const {
return _owner->keyMutex(); return _owner->keyMutex();
} }
MTPrpcError rpcClientError(const QString &type, const QString &description = QString());
} // namespace internal } // namespace internal
} // namespace MTP } // namespace MTP

View File

@ -107,11 +107,7 @@ History *FindWastedPin() {
} }
void AddChatMembers(not_null<ChatData*> chat) { void AddChatMembers(not_null<ChatData*> chat) {
if (chat->count >= Global::ChatSizeMax() && chat->amCreator()) { AddParticipantsBoxController::Start(chat);
// #TODO convert and add inside AddParticipantsBoxController?
} else {
AddParticipantsBoxController::Start(chat);
}
} }
bool PinnedLimitReached(Dialogs::Key key) { bool PinnedLimitReached(Dialogs::Key key) {