Handle migration to supergroups in boxes.

This commit is contained in:
John Preston 2019-01-14 10:34:51 +04:00
parent 3c44bdb6b7
commit 9728ddeaf9
29 changed files with 426 additions and 231 deletions

View file

@ -128,6 +128,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gif_error" = "An error has occurred while reading GIF animation :(";
"lng_edit_error" = "You cannot edit this message";
"lng_join_channel_error" = "Sorry, you have joined too many channels and supergroups. Please leave some before joining this one.";
"lng_migrate_error" = "This action will convert the group to a supergroup. Unfortunately, you are a member of too many supergroups and channels. Please leave some of the channels or groups you don't need before proceeding.";
"lng_error_phone_flood" = "Sorry, you have deleted and re-created your account too many times recently. Please wait for a few days before signing up again.";
"lng_error_start_minimized_passcoded" = "You have set a local passcode, so Telegram Desktop can't be launched minimised; it will ask you to enter your passcode before it can start working.";
"lng_error_pinned_max#one" = "Sorry, you can pin no more than {count} chat to the top.";
@ -797,6 +798,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_history_visibility_shown_about" = "New members will see messages that were sent before they joined.";
"lng_manage_history_visibility_hidden" = "Hidden";
"lng_manage_history_visibility_hidden_about" = "New members won't see earlier messages.";
"lng_manage_history_visibility_hidden_legacy" = "New members won't see more than 100 previous messages.";
"lng_report_title" = "Report channel";
"lng_report_group_title" = "Report group";
@ -957,8 +959,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_convert_feature4" = "— Creator can set a public link for the group";
"lng_profile_convert_warning" = "{bold_start}Note:{bold_end} This action can not be undone";
"lng_profile_convert_confirm" = "Convert";
"lng_profile_add_more_after_upgrade#one" = "You will be able to add up to {count} member after you upgrade your group to a supergroup.";
"lng_profile_add_more_after_upgrade#other" = "You will be able to add up to {count} members after you upgrade your group to a supergroup.";
"lng_profile_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_channel_not_accessible" = "Sorry, this channel is not accessible.";
"lng_group_not_accessible" = "Sorry, this group is not accessible.";

View file

@ -886,6 +886,7 @@ void ApiWrap::requestFullPeer(not_null<PeerData*> peer) {
const auto requestId = [&] {
const auto failHandler = [=](const RPCError &error) {
_fullPeerRequests.remove(peer);
migrateFail(peer, error);
};
if (const auto user = peer->asUser()) {
if (_session->supportMode()) {
@ -911,6 +912,7 @@ void ApiWrap::requestFullPeer(not_null<PeerData*> peer) {
const MTPmessages_ChatFull &result,
mtpRequestId requestId) {
gotChatFull(peer, result, requestId);
migrateDone(channel, channel);
}).fail(failHandler).send();
}
Unexpected("Peer type in requestFullPeer.");
@ -993,40 +995,18 @@ void ApiWrap::gotChatFull(
channel->setUserpicPhoto(f.vchat_photo);
if (f.has_migrated_from_chat_id()) {
channel->addFlags(MTPDchannel::Flag::f_megagroup);
auto cfrom = App::chat(peerFromChat(f.vmigrated_from_chat_id));
bool updatedTo = (cfrom->migrateToPtr != channel), updatedFrom = (channel->mgInfo->migrateFromPtr != cfrom);
if (updatedTo) {
cfrom->migrateToPtr = channel;
const auto chat = channel->owner().chat(
peerFromChat(f.vmigrated_from_chat_id));
Data::ApplyMigration(chat, channel);
}
if (updatedFrom) {
channel->mgInfo->migrateFromPtr = cfrom;
if (auto h = App::historyLoaded(cfrom->id)) {
if (auto hto = App::historyLoaded(channel->id)) {
if (!h->isEmpty()) {
h->unloadBlocks();
}
if (hto->inChatList(Dialogs::Mode::All) && h->inChatList(Dialogs::Mode::All)) {
App::main()->removeDialog(h);
}
}
}
Notify::migrateUpdated(channel);
}
if (updatedTo) {
Notify::migrateUpdated(cfrom);
}
}
auto &v = f.vbot_info.v;
for_const (auto &item, v) {
switch (item.type()) {
case mtpc_botInfo: {
auto &b = item.c_botInfo();
if (auto user = App::userLoaded(b.vuser_id.v)) {
for (const auto &item : f.vbot_info.v) {
auto &owner = channel->owner();
item.match([&](const MTPDbotInfo &info) {
if (const auto user = owner.userLoaded(info.vuser_id.v)) {
user->setBotInfo(item);
fullPeerUpdated().notify(user);
}
} break;
}
});
}
channel->setAbout(qs(f.vabout));
channel->setMembersCount(f.has_participants_count() ? f.vparticipants_count.v : 0);
@ -1179,56 +1159,81 @@ void ApiWrap::migrateChat(
if (i != end(_migrateCallbacks)) {
i->second.push_back(callback());
return;
} else if (const auto channel = chat->migrateTo()) {
}
_migrateCallbacks.emplace(chat).first->second.push_back(callback());
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);
crl::on_main([=] {
migrateDone(chat, channel);
});
} else if (chat->isDeactivated()) {
crl::on_main([fail = std::move(fail)]() mutable {
fail(RPCError::Local(
crl::on_main([=] {
migrateFail(
chat,
RPCError::Local(
"BAD_MIGRATION",
"Chat is already deactivated"));
});
return;
} else if (!chat->amCreator()) {
crl::on_main([fail = std::move(fail)]() mutable {
fail(RPCError::Local(
crl::on_main([=] {
migrateFail(
chat,
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 (const auto channel = chat->migrateTo()) {
if (auto handlers = _migrateCallbacks.take(chat)) {
for (auto &handler : *handlers) {
if (channel) {
handler.done(channel);
_migrateCallbacks.emplace(channel, std::move(*handlers));
}
requestFullPeer(channel);
} else {
handler.fail(RPCError::Local(
"MIGRATION_FAIL",
"No channel"));
}
}
migrateFail(
chat,
RPCError::Local("MIGRATION_FAIL", "No channel"));
}
}).fail([=](const RPCError &error) {
if (auto handlers = _migrateCallbacks.take(chat)) {
migrateFail(chat, error);
}).send();
}
void ApiWrap::migrateDone(
not_null<PeerData*> peer,
not_null<ChannelData*> channel) {
Notify::peerUpdatedSendDelayed();
if (auto handlers = _migrateCallbacks.take(peer)) {
for (auto &handler : *handlers) {
if (handler.done) {
handler.done(channel);
}
}
}
}
void ApiWrap::migrateFail(not_null<PeerData*> peer, const RPCError &error) {
const auto &type = error.type();
if (type == qstr("CHANNELS_TOO_MUCH")) {
Ui::show(Box<InformBox>(lang(lng_migrate_error)));
}
if (auto handlers = _migrateCallbacks.take(peer)) {
for (auto &handler : *handlers) {
if (handler.fail) {
handler.fail(error);
}
}
}).send();
}
}
void ApiWrap::markMediaRead(
@ -5059,6 +5064,7 @@ void ApiWrap::requestSupportContact(FnMut<void(const MTPUser &)> callback) {
}
void ApiWrap::uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image) {
peer = peer->migrateToOrMe();
const auto ready = PreparePeerPhoto(peer->id, std::move(image));
const auto fakeId = FullMsgId(peerToChannel(peer->id), clientMsgId());

View file

@ -587,6 +587,11 @@ private:
void setSelfDestructDays(int days);
void migrateDone(
not_null<PeerData*> peer,
not_null<ChannelData*> channel);
void migrateFail(not_null<PeerData*> peer, const RPCError &error);
not_null<AuthSession*> _session;
MessageDataRequests _messageDataRequests;
@ -737,7 +742,7 @@ private:
FnMut<void(const RPCError&)> fail;
};
base::flat_map<
not_null<ChatData*>,
not_null<PeerData*>,
std::vector<MigrateCallbacks>> _migrateCallbacks;
std::vector<FnMut<void(const MTPUser &)>> _supportContactCallbacks;

View file

@ -348,3 +348,45 @@ enum CreatingGroupType {
CreatingGroupGroup,
CreatingGroupChannel,
};
class BoxPointer {
public:
BoxPointer() = default;
BoxPointer(const BoxPointer &other) = default;
BoxPointer(BoxPointer &&other) : _value(base::take(other._value)) {
}
BoxPointer &operator=(const BoxPointer &other) {
if (_value != other._value) {
destroy();
_value = other._value;
}
return *this;
}
BoxPointer &operator=(BoxPointer &&other) {
if (_value != other._value) {
destroy();
_value = base::take(other._value);
}
return *this;
}
BoxPointer &operator=(BoxContent *other) {
if (_value != other) {
destroy();
_value = other;
}
return *this;
}
~BoxPointer() {
destroy();
}
private:
void destroy() {
if (const auto value = base::take(_value)) {
value->closeBox();
}
}
QPointer<BoxContent> _value;
};

View file

@ -86,11 +86,8 @@ void AddParticipantsBoxController::rowClicked(not_null<PeerListRow*> row) {
}
} else if (count >= Global::ChatSizeMax()
&& count < Global::MegagroupSizeMax()) {
// #TODO groups new error about "after creating"
Ui::show(
Box<InformBox>(lng_profile_add_more_after_upgrade(
lt_count,
Global::MegagroupSizeMax())),
Box<InformBox>(lang(lng_profile_add_more_after_create)),
LayerOption::KeepOther);
}
}
@ -454,11 +451,7 @@ void AddSpecialBoxController::showAdmin(
if (!checkInfoLoaded(user, [=] { showAdmin(user); })) {
return;
}
if (sure && _editBox) {
// Close the confirmation box.
_editBox->closeBox();
}
_editBox = nullptr;
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
@ -555,9 +548,7 @@ void AddSpecialBoxController::showAdmin(
editAdminDone(user, newRights);
});
const auto fail = crl::guard(this, [=] {
if (_editBox) {
_editBox->closeBox();
}
_editBox = nullptr;
});
box->setSaveCallback(SaveAdminCallback(_peer, user, done, fail));
}
@ -567,7 +558,7 @@ void AddSpecialBoxController::showAdmin(
void AddSpecialBoxController::editAdminDone(
not_null<UserData*> user,
const MTPChatAdminRights &rights) {
if (_editBox) _editBox->closeBox();
_editBox = nullptr;
const auto date = unixtime(); // Incorrect, but ignored.
if (rights.c_chatAdminRights().vflags.v == 0) {
@ -597,11 +588,7 @@ void AddSpecialBoxController::showRestricted(
if (!checkInfoLoaded(user, [=] { showRestricted(user); })) {
return;
}
if (sure && _editBox) {
// Close the confirmation box.
_editBox->closeBox();
}
_editBox = nullptr;
const auto chat = _peer->asChat();
const auto channel = _peer->asChannel();
@ -650,9 +637,7 @@ void AddSpecialBoxController::showRestricted(
editRestrictedDone(user, newRights);
});
const auto fail = crl::guard(this, [=] {
if (_editBox) {
_editBox->closeBox();
}
_editBox = nullptr;
});
box->setSaveCallback(
SaveRestrictedCallback(_peer, user, done, fail));
@ -663,7 +648,7 @@ void AddSpecialBoxController::showRestricted(
void AddSpecialBoxController::editRestrictedDone(
not_null<UserData*> user,
const MTPChatBannedRights &rights) {
if (_editBox) _editBox->closeBox();
_editBox = nullptr;
const auto date = unixtime(); // Incorrect, but ignored.
if (rights.c_chatBannedRights().vflags.v == 0) {
@ -731,6 +716,8 @@ void AddSpecialBoxController::kickUser(
LayerOption::KeepOther);
return;
}
_editBox = nullptr;
const auto restrictedRights = _additional.restrictedRights(user);
const auto currentRights = restrictedRights
? *restrictedRights

View file

@ -115,7 +115,7 @@ private:
bool _allLoaded = false;
ParticipantsAdditionalData _additional;
std::unique_ptr<ParticipantsOnlineSorter> _onlineSorter;
QPointer<BoxContent> _editBox;
BoxPointer _editBox;
AdminDoneCallback _adminDoneCallback;
BannedDoneCallback _bannedDoneCallback;

View file

@ -212,17 +212,20 @@ void EditAdminBox::prepare() {
const auto chat = peer()->asChat();
const auto channel = peer()->asChannel();
const auto prepareRights = hadRights ? _oldRights : Defaults(peer());
const auto disabledByDefaults = DisabledByDefaultRestrictions(peer());
const auto filterByMyRights = canSave()
&& !hadRights
&& channel
&& !channel->amCreator();
const auto prepareFlags = prepareRights.c_chatAdminRights().vflags.v
& (filterByMyRights ? channel->adminRights() : ~Flag(0));
const auto prepareFlags = disabledByDefaults
| (prepareRights.c_chatAdminRights().vflags.v
& (filterByMyRights ? channel->adminRights() : ~Flag(0)));
const auto disabledFlags = canSave()
? ((!channel || channel->amCreator())
? (disabledByDefaults
| ((!channel || channel->amCreator())
? Flags(0)
: ~channel->adminRights())
: ~channel->adminRights()))
: ~Flags(0);
const auto anyoneCanAddMembers = chat

View file

@ -802,6 +802,7 @@ void ParticipantsBoxController::addNewItem() {
const auto initBox = [](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [=] { box->closeBox(); });
};
_addBox = Ui::show(
Box<PeerListBox>(
std::make_unique<AddSpecialBoxController>(
@ -1352,9 +1353,7 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
editAdminDone(user, newRights);
});
const auto fail = crl::guard(this, [=] {
if (_editBox) {
_editBox->closeBox();
}
_editBox = nullptr;
});
box->setSaveCallback(SaveAdminCallback(_peer, user, done, fail));
}
@ -1364,12 +1363,9 @@ void ParticipantsBoxController::showAdmin(not_null<UserData*> user) {
void ParticipantsBoxController::editAdminDone(
not_null<UserData*> user,
const MTPChatAdminRights &rights) {
if (_editBox) {
_editBox->closeBox();
}
if (_addBox) {
_addBox->closeBox();
}
_addBox = nullptr;
_editBox = nullptr;
const auto date = unixtime(); // Incorrect, but ignored.
if (rights.c_chatAdminRights().vflags.v == 0) {
_additional.applyParticipant(MTP_channelParticipant(
@ -1420,9 +1416,7 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
editRestrictedDone(user, newRights);
});
const auto fail = crl::guard(this, [=] {
if (_editBox) {
_editBox->closeBox();
}
_editBox = nullptr;
});
box->setSaveCallback(
SaveRestrictedCallback(_peer, user, done, fail));
@ -1433,12 +1427,9 @@ void ParticipantsBoxController::showRestricted(not_null<UserData*> user) {
void ParticipantsBoxController::editRestrictedDone(
not_null<UserData*> user,
const MTPChatBannedRights &rights) {
if (_editBox) {
_editBox->closeBox();
}
if (_addBox) {
_addBox->closeBox();
}
_addBox = nullptr;
_editBox = nullptr;
const auto date = unixtime(); // Incorrect, but ignored.
if (rights.c_chatBannedRights().vflags.v == 0) {
_additional.applyParticipant(MTP_channelParticipant(
@ -1495,9 +1486,8 @@ void ParticipantsBoxController::kickMember(not_null<UserData*> user) {
}
void ParticipantsBoxController::kickMemberSure(not_null<UserData*> user) {
if (_editBox) {
_editBox->closeBox();
}
_editBox = nullptr;
const auto restrictedRights = _additional.restrictedRights(user);
const auto currentRights = restrictedRights
? *restrictedRights
@ -1529,9 +1519,8 @@ void ParticipantsBoxController::removeAdmin(not_null<UserData*> user) {
}
void ParticipantsBoxController::removeAdminSure(not_null<UserData*> user) {
if (_editBox) {
_editBox->closeBox();
}
_editBox = nullptr;
if (const auto chat = _peer->asChat()) {
SaveChatAdmin(chat, user, false, crl::guard(this, [=] {
editAdminDone(user, MTP_chatAdminRights(MTP_flags(0)));

View file

@ -234,8 +234,8 @@ private:
bool _allLoaded = false;
ParticipantsAdditionalData _additional;
std::unique_ptr<ParticipantsOnlineSorter> _onlineSorter;
QPointer<BoxContent> _editBox;
QPointer<PeerListBox> _addBox;
BoxPointer _editBox;
BoxPointer _addBox;
};

View file

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/add_contact_box.h"
#include "boxes/stickers_box.h"
#include "boxes/peer_list_controllers.h"
#include "boxes/peers/edit_participants_box.h"
#include "data/data_peer.h"
#include "data/data_chat.h"
#include "data/data_channel.h"
@ -114,6 +115,7 @@ private:
object_ptr<Ui::RpWidget> createDeleteButton();
QString inviteLinkText() const;
void observeInviteLink();
void submitTitle();
void submitDescription();
@ -157,6 +159,9 @@ private:
void continueSave();
void cancelSave();
void subscribeToMigration();
void migrate(not_null<ChannelData*> channel);
not_null<BoxContent*> _box;
not_null<PeerData*> _peer;
bool _isGroup = false;
@ -171,6 +176,8 @@ private:
std::deque<FnMut<void()>> _saveStagesQueue;
Saving _savingData;
rpl::lifetime _lifetime;
};
Controller::Controller(
@ -179,7 +186,7 @@ Controller::Controller(
: _box(box)
, _peer(peer)
, _isGroup(_peer->isChat() || _peer->isMegagroup())
, _checkUsernameTimer([this] { checkUsernameAvailability(); }) {
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
_box->setTitle(computeTitle());
_box->addButton(langFactory(lng_settings_save), [this] {
save();
@ -187,6 +194,21 @@ Controller::Controller(
_box->addButton(langFactory(lng_cancel), [this] {
_box->closeBox();
});
subscribeToMigration();
_peer->updateFull();
}
void Controller::subscribeToMigration() {
SubscribeToMigration(
_peer,
_lifetime,
[=](not_null<ChannelData*> channel) { migrate(channel); });
}
void Controller::migrate(not_null<ChannelData*> channel) {
_peer = channel;
observeInviteLink();
_peer->updateFull();
}
Fn<QString()> Controller::computeTitle() const {
@ -222,10 +244,10 @@ void Controller::setFocus() {
object_ptr<Ui::RpWidget> Controller::createPhotoAndTitleEdit() {
Expects(_wrap != nullptr);
auto canEdit = [&] {
if (auto channel = _peer->asChannel()) {
const auto canEdit = [&] {
if (const auto channel = _peer->asChannel()) {
return channel->canEditInformation();
} else if (auto chat = _peer->asChat()) {
} else if (const auto chat = _peer->asChat()) {
return chat->canEditInformation();
}
return false;
@ -516,7 +538,7 @@ void Controller::checkUsernameAvailability() {
if (_checkUsernameRequestId) {
request(_checkUsernameRequestId).cancel();
}
const auto channel = _peer->asChannel();
const auto channel = _peer->migrateToOrMe()->asChannel();
const auto username = channel ? channel->username : QString();
_checkUsernameRequestId = request(MTPchannels_CheckUsername(
channel ? channel->inputChannel : MTP_inputChannelEmpty(),
@ -534,7 +556,7 @@ void Controller::checkUsernameAvailability() {
}
}).fail([=](const RPCError &error) {
_checkUsernameRequestId = 0;
auto type = error.type();
const auto &type = error.type();
_usernameState = UsernameState::Normal;
if (type == qstr("CHANNEL_PUBLIC_GROUP_NA")) {
_usernameState = UsernameState::NotAvailable;
@ -643,10 +665,10 @@ void Controller::revokeInviteLink() {
void Controller::exportInviteLink(const QString &confirmation) {
auto boxPointer = std::make_shared<QPointer<ConfirmBox>>();
auto callback = crl::guard(this, [=] {
if (auto strong = *boxPointer) {
if (const auto strong = *boxPointer) {
strong->closeBox();
}
Auth().api().exportInviteLink(_peer);
Auth().api().exportInviteLink(_peer->migrateToOrMe());
});
auto box = Box<ConfirmBox>(
confirmation,
@ -656,12 +678,11 @@ void Controller::exportInviteLink(const QString &confirmation) {
bool Controller::canEditInviteLink() const {
if (const auto channel = _peer->asChannel()) {
if (channel->canEditUsername()) {
return true;
}
return (!channel->isPublic() && channel->canAddMembers());
return channel->amCreator()
|| (channel->adminRights() & ChatAdminRight::f_invite_users);
} else if (const auto chat = _peer->asChat()) {
return chat->amCreator() || !chat->inviteLink().isEmpty();
return chat->amCreator()
|| (chat->adminRights() & ChatAdminRight::f_invite_users);
}
return false;
}
@ -680,6 +701,18 @@ QString Controller::inviteLinkText() const {
return QString();
}
void Controller::observeInviteLink() {
if (!_controls.editInviteLinkWrap) {
return;
}
Notify::PeerUpdateValue(
_peer,
Notify::PeerUpdate::Flag::InviteLinkChanged
) | rpl::start_with_next([=] {
refreshEditInviteLink();
}, _controls.editInviteLinkWrap->lifetime());
}
object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
Expects(_wrap != nullptr);
@ -721,14 +754,9 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
container,
lang(lng_group_invite_create_new),
st::editPeerInviteLinkButton)
)->addClickHandler([this] { revokeInviteLink(); });
)->addClickHandler([=] { revokeInviteLink(); });
Notify::PeerUpdateValue(
_peer,
Notify::PeerUpdate::Flag::InviteLinkChanged
) | rpl::start_with_next([this] {
refreshEditInviteLink();
}, _controls.editInviteLinkWrap->lifetime());
observeInviteLink();
return std::move(result);
}
@ -788,12 +816,7 @@ object_ptr<Ui::RpWidget> Controller::createInviteLinkCreate() {
});
_controls.createInviteLinkWrap = result.data();
Notify::PeerUpdateValue(
_peer,
Notify::PeerUpdate::Flag::InviteLinkChanged
) | rpl::start_with_next([this] {
refreshCreateInviteLink();
}, _controls.createInviteLinkWrap->lifetime());
observeInviteLink();
return std::move(result);
}
@ -865,7 +888,9 @@ object_ptr<Ui::RpWidget> Controller::createHistoryVisibilityEdit() {
addButton(
HistoryVisibility::Hidden,
lng_manage_history_visibility_hidden,
lng_manage_history_visibility_hidden_about);
(_peer->isChat()
? lng_manage_history_visibility_hidden_legacy
: lng_manage_history_visibility_hidden_about));
refreshHistoryVisibility();
@ -1258,7 +1283,7 @@ void Controller::saveHistoryVisibility() {
Auth().api().applyUpdates(result);
continueSave();
}).fail([this](const RPCError &error) {
}).fail([=](const RPCError &error) {
if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
continueSave();
} else {
@ -1340,7 +1365,7 @@ void Controller::deleteChannel() {
EditPeerInfoBox::EditPeerInfoBox(
QWidget*,
not_null<PeerData*> peer)
: _peer(peer) {
: _peer(peer->migrateToOrMe()) {
}
void EditPeerInfoBox::prepare() {

View file

@ -215,10 +215,40 @@ ChatRestrictions DisabledByAdminRights(not_null<PeerData*> peer) {
} // namespace
ChatAdminRights DisabledByDefaultRestrictions(not_null<PeerData*> peer) {
using Flag = ChatAdminRight;
using Restriction = ChatRestriction;
const auto restrictions = [&] {
if (const auto chat = peer->asChat()) {
return chat->defaultRestrictions();
} else if (const auto channel = peer->asChannel()) {
return channel->defaultRestrictions();
}
Unexpected("User in DisabledByDefaultRestrictions.");
}();
return Flag(0)
| ((restrictions & Restriction::f_pin_messages)
? Flag(0)
: Flag::f_pin_messages)
//
// We allow to edit 'invite_users' admin right no matter what
// is chosen in default permissions for 'invite_users', because
// if everyone can 'invite_users' it handles invite link for admins.
//
//| ((restrictions & Restriction::f_invite_users)
// ? Flag(0)
// : Flag::f_invite_users)
//
| ((restrictions & Restriction::f_change_info)
? Flag(0)
: Flag::f_change_info);
}
EditPeerPermissionsBox::EditPeerPermissionsBox(
QWidget*,
not_null<PeerData*> peer)
: _peer(peer) {
: _peer(peer->migrateToOrMe()) {
}
auto EditPeerPermissionsBox::saveEvents() const
@ -282,12 +312,18 @@ void EditPeerPermissionsBox::prepare() {
void EditPeerPermissionsBox::addBannedButtons(
not_null<Ui::VerticalLayout*> container) {
if (const auto chat = _peer->asChat()) {
if (!chat->amCreator()) {
return;
}
}
const auto channel = _peer->asChannel();
container->add(
object_ptr<BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
const auto navigation = App::wnd()->controller();
const auto channel = _peer->asChannel();
ManagePeerBox::CreateButton(
container,
Lang::Viewer(lng_manage_peer_exceptions),

View file

@ -55,3 +55,5 @@ EditFlagsControl<MTPDchatAdminRights::Flags> CreateEditAdminRights(
MTPDchatAdminRights::Flags disabled,
bool isGroup,
bool anyoneCanAddMembers);
ChatAdminRights DisabledByDefaultRestrictions(not_null<PeerData*> peer);

View file

@ -107,8 +107,8 @@ void ShowEditPermissions(not_null<PeerData*> peer) {
box->closeBox();
}
});
Auth().api().saveDefaultRestrictions(
peer,
peer->session().api().saveDefaultRestrictions(
peer->migrateToOrMe(),
MTP_chatBannedRights(MTP_flags(restrictions), MTP_int(0)),
callback);
}, box->lifetime());

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer_values.h"
#include "data/data_channel_admins.h"
#include "data/data_user.h"
#include "data/data_chat.h"
#include "data/data_session.h"
#include "data/data_feed.h"
#include "observer_peer.h"
@ -22,13 +23,21 @@ using UpdateFlag = Notify::PeerUpdate::Flag;
} // namespace
ChatData *MegagroupInfo::getMigrateFromChat() const {
return _migratedFrom;
}
void MegagroupInfo::setMigrateFromChat(ChatData *chat) {
_migratedFrom = chat;
}
ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
: PeerData(owner, id)
, inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) {
Data::PeerFlagValue(
this,
MTPDchannel::Flag::f_megagroup
) | rpl::start_with_next([this](bool megagroup) {
) | rpl::start_with_next([=](bool megagroup) {
if (megagroup) {
if (!mgInfo) {
mgInfo = std::make_unique<MegagroupInfo>();
@ -37,6 +46,17 @@ ChannelData::ChannelData(not_null<Data::Session*> owner, PeerId id)
mgInfo = nullptr;
}
}, _lifetime);
Data::PeerFlagsValue(
this,
MTPDchannel::Flag::f_left | MTPDchannel_ClientFlag::f_forbidden
) | rpl::distinct_until_changed(
) | rpl::start_with_next([=] {
if (const auto chat = getMigrateFromChat()) {
Notify::peerUpdatedDelayed(chat, UpdateFlag::MigrationChanged);
Notify::peerUpdatedDelayed(this, UpdateFlag::MigrationChanged);
}
}, _lifetime);
}
void ChannelData::setPhoto(const MTPChatPhoto &photo) {
@ -365,7 +385,8 @@ bool ChannelData::canEditInformation() const {
}
bool ChannelData::canEditPermissions() const {
return isMegagroup() && (hasAdminRights() || amCreator());
return isMegagroup()
&& ((adminRights() & AdminRight::f_ban_users) || amCreator());
}
bool ChannelData::canEditSignatures() const {
@ -495,8 +516,36 @@ auto ChannelData::applyUpdateVersion(int version) -> UpdateStatus {
return UpdateStatus::Good;
}
ChatData *ChannelData::getMigrateFromChat() const {
if (const auto info = mgInfo.get()) {
return info->getMigrateFromChat();
}
return nullptr;
}
void ChannelData::setMigrateFromChat(ChatData *chat) {
Expects(mgInfo != nullptr);
const auto info = mgInfo.get();
if (chat != info->getMigrateFromChat()) {
info->setMigrateFromChat(chat);
if (amIn()) {
Notify::peerUpdatedDelayed(this, UpdateFlag::MigrationChanged);
}
}
}
namespace Data {
void ApplyMigration(
not_null<ChatData*> chat,
not_null<ChannelData*> channel) {
Expects(channel->isMegagroup());
chat->setMigrateToChannel(channel);
channel->setMigrateFromChat(chat);
}
void ApplyChannelUpdate(
not_null<ChannelData*> channel,
const MTPDupdateChatDefaultBannedRights &update) {

View file

@ -10,7 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h"
#include "data/data_pts_waiter.h"
struct MegagroupInfo {
class MegagroupInfo {
public:
struct Admin {
explicit Admin(MTPChatAdminRights rights)
: rights(rights) {
@ -30,6 +31,9 @@ struct MegagroupInfo {
MTPChatBannedRights rights;
};
ChatData *getMigrateFromChat() const;
void setMigrateFromChat(ChatData *chat);
std::deque<not_null<UserData*>> lastParticipants;
base::flat_map<not_null<UserData*>, Admin> lastAdmins;
base::flat_map<not_null<UserData*>, Restricted> lastRestricted;
@ -51,7 +55,8 @@ struct MegagroupInfo {
mutable int lastParticipantsStatus = LastParticipantsUpToDate;
int lastParticipantsCount = 0;
ChatData *migrateFromPtr = nullptr;
private:
ChatData *_migratedFrom = nullptr;
};
@ -60,6 +65,7 @@ public:
static constexpr auto kEssentialFlags = 0
| MTPDchannel::Flag::f_creator
| MTPDchannel::Flag::f_left
| MTPDchannel_ClientFlag::f_forbidden
| MTPDchannel::Flag::f_broadcast
| MTPDchannel::Flag::f_verified
| MTPDchannel::Flag::f_megagroup
@ -339,6 +345,9 @@ public:
}
UpdateStatus applyUpdateVersion(int version);
ChatData *getMigrateFromChat() const;
void setMigrateFromChat(ChatData *chat);
// Still public data members.
uint64 access = 0;
@ -384,6 +393,10 @@ private:
namespace Data {
void ApplyMigration(
not_null<ChatData*> chat,
not_null<ChannelData*> channel);
void ApplyChannelUpdate(
not_null<ChannelData*> channel,
const MTPDupdateChatDefaultBannedRights &update);

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_chat.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_session.h"
#include "history/history.h"
#include "auth_session.h"
@ -64,7 +65,7 @@ bool ChatData::canEditInformation() const {
bool ChatData::canEditPermissions() const {
return !actionsUnavailable()
&& (amCreator() || hasAdminRights());
&& (amCreator() || (adminRights() & AdminRight::f_ban_users));
}
bool ChatData::canEditUsername() const {
@ -177,6 +178,19 @@ auto ChatData::applyUpdateVersion(int version) -> UpdateStatus {
return UpdateStatus::Good;
}
ChannelData *ChatData::getMigrateToChannel() const {
return _migratedTo;
}
void ChatData::setMigrateToChannel(ChannelData *channel) {
if (_migratedTo != channel) {
_migratedTo = channel;
if (channel->amIn()) {
Notify::peerUpdatedDelayed(this, UpdateFlag::MigrationChanged);
}
}
}
namespace Data {
void ApplyChatUpdate(

View file

@ -156,11 +156,12 @@ public:
}
UpdateStatus applyUpdateVersion(int version);
ChannelData *getMigrateToChannel() const;
void setMigrateToChannel(ChannelData *channel);
// Still public data members.
MTPint inputChat;
ChannelData *migrateToPtr = nullptr;
int count = 0;
TimeId date = 0;
UserId creator = 0;
@ -184,6 +185,8 @@ private:
AdminRightFlags _adminRights;
int _version = 0;
ChannelData *_migratedTo = nullptr;
};
namespace Data {

View file

@ -520,7 +520,7 @@ const ChannelData *PeerData::asChannelOrMigrated() const {
ChatData *PeerData::migrateFrom() const {
if (const auto megagroup = asMegagroup()) {
return megagroup->amIn()
? megagroup->mgInfo->migrateFromPtr
? megagroup->getMigrateFromChat()
: nullptr;
}
return nullptr;
@ -528,13 +528,27 @@ ChatData *PeerData::migrateFrom() const {
ChannelData *PeerData::migrateTo() const {
if (const auto chat = asChat()) {
if (const auto migrateTo = chat->migrateToPtr) {
return migrateTo->amIn() ? migrateTo : nullptr;
if (const auto result = chat->getMigrateToChannel()) {
return result->amIn() ? result : nullptr;
}
}
return nullptr;
}
not_null<PeerData*> PeerData::migrateToOrMe() {
if (const auto channel = migrateTo()) {
return channel;
}
return this;
}
not_null<const PeerData*> PeerData::migrateToOrMe() const {
if (const auto channel = migrateTo()) {
return channel;
}
return this;
}
Data::Feed *PeerData::feed() const {
if (const auto channel = asChannel()) {
return channel->feed();

View file

@ -117,6 +117,8 @@ public:
[[nodiscard]] ChatData *migrateFrom() const;
[[nodiscard]] ChannelData *migrateTo() const;
[[nodiscard]] not_null<PeerData*> migrateToOrMe();
[[nodiscard]] not_null<const PeerData*> migrateToOrMe() const;
[[nodiscard]] Data::Feed *feed() const;
void updateFull();

View file

@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "auth_session.h"
#include "apiwrap.h"
#include "messenger.h"
#include "mainwidget.h" // for main()->removeDialog
#include "core/crash_reports.h" // for CrashReports::SetAnnotation
#include "ui/image/image.h"
#include "export/export_controller.h"
@ -455,35 +454,13 @@ not_null<PeerData*> Session::chat(const MTPChat &data) {
const auto channel = this->channel(input.vchannel_id.v);
channel->addFlags(MTPDchannel::Flag::f_megagroup);
if (!channel->access) {
channel->input = MTP_inputPeerChannel(input.vchannel_id, input.vaccess_hash);
channel->inputChannel = data.vmigrated_to;
channel->input = MTP_inputPeerChannel(
input.vchannel_id,
input.vaccess_hash);
channel->inputChannel = migratedTo;
channel->access = input.vaccess_hash.v;
}
const auto updatedTo = (chat->migrateToPtr != channel);
const auto updatedFrom = (channel->mgInfo->migrateFromPtr != chat);
if (updatedTo) {
chat->migrateToPtr = channel;
}
if (updatedFrom) {
channel->mgInfo->migrateFromPtr = chat;
if (const auto h = historyLoaded(chat->id)) {
if (const auto hto = historyLoaded(channel->id)) {
if (!h->isEmpty()) {
h->unloadBlocks();
}
if (hto->inChatList(Dialogs::Mode::All)
&& h->inChatList(Dialogs::Mode::All)) {
App::main()->removeDialog(h);
}
}
}
Notify::migrateUpdated(channel);
update.flags |= UpdateFlag::MigrationChanged;
}
if (updatedTo) {
Notify::migrateUpdated(chat);
update.flags |= UpdateFlag::MigrationChanged;
}
ApplyMigration(chat, channel);
}, [](const MTPDinputChannelEmpty &) {
});

View file

@ -139,8 +139,9 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
| UpdateFlag::NameChanged
| UpdateFlag::PhotoChanged
| UpdateFlag::UserIsContact
| UpdateFlag::UserOccupiedChanged;
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [this](const Notify::PeerUpdate &update) {
| UpdateFlag::UserOccupiedChanged
| UpdateFlag::MigrationChanged;
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(changes, [=](const Notify::PeerUpdate &update) {
if (update.flags & UpdateFlag::ChatPinnedChanged) {
stopReorderPinned();
}
@ -156,6 +157,11 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
userIsContactUpdated(user);
}
}
if (update.flags & UpdateFlag::MigrationChanged) {
if (const auto chat = update.peer->asChat()) {
handleChatMigration(chat);
}
}
}));
Auth().data().feedUpdated(
) | rpl::start_with_next([=](const Data::FeedUpdate &update) {
@ -175,6 +181,22 @@ DialogsInner::DialogsInner(QWidget *parent, not_null<Window::Controller*> contro
setupShortcuts();
}
void DialogsInner::handleChatMigration(not_null<ChatData*> chat) {
const auto channel = chat->migrateTo();
if (!channel) {
return;
}
if (const auto from = chat->owner().historyLoaded(chat)) {
if (const auto to = chat->owner().historyLoaded(channel)) {
if (to->inChatList(Dialogs::Mode::All)
&& from->inChatList(Dialogs::Mode::All)) {
removeDialog(from);
}
}
}
}
int DialogsInner::dialogsOffset() const {
return _dialogsImportant ? st::dialogsImportantBarHeight : 0;
}

View file

@ -289,6 +289,7 @@ private:
int countPinnedIndex(Dialogs::Row *ofRow);
void savePinnedOrder();
void step_pinnedShifting(TimeMs ms, bool timer);
void handleChatMigration(not_null<ChatData*> chat);
not_null<Window::Controller*> _controller;

View file

@ -310,7 +310,7 @@ void DialogsWidget::createDialog(Dialogs::Key key) {
if (const auto migrated = App::historyLoaded(
history->peer->migrateFrom())) {
if (migrated->inChatList(Dialogs::Mode::All)) {
removeDialog(migrated);
_inner->removeDialog(migrated);
}
}
}

View file

@ -321,10 +321,6 @@ bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot,
return false;
}
void migrateUpdated(PeerData *peer) {
if (MainWidget *m = App::main()) m->notify_migrateUpdated(peer);
}
void historyMuteUpdated(History *history) {
if (MainWidget *m = App::main()) m->notify_historyMuteUpdated(history);
}

View file

@ -149,8 +149,6 @@ void replyMarkupUpdated(const HistoryItem *item);
void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop);
bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot = nullptr, MsgId samePeerReplyTo = 0);
void migrateUpdated(PeerData *peer);
void historyMuteUpdated(History *history);
void unreadCounterUpdated();

View file

@ -430,11 +430,7 @@ HistoryWidget::HistoryWidget(
unreadCountUpdated();
}
if (update.flags & UpdateFlag::MigrationChanged) {
if (auto channel = _peer->migrateTo()) {
Ui::showPeerHistory(channel, ShowAtUnreadMsgId);
Auth().api().requestParticipantsCountDelayed(channel);
return;
}
handlePeerMigration();
}
if (update.flags & UpdateFlag::NotificationsEnabled) {
updateNotifyControls();
@ -1338,27 +1334,6 @@ void HistoryWidget::notify_userIsBotChanged(UserData *user) {
}
}
void HistoryWidget::notify_migrateUpdated(PeerData *peer) {
if (_peer) {
if (_peer == peer) {
if (peer->migrateTo()) {
showHistory(peer->migrateTo()->id, (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId, true);
} else if ((_migrated ? _migrated->peer.get() : nullptr) != peer->migrateFrom()) {
auto migrated = _history->migrateFrom();
if (_migrated || (migrated && migrated->unreadCount() > 0)) {
showHistory(peer->id, peer->migrateFrom() ? _showAtMsgId : ((_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId) ? ShowAtUnreadMsgId : _showAtMsgId), true);
} else {
_migrated = migrated;
_list->notifyMigrateUpdated();
updateHistoryGeometry();
}
}
} else if (_migrated && _migrated->peer == peer && peer->migrateTo() != _peer) {
showHistory(_peer->id, _showAtMsgId, true);
}
}
}
void HistoryWidget::setupShortcuts() {
Shortcuts::Requests(
) | rpl::filter([=] {
@ -1516,7 +1491,10 @@ void HistoryWidget::applyCloudDraft(History *history) {
}
}
void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool reload) {
void HistoryWidget::showHistory(
const PeerId &peerId,
MsgId showAtMsgId,
bool reload) {
MsgId wasMsgId = _showAtMsgId;
History *wasHistory = _history;
@ -1671,6 +1649,11 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re
_history = App::history(_peer);
_migrated = _history->migrateFrom();
if (_migrated
&& !_migrated->isEmpty()
&& (!_history->loadedAtTop() || !_migrated->loadedAtBottom())) {
_migrated->unloadBlocks();
}
_topBar->setActiveChat(_history);
updateTopBarSelection();
@ -5173,6 +5156,35 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
}
}
void HistoryWidget::handlePeerMigration() {
const auto current = _peer->migrateToOrMe();
const auto chat = current->migrateFrom();
if (!chat) {
return;
}
const auto channel = current->asChannel();
Assert(channel != nullptr);
if (_peer != channel) {
showHistory(
channel->id,
(_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId);
channel->session().api().requestParticipantsCountDelayed(channel);
} else {
_migrated = _history->migrateFrom();
_list->notifyMigrateUpdated();
updateHistoryGeometry();
}
const auto from = chat->owner().historyLoaded(chat);
const auto to = channel->owner().historyLoaded(channel);
if (from
&& to
&& !from->isEmpty()
&& (!from->loadedAtBottom() || !to->loadedAtTop())) {
from->unloadBlocks();
}
}
void HistoryWidget::replyToPreviousMessage() {
if (!_history || _editMsgId) {
return;

View file

@ -288,7 +288,6 @@ public:
void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop);
bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo);
void notify_userIsBotChanged(UserData *user);
void notify_migrateUpdated(PeerData *peer);
~HistoryWidget();
@ -503,6 +502,8 @@ private:
bool showNextChat();
bool showPreviousChat();
void handlePeerMigration();
MsgId _replyToId = 0;
Text _replyToName;
int _replyToNameVersion = 0;

View file

@ -621,10 +621,6 @@ void MainWidget::notify_userIsBotChanged(UserData *bot) {
_history->notify_userIsBotChanged(bot);
}
void MainWidget::notify_migrateUpdated(PeerData *peer) {
_history->notify_migrateUpdated(peer);
}
void MainWidget::notify_historyMuteUpdated(History *history) {
_dialogs->notify_historyMuteUpdated(history);
}
@ -874,7 +870,7 @@ void MainWidget::deleteConversation(
removeDialog(history);
if (const auto channel = peer->asMegagroup()) {
channel->addFlags(MTPDchannel::Flag::f_left);
if (const auto from = channel->mgInfo->migrateFromPtr) {
if (const auto from = channel->getMigrateFromChat()) {
if (const auto migrated = App::historyLoaded(from)) {
migrated->updateChatListExistence();
}
@ -2832,15 +2828,17 @@ void MainWidget::searchInChat(Dialogs::Key chat) {
void MainWidget::feedUpdateVector(
const MTPVector<MTPUpdate> &updates,
bool skipMessageIds) {
for_const (auto &update, updates.v) {
if (skipMessageIds && update.type() == mtpc_updateMessageID) continue;
for (const auto &update : updates.v) {
if (skipMessageIds && update.type() == mtpc_updateMessageID) {
continue;
}
feedUpdate(update);
}
Auth().data().sendHistoryChangeNotifications();
}
void MainWidget::feedMessageIds(const MTPVector<MTPUpdate> &updates) {
for_const (auto &update, updates.v) {
for (const auto &update : updates.v) {
if (update.type() == mtpc_updateMessageID) {
feedUpdate(update);
}

View file

@ -290,7 +290,6 @@ public:
void notify_inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKeyboardTop);
bool notify_switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo);
void notify_userIsBotChanged(UserData *bot);
void notify_migrateUpdated(PeerData *peer);
void notify_historyMuteUpdated(History *history);
~MainWidget();