/* 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 "api/api_chat_invite.h" #include "apiwrap.h" #include "window/window_session_controller.h" #include "info/profile/info_profile_badge.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/empty_userpic.h" #include "ui/painter.h" #include "core/application.h" #include "data/data_session.h" #include "data/data_photo.h" #include "data/data_photo_media.h" #include "data/data_channel.h" #include "data/data_forum.h" #include "data/data_user.h" #include "data/data_file_origin.h" #include "ui/boxes/confirm_box.h" #include "ui/toast/toast.h" #include "boxes/premium_limits_box.h" #include "styles/style_boxes.h" #include "styles/style_info.h" #include "styles/style_layers.h" namespace Api { namespace { void SubmitChatInvite( base::weak_ptr weak, not_null session, const QString &hash, bool isGroup) { session->api().request(MTPmessages_ImportChatInvite( MTP_string(hash) )).done([=](const MTPUpdates &result) { session->api().applyUpdates(result); const auto strongController = weak.get(); if (!strongController) { return; } strongController->hideLayer(); const auto handleChats = [&](const MTPVector &chats) { if (chats.v.isEmpty()) { return; } const auto peerId = chats.v[0].match([](const MTPDchat &data) { return peerFromChat(data.vid().v); }, [](const MTPDchannel &data) { return peerFromChannel(data.vid().v); }, [](auto&&) { return PeerId(0); }); if (const auto peer = session->data().peerLoaded(peerId)) { // Shows in the primary window anyway. strongController->showPeerHistory( peer, Window::SectionShow::Way::Forward); } }; result.match([&](const MTPDupdates &data) { handleChats(data.vchats()); }, [&](const MTPDupdatesCombined &data) { handleChats(data.vchats()); }, [&](auto &&) { LOG(("API Error: unexpected update cons %1 " "(ApiWrap::importChatInvite)").arg(result.type())); }); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); const auto strongController = weak.get(); if (!strongController) { return; } else if (type == u"CHANNELS_TOO_MUCH"_q) { strongController->show( Box(ChannelsLimitBox, &strongController->session())); return; } strongController->hideLayer(); strongController->showToast([&] { if (type == u"INVITE_REQUEST_SENT"_q) { return isGroup ? tr::lng_group_request_sent(tr::now) : tr::lng_group_request_sent_channel(tr::now); } else if (type == u"USERS_TOO_MUCH"_q) { return tr::lng_group_invite_no_room(tr::now); } else { return tr::lng_group_invite_bad_link(tr::now); } }(), ApiWrap::kJoinErrorDuration); }).send(); } } // namespace void CheckChatInvite( not_null controller, const QString &hash, ChannelData *invitePeekChannel) { const auto session = &controller->session(); const auto weak = base::make_weak(controller); session->api().checkChatInvite(hash, [=](const MTPChatInvite &result) { const auto strong = weak.get(); if (!strong) { return; } Core::App().hideMediaView(); const auto show = [&](not_null chat) { const auto way = Window::SectionShow::Way::Forward; if (const auto forum = chat->forum()) { strong->showForum(forum, way); } else { strong->showPeerHistory(chat, way); } }; result.match([=](const MTPDchatInvite &data) { const auto isGroup = !data.is_broadcast(); const auto box = strong->show(Box( session, data, invitePeekChannel, [=] { SubmitChatInvite(weak, session, hash, isGroup); })); if (invitePeekChannel) { box->boxClosing( ) | rpl::filter([=] { return !invitePeekChannel->amIn(); }) | rpl::start_with_next([=] { if (const auto strong = weak.get()) { strong->clearSectionStack(Window::SectionShow( Window::SectionShow::Way::ClearStack, anim::type::normal, anim::activation::background)); } }, box->lifetime()); } }, [=](const MTPDchatInviteAlready &data) { if (const auto chat = session->data().processChat(data.vchat())) { if (const auto channel = chat->asChannel()) { channel->clearInvitePeek(); } show(chat); } }, [=](const MTPDchatInvitePeek &data) { if (const auto chat = session->data().processChat(data.vchat())) { if (const auto channel = chat->asChannel()) { channel->setInvitePeek(hash, data.vexpires().v); show(chat); } } }); }, [=](const MTP::Error &error) { if (error.code() != 400) { return; } Core::App().hideMediaView(); if (const auto strong = weak.get()) { strong->show(Ui::MakeInformBox(tr::lng_group_invite_bad_link())); } }); } } // namespace Api struct ConfirmInviteBox::Participant { not_null user; Ui::PeerUserpicView userpic; }; ConfirmInviteBox::ConfirmInviteBox( QWidget*, not_null session, const MTPDchatInvite &data, ChannelData *invitePeekChannel, Fn submit) : ConfirmInviteBox( session, Parse(session, data), invitePeekChannel, std::move(submit)) { } ConfirmInviteBox::ConfirmInviteBox( not_null session, ChatInvite &&invite, ChannelData *invitePeekChannel, Fn submit) : _session(session) , _submit(std::move(submit)) , _title(this, st::confirmInviteTitle) , _badge(std::make_unique( this, st::infoPeerBadge, _session, rpl::single(Info::Profile::Badge::Content{ BadgeForInvite(invite) }), nullptr, [=] { return false; })) , _status(this, st::confirmInviteStatus) , _about(this, st::confirmInviteAbout) , _aboutRequests(this, st::confirmInviteStatus) , _participants(std::move(invite.participants)) , _isChannel(invite.isChannel && !invite.isMegagroup) , _requestApprove(invite.isRequestNeeded) { const auto count = invite.participantsCount; const auto status = [&] { return invitePeekChannel ? tr::lng_channel_invite_private(tr::now) : (!_participants.empty() && _participants.size() < count) ? tr::lng_group_invite_members(tr::now, lt_count, count) : (count > 0 && _isChannel) ? tr::lng_chat_status_subscribers( tr::now, lt_count_decimal, count) : (count > 0) ? tr::lng_chat_status_members(tr::now, lt_count_decimal, count) : _isChannel ? tr::lng_channel_status(tr::now) : tr::lng_group_status(tr::now); }(); _title->setText(invite.title); _status->setText(status); if (!invite.about.isEmpty()) { _about->setText(invite.about); } else { _about.destroy(); } if (_requestApprove) { _aboutRequests->setText(_isChannel ? tr::lng_group_request_about_channel(tr::now) : tr::lng_group_request_about(tr::now)); } else { _aboutRequests.destroy(); } if (invite.photo) { _photo = invite.photo->createMediaView(); _photo->wanted(Data::PhotoSize::Small, Data::FileOrigin()); if (!_photo->image(Data::PhotoSize::Small)) { _session->downloaderTaskFinished( ) | rpl::start_with_next([=] { update(); }, lifetime()); } } else { _photoEmpty = std::make_unique( Ui::EmptyUserpic::UserpicColor(0), invite.title); } } ConfirmInviteBox::~ConfirmInviteBox() = default; ConfirmInviteBox::ChatInvite ConfirmInviteBox::Parse( not_null session, const MTPDchatInvite &data) { auto participants = std::vector(); if (const auto list = data.vparticipants()) { participants.reserve(list->v.size()); for (const auto &participant : list->v) { if (const auto user = session->data().processUser(participant)) { participants.push_back(Participant{ user }); } } } const auto photo = session->data().processPhoto(data.vphoto()); return { .title = qs(data.vtitle()), .about = data.vabout().value_or_empty(), .photo = (photo->isNull() ? nullptr : photo.get()), .participantsCount = data.vparticipants_count().v, .participants = std::move(participants), .isPublic = data.is_public(), .isChannel = data.is_channel(), .isMegagroup = data.is_megagroup(), .isBroadcast = data.is_broadcast(), .isRequestNeeded = data.is_request_needed(), .isFake = data.is_fake(), .isScam = data.is_scam(), .isVerified = data.is_verified(), }; } [[nodiscard]] Info::Profile::BadgeType ConfirmInviteBox::BadgeForInvite( const ChatInvite &invite) { using Type = Info::Profile::BadgeType; return invite.isVerified ? Type::Verified : invite.isScam ? Type::Scam : invite.isFake ? Type::Fake : Type::None; } void ConfirmInviteBox::prepare() { addButton( (_requestApprove ? tr::lng_group_request_to_join() : _isChannel ? tr::lng_profile_join_channel() : tr::lng_profile_join_group()), _submit); addButton(tr::lng_cancel(), [=] { closeBox(); }); while (_participants.size() > 4) { _participants.pop_back(); } auto newHeight = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom(); if (!_participants.empty()) { int skip = (st::confirmInviteUsersWidth - 4 * st::confirmInviteUserPhotoSize) / 5; int padding = skip / 2; _userWidth = (st::confirmInviteUserPhotoSize + 2 * padding); int sumWidth = _participants.size() * _userWidth; int left = (st::boxWideWidth - sumWidth) / 2; for (const auto &participant : _participants) { auto name = new Ui::FlatLabel(this, st::confirmInviteUserName); name->resizeToWidth(st::confirmInviteUserPhotoSize + padding); name->setText(participant.user->firstName.isEmpty() ? participant.user->name() : participant.user->firstName); name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop); left += _userWidth; } newHeight += st::confirmInviteUserHeight; } if (_about) { const auto padding = st::confirmInviteAboutPadding; _about->resizeToWidth(st::boxWideWidth - padding.left() - padding.right()); newHeight += padding.top() + _about->height() + padding.bottom(); } if (_aboutRequests) { const auto padding = st::confirmInviteAboutRequestsPadding; _aboutRequests->resizeToWidth(st::boxWideWidth - padding.left() - padding.right()); newHeight += padding.top() + _aboutRequests->height() + padding.bottom(); } setDimensions(st::boxWideWidth, newHeight); } void ConfirmInviteBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); const auto padding = st::boxRowPadding; auto nameWidth = width() - padding.left() - padding.right(); auto badgeWidth = 0; if (const auto widget = _badge->widget()) { badgeWidth = st::infoVerifiedCheckPosition.x() + widget->width(); nameWidth -= badgeWidth; } _title->resizeToWidth(std::min(nameWidth, _title->textMaxWidth())); _title->moveToLeft( (width() - _title->width() - badgeWidth) / 2, st::confirmInviteTitleTop); const auto badgeLeft = _title->x() + _title->width(); const auto badgeTop = _title->y(); const auto badgeBottom = _title->y() + _title->height(); _badge->move(badgeLeft, badgeTop, badgeBottom); _status->move( (width() - _status->width()) / 2, st::confirmInviteStatusTop); auto bottom = _status->y() + _status->height() + st::boxPadding.bottom() + (_participants.empty() ? 0 : st::confirmInviteUserHeight); if (_about) { const auto padding = st::confirmInviteAboutPadding; _about->move((width() - _about->width()) / 2, bottom + padding.top()); bottom += padding.top() + _about->height() + padding.bottom(); } if (_aboutRequests) { const auto padding = st::confirmInviteAboutRequestsPadding; _aboutRequests->move((width() - _aboutRequests->width()) / 2, bottom + padding.top()); } } void ConfirmInviteBox::paintEvent(QPaintEvent *e) { BoxContent::paintEvent(e); Painter p(this); if (_photo) { if (const auto image = _photo->image(Data::PhotoSize::Small)) { const auto size = st::confirmInvitePhotoSize; p.drawPixmap( (width() - size) / 2, st::confirmInvitePhotoTop, image->pix( { size, size }, { .options = Images::Option::RoundCircle })); } } else if (_photoEmpty) { _photoEmpty->paintCircle( p, (width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, width(), st::confirmInvitePhotoSize); } int sumWidth = _participants.size() * _userWidth; int left = (width() - sumWidth) / 2; for (auto &participant : _participants) { participant.user->paintUserpicLeft( p, participant.userpic, left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, st::confirmInviteUserPhotoTop, width(), st::confirmInviteUserPhotoSize); left += _userWidth; } }