mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
Added api support to catch next channel owner before leave.
This commit is contained in:
@@ -325,6 +325,8 @@ PRIVATE
|
||||
boxes/report_messages_box.h
|
||||
boxes/ringtones_box.cpp
|
||||
boxes/ringtones_box.h
|
||||
boxes/select_future_owner_box.cpp
|
||||
boxes/select_future_owner_box.h
|
||||
boxes/self_destruction_box.cpp
|
||||
boxes/self_destruction_box.h
|
||||
boxes/send_credits_box.cpp
|
||||
|
||||
@@ -1643,6 +1643,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_invite_upgrade_via_channel_about" = "You can send an invite link to the channel in a private message instead.";
|
||||
"lng_invite_status_disabled" = "available only to Premium users";
|
||||
|
||||
"lng_leave_next_owner_box_title" = "Leave Channel?";
|
||||
"lng_leave_next_owner_box_title_group" = "Leave Group?";
|
||||
"lng_leave_next_owner_box_about" = "If you leave, **{user}** will become the new owner of **{chat}** in **1 week**.";
|
||||
"lng_leave_next_owner_box_about_admin" = "If you leave, **{user}** will become the new admin of **{chat}** in **1 week**.";
|
||||
"lng_select_next_owner_box" = "Appoint Another Owner";
|
||||
"lng_select_next_owner_box_admin" = "Appoint Another Admin";
|
||||
"lng_select_next_owner_box_title" = "Appoint New Owner";
|
||||
"lng_select_next_owner_box_title_admin" = "Appoint New Admin";
|
||||
"lng_select_next_owner_box_confirm" = "Appoint and Leave Group";
|
||||
"lng_select_next_owner_box_sub_admins" = "Channel admins";
|
||||
"lng_select_next_owner_box_sub_admins_group" = "Group admins";
|
||||
"lng_select_next_owner_box_sub_members" = "Channel members";
|
||||
"lng_select_next_owner_box_sub_members_group" = "Group members";
|
||||
"lng_select_next_owner_box_status_joined" = "joined {date}";
|
||||
"lng_select_next_owner_box_status_promoted" = "promoted {date}";
|
||||
|
||||
"lng_via_link_group_one" = "**{user}** restricts adding them to groups.\nYou can send them an invite link as message instead.";
|
||||
"lng_via_link_group_many#one" = "**{count} user** restricts adding them to groups.\nYou can send them an invite link as message instead.";
|
||||
"lng_via_link_group_many#other" = "**{count} users** restrict adding them to groups.\nYou can send them an invite link as message instead.";
|
||||
|
||||
@@ -1182,3 +1182,9 @@ moderateCommonGroupsCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) {
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
futureOwnerBox: Box(defaultBox) {
|
||||
buttonPadding: margins(0px, 0px, 0px, 14px);
|
||||
buttonHeight: 0px;
|
||||
}
|
||||
futureOwnerBoxSelect: collectibleBox;
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "boxes/select_future_owner_box.h"
|
||||
|
||||
#include "api/api_chat_participants.h"
|
||||
#include "api/api_cloud_password.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "boxes/filters/edit_filter_chats_list.h"
|
||||
#include "boxes/passcode_box.h"
|
||||
#include "boxes/peer_list_controllers.h"
|
||||
#include "boxes/peer_lists_box.h"
|
||||
#include "boxes/peers/replace_boost_box.h" // CreateUserpicsTransfer.
|
||||
#include "core/application.h"
|
||||
#include "core/core_cloud_password.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "boxes/peers/channel_ownership_transfer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/vertical_list.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ParticipantType {
|
||||
Admins,
|
||||
Members
|
||||
};
|
||||
|
||||
class ParticipantsController : public PeerListController {
|
||||
public:
|
||||
ParticipantsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<ChannelData*> channel,
|
||||
ParticipantType type);
|
||||
|
||||
Main::Session &session() const override;
|
||||
void prepare() override;
|
||||
void rowClicked(not_null<PeerListRow*> row) override;
|
||||
void loadMoreRows() override;
|
||||
void itemDeselectedHook(not_null<PeerData*> peer) override;
|
||||
|
||||
void setOnRowClicked(Fn<void()> callback);
|
||||
rpl::producer<> itemDeselected() const;
|
||||
|
||||
private:
|
||||
const not_null<Window::SessionController*> _window;
|
||||
const not_null<ChannelData*> _channel;
|
||||
const ParticipantType _type;
|
||||
MTP::Sender _api;
|
||||
Fn<void()> _onRowClicked;
|
||||
rpl::event_stream<> _itemDeselected;
|
||||
|
||||
mtpRequestId _loadRequestId = 0;
|
||||
int _offset = 0;
|
||||
bool _allLoaded = false;
|
||||
};
|
||||
|
||||
ParticipantsController::ParticipantsController(
|
||||
not_null<Window::SessionController*> window,
|
||||
not_null<ChannelData*> channel,
|
||||
ParticipantType type)
|
||||
: _window(window)
|
||||
, _channel(channel)
|
||||
, _type(type)
|
||||
, _api(&channel->session().mtp()) {
|
||||
}
|
||||
|
||||
Main::Session &ParticipantsController::session() const {
|
||||
return _channel->session();
|
||||
}
|
||||
|
||||
void ParticipantsController::setOnRowClicked(Fn<void()> callback) {
|
||||
_onRowClicked = callback;
|
||||
}
|
||||
|
||||
void ParticipantsController::prepare() {
|
||||
loadMoreRows();
|
||||
}
|
||||
|
||||
void ParticipantsController::rowClicked(not_null<PeerListRow*> row) {
|
||||
delegate()->peerListSetRowChecked(
|
||||
row,
|
||||
!delegate()->peerListIsRowChecked(row));
|
||||
for (auto i = 0; i < delegate()->peerListFullRowsCount(); ++i) {
|
||||
auto r = delegate()->peerListRowAt(i);
|
||||
if (r != row) {
|
||||
delegate()->peerListSetRowChecked(r, false);
|
||||
}
|
||||
}
|
||||
if (_onRowClicked) {
|
||||
_onRowClicked();
|
||||
}
|
||||
}
|
||||
|
||||
void ParticipantsController::itemDeselectedHook(not_null<PeerData*> peer) {
|
||||
_itemDeselected.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> ParticipantsController::itemDeselected() const {
|
||||
return _itemDeselected.events();
|
||||
}
|
||||
|
||||
void ParticipantsController::loadMoreRows() {
|
||||
if (_loadRequestId || _allLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto perPage = (_offset > 0) ? 200 : 50;
|
||||
const auto participantsHash = uint64(0);
|
||||
const auto filter = (_type == ParticipantType::Admins)
|
||||
? MTP_channelParticipantsAdmins()
|
||||
: MTP_channelParticipantsRecent();
|
||||
|
||||
_loadRequestId = _api.request(MTPchannels_GetParticipants(
|
||||
_channel->inputChannel(),
|
||||
filter,
|
||||
MTP_int(_offset),
|
||||
MTP_int(perPage),
|
||||
MTP_long(participantsHash)
|
||||
)).done([=](const MTPchannels_ChannelParticipants &result) {
|
||||
_loadRequestId = 0;
|
||||
auto added = false;
|
||||
|
||||
result.match([&](const MTPDchannels_channelParticipants &data) {
|
||||
const auto &[availableCount, list] = Api::ChatParticipants::Parse(
|
||||
_channel,
|
||||
data);
|
||||
for (const auto &participant : list) {
|
||||
if (const auto user = _channel->owner().userLoaded(
|
||||
participant.userId())) {
|
||||
if (delegate()->peerListFindRow(user->id.value)) {
|
||||
continue;
|
||||
}
|
||||
if (user->isBot()) {
|
||||
continue;
|
||||
}
|
||||
using Type = Api::ChatParticipant::Type;
|
||||
if ((participant.type() == Type::Creator)
|
||||
|| (_type == ParticipantType::Members
|
||||
&& (participant.type() == Type::Admin))) {
|
||||
continue;
|
||||
}
|
||||
auto row = std::make_unique<PeerListRow>(user);
|
||||
const auto promotedSince = participant.promotedSince();
|
||||
row->setCustomStatus(
|
||||
(promotedSince
|
||||
? tr::lng_select_next_owner_box_status_promoted
|
||||
: tr::lng_select_next_owner_box_status_joined)(
|
||||
tr::now,
|
||||
lt_date,
|
||||
Ui::FormatDateTime(
|
||||
base::unixtime::parse(promotedSince
|
||||
? promotedSince
|
||||
: participant.memberSince()))));
|
||||
delegate()->peerListAppendRow(std::move(row));
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if (const auto size = list.size()) {
|
||||
_offset += size;
|
||||
} else {
|
||||
_allLoaded = true;
|
||||
}
|
||||
}, [](const MTPDchannels_channelParticipantsNotModified &) {
|
||||
});
|
||||
|
||||
if (!added && _offset > 0) {
|
||||
_allLoaded = true;
|
||||
}
|
||||
delegate()->peerListRefreshRows();
|
||||
}).fail([=] {
|
||||
_loadRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SelectFutureOwnerbox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user) {
|
||||
const auto content = box->verticalLayout();
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
content->add(
|
||||
CreateUserpicsTransfer(
|
||||
content,
|
||||
rpl::single(std::vector<not_null<PeerData*>>{
|
||||
user->session().user(),
|
||||
channel,
|
||||
}),
|
||||
user,
|
||||
UserpicsTransferType::ChannelFutureOwner),
|
||||
st::boxRowPadding);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
const auto title = content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
channel->isMegagroup()
|
||||
? tr::lng_leave_next_owner_box_title_group()
|
||||
: tr::lng_leave_next_owner_box_title(),
|
||||
box->getDelegate()->style().title),
|
||||
st::boxRowPadding);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
const auto adminsAreEqual = false;
|
||||
const auto about = content->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
content,
|
||||
(adminsAreEqual
|
||||
? tr::lng_leave_next_owner_box_about
|
||||
: tr::lng_leave_next_owner_box_about_admin)(
|
||||
lt_user,
|
||||
Info::Profile::NameValue(user) | rpl::map(tr::marked),
|
||||
lt_chat,
|
||||
Info::Profile::NameValue(channel) | rpl::map(tr::marked),
|
||||
tr::rich),
|
||||
st::boxLabel),
|
||||
st::boxRowPadding);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
Ui::AddSkip(content);
|
||||
|
||||
const auto select = content->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
content,
|
||||
!adminsAreEqual
|
||||
? tr::lng_select_next_owner_box()
|
||||
: tr::lng_select_next_owner_box_admin(),
|
||||
st::defaultLightButton),
|
||||
st::boxRowPadding,
|
||||
style::al_justify);
|
||||
Ui::AddSkip(content);
|
||||
const auto cancel = content->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
content,
|
||||
tr::lng_cancel(),
|
||||
st::defaultLightButton),
|
||||
st::boxRowPadding,
|
||||
style::al_justify);
|
||||
cancel->setClickedCallback([=] {
|
||||
box->closeBox();
|
||||
});
|
||||
Ui::AddSkip(content);
|
||||
const auto leave = content->add(
|
||||
object_ptr<Ui::RoundButton>(
|
||||
content,
|
||||
channel->isMegagroup()
|
||||
? tr::lng_profile_leave_group()
|
||||
: tr::lng_profile_leave_channel(),
|
||||
st::attentionBoxButton),
|
||||
st::boxRowPadding,
|
||||
style::al_justify);
|
||||
leave->setClickedCallback([=, revoke = false] {
|
||||
channel->session().api().deleteConversation(channel, revoke);
|
||||
box->closeBox();
|
||||
});
|
||||
select->setClickedCallback([=] {
|
||||
const auto window = Core::App().findWindow(box);
|
||||
const auto sessionController = window
|
||||
? window->sessionController()
|
||||
: nullptr;
|
||||
if (!sessionController) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto adminsOwned = std::make_unique<ParticipantsController>(
|
||||
sessionController,
|
||||
channel,
|
||||
ParticipantType::Admins);
|
||||
|
||||
auto membersOwned = std::make_unique<ParticipantsController>(
|
||||
sessionController,
|
||||
channel,
|
||||
ParticipantType::Members);
|
||||
|
||||
const auto admins = adminsOwned.get();
|
||||
const auto members = membersOwned.get();
|
||||
|
||||
auto initBox = [=](not_null<PeerListsBox*> selectBox) {
|
||||
const auto selectionChanges
|
||||
= selectBox->lifetime().make_state<rpl::event_stream<>>();
|
||||
const auto uncheckOtherList = [=](
|
||||
not_null<PeerListController*> otherController) {
|
||||
auto delegate = otherController->delegate();
|
||||
const auto full = delegate->peerListFullRowsCount();
|
||||
for (auto i = 0; i < full; ++i) {
|
||||
delegate->peerListSetRowChecked(
|
||||
delegate->peerListRowAt(i),
|
||||
false);
|
||||
}
|
||||
selectionChanges->fire({});
|
||||
};
|
||||
admins->setOnRowClicked([=] { uncheckOtherList(members); });
|
||||
members->setOnRowClicked([=] { uncheckOtherList(admins); });
|
||||
selectBox->setTitle(!adminsAreEqual
|
||||
? tr::lng_select_next_owner_box_title()
|
||||
: tr::lng_select_next_owner_box_title_admin());
|
||||
const auto searchEnabled = PeerListSearchMode::Enabled;
|
||||
admins->delegate()->peerListSetSearchMode(searchEnabled);
|
||||
members->delegate()->peerListSetSearchMode(searchEnabled);
|
||||
rpl::merge(
|
||||
admins->itemDeselected(),
|
||||
members->itemDeselected()
|
||||
) | rpl::on_next([=] {
|
||||
selectionChanges->fire({});
|
||||
}, selectBox->lifetime());
|
||||
selectBox->addSeparatorBefore(
|
||||
0,
|
||||
CreatePeerListSectionSubtitle(
|
||||
selectBox,
|
||||
!channel->isMegagroup()
|
||||
? tr::lng_select_next_owner_box_sub_admins()
|
||||
: tr::lng_select_next_owner_box_sub_admins_group()));
|
||||
selectBox->addSeparatorBefore(
|
||||
1,
|
||||
CreatePeerListSectionSubtitle(
|
||||
selectBox,
|
||||
!channel->isMegagroup()
|
||||
? tr::lng_select_next_owner_box_sub_members()
|
||||
: tr::lng_select_next_owner_box_sub_members_group()));
|
||||
{
|
||||
const auto &st = st::futureOwnerBoxSelect;
|
||||
selectBox->setStyle(st);
|
||||
auto button = object_ptr<Ui::RoundButton>(
|
||||
selectBox,
|
||||
rpl::conditional(
|
||||
selectionChanges->events(
|
||||
) | rpl::map([=] {
|
||||
return !selectBox->collectSelectedRows().empty();
|
||||
}),
|
||||
tr::lng_select_next_owner_box_confirm(),
|
||||
tr::lng_close()),
|
||||
st::defaultActiveButton);
|
||||
button->setTextTransform(
|
||||
Ui::RoundButton::TextTransform::NoTransform);
|
||||
const auto raw = button.data();
|
||||
rpl::combine(
|
||||
selectionChanges->events() | rpl::map_to(0),
|
||||
selectBox->widthValue()
|
||||
) | rpl::on_next([=](int, int width) {
|
||||
raw->resizeToWidth(width
|
||||
- st.buttonPadding.left()
|
||||
- st.buttonPadding.right());
|
||||
}, selectBox->lifetime());
|
||||
button->setFullRadius(true);
|
||||
button->setClickedCallback([=] {
|
||||
const auto selected = selectBox->collectSelectedRows();
|
||||
if (selected.empty()) {
|
||||
return selectBox->closeBox();
|
||||
}
|
||||
if (const auto user = selected.front()->asUser()) {
|
||||
auto &lifetime = selectBox->lifetime();
|
||||
lifetime.make_state<ChannelOwnershipTransfer>(
|
||||
channel,
|
||||
user,
|
||||
selectBox->uiShow(),
|
||||
[=](std::shared_ptr<Ui::Show> show) {
|
||||
const auto revoke = false;
|
||||
channel->session().api().deleteConversation(
|
||||
channel,
|
||||
revoke);
|
||||
show->hideLayer();
|
||||
})->start();
|
||||
}
|
||||
});
|
||||
selectBox->addButton(std::move(button));
|
||||
selectionChanges->fire({});
|
||||
}
|
||||
};
|
||||
|
||||
auto controllers = std::vector<std::unique_ptr<PeerListController>>();
|
||||
controllers.reserve(2);
|
||||
controllers.push_back(std::move(adminsOwned));
|
||||
controllers.push_back(std::move(membersOwned));
|
||||
box->uiShow()->showBox(
|
||||
Box<PeerListsBox>(std::move(controllers), initBox));
|
||||
});
|
||||
for (const auto &b : { select, cancel, leave }) {
|
||||
b->setFullRadius(true);
|
||||
b->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
}
|
||||
box->setStyle(st::futureOwnerBox);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class ChannelData;
|
||||
class PeerData;
|
||||
class UserData;
|
||||
|
||||
namespace Ui {
|
||||
class GenericBox;
|
||||
class Show;
|
||||
} // namespace Ui
|
||||
|
||||
void SelectFutureOwnerbox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<ChannelData*> channel,
|
||||
not_null<UserData*> user);
|
||||
@@ -967,11 +967,8 @@ void TopBar::setupActions(not_null<Window::SessionController*> controller) {
|
||||
this,
|
||||
tr::lng_profile_action_short_leave(tr::now),
|
||||
st::infoProfileTopBarActionLeave);
|
||||
leaveButton->setClickedCallback([=] {
|
||||
if (!controller->showFrozenError()) {
|
||||
controller->show(Box(DeleteChatBox, peer));
|
||||
}
|
||||
});
|
||||
leaveButton->setClickedCallback(
|
||||
Window::DeleteAndLeaveHandler(controller, peer));
|
||||
_actions->add(leaveButton);
|
||||
buttons.push_back(leaveButton);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "boxes/max_invite_box.h"
|
||||
#include "boxes/moderate_messages_box.h"
|
||||
#include "boxes/select_future_owner_box.h"
|
||||
#include "boxes/choose_filter_box.h"
|
||||
#include "boxes/create_poll_box.h"
|
||||
#include "boxes/edit_todo_list_box.h"
|
||||
@@ -3655,6 +3656,26 @@ Fn<void()> ClearHistoryHandler(
|
||||
Fn<void()> DeleteAndLeaveHandler(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<PeerData*> peer) {
|
||||
if (const auto channel = peer->asChannel();
|
||||
channel && channel->amCreator()) {
|
||||
const auto requestId = std::make_shared<mtpRequestId>(0);
|
||||
return [=] {
|
||||
if (controller->showFrozenError() || (*requestId > 0)) {
|
||||
return;
|
||||
}
|
||||
*requestId = peer->session().api().request(
|
||||
MTPchannels_GetFutureCreatorAfterLeave(
|
||||
channel->inputChannel()
|
||||
)).done([=](const MTPUser &result) {
|
||||
*requestId = 0;
|
||||
const auto user = peer->owner().processUser(result);
|
||||
controller->show(Box(SelectFutureOwnerbox, channel, user));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
*requestId = 0;
|
||||
controller->show(Box(DeleteChatBox, peer));
|
||||
}).send();
|
||||
};
|
||||
}
|
||||
return [=] {
|
||||
if (!controller->showFrozenError()) {
|
||||
controller->show(Box(DeleteChatBox, peer));
|
||||
|
||||
Reference in New Issue
Block a user