diff --git a/Telegram/SourceFiles/api/api_chat_invite.cpp b/Telegram/SourceFiles/api/api_chat_invite.cpp index f1a3f81d1c..e7e9d1bc1a 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.cpp +++ b/Telegram/SourceFiles/api/api_chat_invite.cpp @@ -48,6 +48,69 @@ namespace Api { namespace { +struct InviteParticipant { + not_null user; + Ui::PeerUserpicView userpic; +}; + +struct ChatInvite { + QString title; + QString about; + PhotoData *photo = nullptr; + int participantsCount = 0; + std::vector participants; + bool isPublic = false; + bool isChannel = false; + bool isMegagroup = false; + bool isBroadcast = false; + bool isRequestNeeded = false; + bool isFake = false; + bool isScam = false; + bool isVerified = false; +}; + +[[nodiscard]] ChatInvite ParseInvite( + 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(InviteParticipant{ 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 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 SubmitChatInvite( base::weak_ptr weak, not_null session, @@ -356,6 +419,207 @@ void ConfirmSubscriptionBox( box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } +void ConfirmInviteBox( + not_null box, + not_null session, + const MTPDchatInvite *invitePtr, + ChannelData *invitePeekChannel, + Fn submit) { + auto invite = ParseInvite(session, *invitePtr); + const auto isChannel = invite.isChannel && !invite.isMegagroup; + const auto requestApprove = invite.isRequestNeeded; + const auto count = invite.participantsCount; + + struct State { + std::shared_ptr photoMedia; + std::unique_ptr photoEmpty; + std::vector participants; + }; + const auto state = box->lifetime().make_state(); + state->participants = std::move(invite.participants); + + const auto status = [&] { + return invitePeekChannel + ? tr::lng_channel_invite_private(tr::now) + : (!state->participants.empty() + && int(state->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); + }(); + + box->setNoContentMargin(true); + box->setWidth(st::boxWideWidth); + const auto content = box->verticalLayout(); + + Ui::AddSkip(content, st::confirmInvitePhotoTop); + const auto userpic = content->add( + object_ptr(content), + style::al_top); + const auto photoSize = st::confirmInvitePhotoSize; + userpic->resize(Size(photoSize)); + userpic->setNaturalWidth(photoSize); + userpic->paintRequest( + ) | rpl::on_next([=, small = Data::PhotoSize::Small] { + auto p = QPainter(userpic); + if (state->photoMedia) { + if (const auto image = state->photoMedia->image(small)) { + p.drawPixmap( + 0, + 0, + image->pix( + Size(photoSize), + { .options = Images::Option::RoundCircle })); + } + } else if (state->photoEmpty) { + state->photoEmpty->paintCircle( + p, + 0, + 0, + userpic->width(), + photoSize); + } + }, userpic->lifetime()); + userpic->setAttribute(Qt::WA_TransparentForMouseEvents); + if (const auto photo = invite.photo) { + state->photoMedia = photo->createMediaView(); + state->photoMedia->wanted( + Data::PhotoSize::Small, + Data::FileOrigin()); + if (!state->photoMedia->image(Data::PhotoSize::Small)) { + session->downloaderTaskFinished( + ) | rpl::on_next([=] { + userpic->update(); + }, userpic->lifetime()); + } + } else { + state->photoEmpty = std::make_unique( + Ui::EmptyUserpic::UserpicColor(0), + invite.title); + } + + Ui::AddSkip(content); + const auto title = box->addRow( + object_ptr( + box, + invite.title, + st::confirmInviteTitle), + style::al_top); + + const auto badgeType = BadgeForInvite(invite); + if (badgeType != Info::Profile::BadgeType::None) { + const auto badgeParent = title->parentWidget(); + const auto badge = box->lifetime().make_state( + badgeParent, + st::infoPeerBadge, + session, + rpl::single(Info::Profile::Badge::Content{ badgeType }), + nullptr, + [] { return false; }); + title->geometryValue( + ) | rpl::on_next([=](const QRect &r) { + badge->move(r.x() + r.width(), r.y(), r.y() + r.height()); + }, title->lifetime()); + } + + box->addRow( + object_ptr( + box, + status, + st::confirmInviteStatus), + style::al_top); + + if (!invite.about.isEmpty()) { + box->addRow( + object_ptr( + box, + invite.about, + st::confirmInviteAbout), + st::confirmInviteAboutPadding, + style::al_top); + } + + if (requestApprove) { + box->addRow( + object_ptr( + box, + (isChannel + ? tr::lng_group_request_about_channel(tr::now) + : tr::lng_group_request_about(tr::now)), + st::confirmInviteStatus), + st::confirmInviteAboutRequestsPadding, + style::al_top); + } + + if (!state->participants.empty()) { + while (state->participants.size() > 4) { + state->participants.pop_back(); + } + const auto padding = (st::confirmInviteUsersWidth + - 4 * st::confirmInviteUserPhotoSize) / 10; + const auto userWidth = st::confirmInviteUserPhotoSize + 2 * padding; + + auto strip = object_ptr(content); + const auto rawStrip = strip.data(); + rawStrip->resize(st::boxWideWidth, st::confirmInviteUserHeight); + rawStrip->setNaturalWidth(st::boxWideWidth); + + const auto shown = int(state->participants.size()); + const auto sumWidth = shown * userWidth; + const auto baseLeft = (st::boxWideWidth - sumWidth) / 2; + for (auto i = 0; i != shown; ++i) { + const auto &participant = state->participants[i]; + const auto name = Ui::CreateChild( + rawStrip, + st::confirmInviteUserName); + name->resizeToWidth( + st::confirmInviteUserPhotoSize + padding); + name->setText(participant.user->firstName.isEmpty() + ? participant.user->name() + : participant.user->firstName); + name->moveToLeft( + baseLeft + i * userWidth + (padding / 2), + st::confirmInviteUserNameTop - st::confirmInviteUserPhotoTop); + } + + rawStrip->paintRequest( + ) | rpl::on_next([=] { + auto p = Painter(rawStrip); + const auto total = int(state->participants.size()); + const auto totalWidth = total * userWidth; + auto left = (rawStrip->width() - totalWidth) / 2; + for (auto &participant : state->participants) { + participant.user->paintUserpicLeft( + p, + participant.userpic, + left + (userWidth - st::confirmInviteUserPhotoSize) / 2, + 0, + rawStrip->width(), + st::confirmInviteUserPhotoSize); + left += userWidth; + } + }, rawStrip->lifetime()); + + Ui::AddSkip(content, st::boxPadding.bottom()); + content->add(std::move(strip), style::margins()); + } + + box->addButton((requestApprove + ? tr::lng_group_request_to_join() + : isChannel + ? tr::lng_profile_join_channel() + : tr::lng_profile_join_group()), submit); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + } // namespace void CheckChatInvite( @@ -399,9 +663,10 @@ void CheckChatInvite( session, hash, &data)) - : strong->show(Box( + : strong->show(Box( + ConfirmInviteBox, session, - data, + &data, invitePeekChannel, [=] { SubmitChatInvite(weak, session, hash, isGroup); })); if (invitePeekChannel) { @@ -450,255 +715,3 @@ void CheckChatInvite( } } // 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::on_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; - } -} diff --git a/Telegram/SourceFiles/api/api_chat_invite.h b/Telegram/SourceFiles/api/api_chat_invite.h index 123ccb1f8d..dbd5a43321 100644 --- a/Telegram/SourceFiles/api/api_chat_invite.h +++ b/Telegram/SourceFiles/api/api_chat_invite.h @@ -7,16 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "ui/layers/box_content.h" - -class UserData; class ChannelData; -namespace Info::Profile { -class Badge; -enum class BadgeType : uchar; -} // namespace Info::Profile - namespace Main { class Session; } // namespace Main @@ -25,14 +17,6 @@ namespace Window { class SessionController; } // namespace Window -namespace Data { -class PhotoMedia; -} // namespace Data - -namespace Ui { -class EmptyUserpic; -} // namespace Ui - namespace Api { void CheckChatInvite( @@ -42,67 +26,3 @@ void CheckChatInvite( Fn loaded = nullptr); } // namespace Api - -class ConfirmInviteBox final : public Ui::BoxContent { -public: - ConfirmInviteBox( - QWidget*, - not_null session, - const MTPDchatInvite &data, - ChannelData *invitePeekChannel, - Fn submit); - ~ConfirmInviteBox(); - -protected: - void prepare() override; - - void resizeEvent(QResizeEvent *e) override; - void paintEvent(QPaintEvent *e) override; - -private: - struct Participant; - struct ChatInvite { - QString title; - QString about; - PhotoData *photo = nullptr; - int participantsCount = 0; - std::vector participants; - bool isPublic = false; - bool isChannel = false; - bool isMegagroup = false; - bool isBroadcast = false; - bool isRequestNeeded = false; - bool isFake = false; - bool isScam = false; - bool isVerified = false; - }; - [[nodiscard]] static ChatInvite Parse( - not_null session, - const MTPDchatInvite &data); - [[nodiscard]] Info::Profile::BadgeType BadgeForInvite( - const ChatInvite &invite); - - ConfirmInviteBox( - not_null session, - ChatInvite &&invite, - ChannelData *invitePeekChannel, - Fn submit); - - const not_null _session; - - Fn _submit; - object_ptr _title; - std::unique_ptr _badge; - object_ptr _status; - object_ptr _about; - object_ptr _aboutRequests; - std::shared_ptr _photo; - std::unique_ptr _photoEmpty; - std::vector _participants; - - bool _isChannel = false; - bool _requestApprove = false; - - int _userWidth = 0; - -}; diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index e3830b53d1..0698689800 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -92,7 +92,7 @@ restoreUserpicIcon: UserpicButton(defaultUserpicButton) { } confirmInviteTitle: FlatLabel(defaultFlatLabel) { - align: align(center); + align: align(top); minWidth: 320px; maxHeight: 24px; textFg: windowBoldFg; @@ -101,9 +101,8 @@ confirmInviteTitle: FlatLabel(defaultFlatLabel) { } } confirmInviteAbout: FlatLabel(boxLabel) { - align: align(center); + align: align(top); minWidth: 320px; - maxHeight: 60px; style: TextStyle(boxLabelStyle) { lineHeight: 19px; }