1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "calls/group/calls_group_invite_controller.h"
9 
10 #include "calls/group/calls_group_call.h"
11 #include "calls/group/calls_group_menu.h"
12 #include "boxes/peer_lists_box.h"
13 #include "data/data_user.h"
14 #include "data/data_channel.h"
15 #include "data/data_session.h"
16 #include "data/data_group_call.h"
17 #include "main/main_session.h"
18 #include "ui/text/text_utilities.h"
19 #include "ui/layers/generic_box.h"
20 #include "ui/widgets/labels.h"
21 #include "apiwrap.h"
22 #include "lang/lang_keys.h"
23 #include "styles/style_calls.h"
24 
25 namespace Calls::Group {
26 namespace {
27 
CreateSectionSubtitle(QWidget * parent,rpl::producer<QString> text)28 [[nodiscard]] object_ptr<Ui::RpWidget> CreateSectionSubtitle(
29 		QWidget *parent,
30 		rpl::producer<QString> text) {
31 	auto result = object_ptr<Ui::FixedHeightWidget>(
32 		parent,
33 		st::searchedBarHeight);
34 
35 	const auto raw = result.data();
36 	raw->paintRequest(
37 	) | rpl::start_with_next([=](QRect clip) {
38 		auto p = QPainter(raw);
39 		p.fillRect(clip, st::groupCallMembersBgOver);
40 	}, raw->lifetime());
41 
42 	const auto label = Ui::CreateChild<Ui::FlatLabel>(
43 		raw,
44 		std::move(text),
45 		st::groupCallBoxLabel);
46 	raw->widthValue(
47 	) | rpl::start_with_next([=](int width) {
48 		const auto padding = st::groupCallInviteDividerPadding;
49 		const auto available = width - padding.left() - padding.right();
50 		label->resizeToNaturalWidth(available);
51 		label->moveToLeft(padding.left(), padding.top(), width);
52 	}, label->lifetime());
53 
54 	return result;
55 }
56 
57 } // namespace
58 
InviteController(not_null<PeerData * > peer,base::flat_set<not_null<UserData * >> alreadyIn)59 InviteController::InviteController(
60 	not_null<PeerData*> peer,
61 	base::flat_set<not_null<UserData*>> alreadyIn)
62 : ParticipantsBoxController(CreateTag{}, nullptr, peer, Role::Members)
63 , _peer(peer)
64 , _alreadyIn(std::move(alreadyIn)) {
65 	SubscribeToMigration(
66 		_peer,
67 		lifetime(),
__anon3ec7d8f40402(not_null<ChannelData*> channel) 68 		[=](not_null<ChannelData*> channel) { _peer = channel; });
69 }
70 
prepare()71 void InviteController::prepare() {
72 	delegate()->peerListSetHideEmpty(true);
73 	ParticipantsBoxController::prepare();
74 	delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
75 		nullptr,
76 		tr::lng_group_call_invite_members()));
77 	delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
78 		nullptr,
79 		tr::lng_group_call_invite_members()));
80 }
81 
rowClicked(not_null<PeerListRow * > row)82 void InviteController::rowClicked(not_null<PeerListRow*> row) {
83 	delegate()->peerListSetRowChecked(row, !row->checked());
84 }
85 
rowContextMenu(QWidget * parent,not_null<PeerListRow * > row)86 base::unique_qptr<Ui::PopupMenu> InviteController::rowContextMenu(
87 		QWidget *parent,
88 		not_null<PeerListRow*> row) {
89 	return nullptr;
90 }
91 
itemDeselectedHook(not_null<PeerData * > peer)92 void InviteController::itemDeselectedHook(not_null<PeerData*> peer) {
93 }
94 
hasRowFor(not_null<PeerData * > peer) const95 bool InviteController::hasRowFor(not_null<PeerData*> peer) const {
96 	return (delegate()->peerListFindRow(peer->id.value) != nullptr);
97 }
98 
isAlreadyIn(not_null<UserData * > user) const99 bool InviteController::isAlreadyIn(not_null<UserData*> user) const {
100 	return _alreadyIn.contains(user);
101 }
102 
createRow(not_null<PeerData * > participant) const103 std::unique_ptr<PeerListRow> InviteController::createRow(
104 		not_null<PeerData*> participant) const {
105 	const auto user = participant->asUser();
106 	if (!user
107 		|| user->isSelf()
108 		|| user->isBot()
109 		|| user->isInaccessible()) {
110 		return nullptr;
111 	}
112 	auto result = std::make_unique<PeerListRow>(user);
113 	_rowAdded.fire_copy(user);
114 	_inGroup.emplace(user);
115 	if (isAlreadyIn(user)) {
116 		result->setDisabledState(PeerListRow::State::DisabledChecked);
117 	}
118 	return result;
119 }
120 
peersWithRows() const121 auto InviteController::peersWithRows() const
122 -> not_null<const base::flat_set<not_null<UserData*>>*> {
123 	return &_inGroup;
124 }
125 
rowAdded() const126 rpl::producer<not_null<UserData*>> InviteController::rowAdded() const {
127 	return _rowAdded.events();
128 }
129 
InviteContactsController(not_null<PeerData * > peer,base::flat_set<not_null<UserData * >> alreadyIn,not_null<const base::flat_set<not_null<UserData * >> * > inGroup,rpl::producer<not_null<UserData * >> discoveredInGroup)130 InviteContactsController::InviteContactsController(
131 	not_null<PeerData*> peer,
132 	base::flat_set<not_null<UserData*>> alreadyIn,
133 	not_null<const base::flat_set<not_null<UserData*>>*> inGroup,
134 	rpl::producer<not_null<UserData*>> discoveredInGroup)
135 : AddParticipantsBoxController(peer, std::move(alreadyIn))
136 , _inGroup(inGroup)
137 , _discoveredInGroup(std::move(discoveredInGroup)) {
138 }
139 
prepareViewHook()140 void InviteContactsController::prepareViewHook() {
141 	AddParticipantsBoxController::prepareViewHook();
142 
143 	delegate()->peerListSetAboveWidget(CreateSectionSubtitle(
144 		nullptr,
145 		tr::lng_contacts_header()));
146 	delegate()->peerListSetAboveSearchWidget(CreateSectionSubtitle(
147 		nullptr,
148 		tr::lng_group_call_invite_search_results()));
149 
150 	std::move(
151 		_discoveredInGroup
152 	) | rpl::start_with_next([=](not_null<UserData*> user) {
153 		if (auto row = delegate()->peerListFindRow(user->id.value)) {
154 			delegate()->peerListRemoveRow(row);
155 		}
156 	}, _lifetime);
157 }
158 
createRow(not_null<UserData * > user)159 std::unique_ptr<PeerListRow> InviteContactsController::createRow(
160 		not_null<UserData*> user) {
161 	return _inGroup->contains(user)
162 		? nullptr
163 		: AddParticipantsBoxController::createRow(user);
164 }
165 
PrepareInviteBox(not_null<GroupCall * > call,Fn<void (TextWithEntities &&)> showToast)166 object_ptr<Ui::BoxContent> PrepareInviteBox(
167 		not_null<GroupCall*> call,
168 		Fn<void(TextWithEntities&&)> showToast) {
169 	const auto real = call->lookupReal();
170 	if (!real) {
171 		return nullptr;
172 	}
173 	const auto peer = call->peer();
174 	auto alreadyIn = peer->owner().invitedToCallUsers(real->id());
175 	for (const auto &participant : real->participants()) {
176 		if (const auto user = participant.peer->asUser()) {
177 			alreadyIn.emplace(user);
178 		}
179 	}
180 	alreadyIn.emplace(peer->session().user());
181 	auto controller = std::make_unique<InviteController>(peer, alreadyIn);
182 	controller->setStyleOverrides(
183 		&st::groupCallInviteMembersList,
184 		&st::groupCallMultiSelect);
185 
186 	auto contactsController = std::make_unique<InviteContactsController>(
187 		peer,
188 		std::move(alreadyIn),
189 		controller->peersWithRows(),
190 		controller->rowAdded());
191 	contactsController->setStyleOverrides(
192 		&st::groupCallInviteMembersList,
193 		&st::groupCallMultiSelect);
194 
195 	const auto weak = base::make_weak(call.get());
196 	const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
197 		const auto call = weak.get();
198 		if (!call) {
199 			return;
200 		}
201 		const auto result = call->inviteUsers(users);
202 		if (const auto user = std::get_if<not_null<UserData*>>(&result)) {
203 			showToast(tr::lng_group_call_invite_done_user(
204 				tr::now,
205 				lt_user,
206 				Ui::Text::Bold((*user)->firstName),
207 				Ui::Text::WithEntities));
208 		} else if (const auto count = std::get_if<int>(&result)) {
209 			if (*count > 0) {
210 				showToast(tr::lng_group_call_invite_done_many(
211 					tr::now,
212 					lt_count,
213 					*count,
214 					Ui::Text::RichLangValue));
215 			}
216 		} else {
217 			Unexpected("Result in GroupCall::inviteUsers.");
218 		}
219 	};
220 	const auto inviteWithAdd = [=](
221 			const std::vector<not_null<UserData*>> &users,
222 			const std::vector<not_null<UserData*>> &nonMembers,
223 			Fn<void()> finish) {
224 		peer->session().api().addChatParticipants(
225 			peer,
226 			nonMembers,
227 			[=](bool) { invite(users); finish(); });
228 	};
229 	const auto inviteWithConfirmation = [=](
230 			not_null<PeerListsBox*> parentBox,
231 			const std::vector<not_null<UserData*>> &users,
232 			const std::vector<not_null<UserData*>> &nonMembers,
233 			Fn<void()> finish) {
234 		if (nonMembers.empty()) {
235 			invite(users);
236 			finish();
237 			return;
238 		}
239 		const auto name = peer->name;
240 		const auto text = (nonMembers.size() == 1)
241 			? tr::lng_group_call_add_to_group_one(
242 				tr::now,
243 				lt_user,
244 				nonMembers.front()->shortName(),
245 				lt_group,
246 				name)
247 			: (nonMembers.size() < users.size())
248 			? tr::lng_group_call_add_to_group_some(tr::now, lt_group, name)
249 			: tr::lng_group_call_add_to_group_all(tr::now, lt_group, name);
250 		const auto shared = std::make_shared<QPointer<Ui::GenericBox>>();
251 		const auto finishWithConfirm = [=] {
252 			if (*shared) {
253 				(*shared)->closeBox();
254 			}
255 			finish();
256 		};
257 		const auto done = [=] {
258 			inviteWithAdd(users, nonMembers, finishWithConfirm);
259 		};
260 		auto box = ConfirmBox({
261 			.text = { text },
262 			.button = tr::lng_participant_invite(),
263 			.callback = done,
264 		});
265 		*shared = box.data();
266 		parentBox->getDelegate()->showBox(
267 			std::move(box),
268 			Ui::LayerOption::KeepOther,
269 			anim::type::normal);
270 	};
271 	auto initBox = [=, controller = controller.get()](
272 			not_null<PeerListsBox*> box) {
273 		box->setTitle(tr::lng_group_call_invite_title());
274 		box->addButton(tr::lng_group_call_invite_button(), [=] {
275 			const auto rows = box->collectSelectedRows();
276 
277 			const auto users = ranges::views::all(
278 				rows
279 			) | ranges::views::transform([](not_null<PeerData*> peer) {
280 				return not_null<UserData*>(peer->asUser());
281 			}) | ranges::to_vector;
282 
283 			const auto nonMembers = ranges::views::all(
284 				users
285 			) | ranges::views::filter([&](not_null<UserData*> user) {
286 				return !controller->hasRowFor(user);
287 			}) | ranges::to_vector;
288 
289 			const auto finish = [box = Ui::MakeWeak(box)]() {
290 				if (box) {
291 					box->closeBox();
292 				}
293 			};
294 			inviteWithConfirmation(box, users, nonMembers, finish);
295 		});
296 		box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
297 	};
298 
299 	auto controllers = std::vector<std::unique_ptr<PeerListController>>();
300 	controllers.push_back(std::move(controller));
301 	controllers.push_back(std::move(contactsController));
302 	return Box<PeerListsBox>(std::move(controllers), initBox);
303 }
304 
305 } // namespace Calls::Group
306