| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113 |
- /*
- 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 "info/profile/info_profile_cover.h"
- #include "api/api_user_privacy.h"
- #include "base/timer_rpl.h"
- #include "data/data_peer_values.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_emoji_statuses.h"
- #include "data/data_peer.h"
- #include "data/data_user.h"
- #include "data/data_document.h"
- #include "data/data_document_media.h"
- #include "data/data_changes.h"
- #include "data/data_session.h"
- #include "data/data_forum_topic.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "info/profile/info_profile_values.h"
- #include "info/profile/info_profile_badge.h"
- #include "info/profile/info_profile_emoji_status_panel.h"
- #include "info/info_controller.h"
- #include "boxes/peers/edit_forum_topic_box.h"
- #include "boxes/report_messages_box.h"
- #include "history/view/media/history_view_sticker_player.h"
- #include "lang/lang_keys.h"
- #include "ui/boxes/show_or_premium_box.h"
- #include "ui/controls/userpic_button.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/labels.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/text/text_utilities.h"
- #include "ui/ui_utility.h"
- #include "ui/painter.h"
- #include "base/event_filter.h"
- #include "base/unixtime.h"
- #include "window/window_controller.h"
- #include "window/window_session_controller.h"
- #include "main/main_session.h"
- #include "settings/settings_premium.h"
- #include "chat_helpers/stickers_lottie.h"
- #include "apiwrap.h"
- #include "api/api_peer_photo.h"
- #include "styles/style_boxes.h"
- #include "styles/style_info.h"
- #include "styles/style_dialogs.h"
- #include "styles/style_menu_icons.h"
- namespace Info::Profile {
- namespace {
- constexpr auto kWaitBeforeGiftBadge = crl::time(1000);
- constexpr auto kGiftBadgeGlares = 3;
- constexpr auto kGlareDurationStep = crl::time(320);
- constexpr auto kGlareTimeout = crl::time(1000);
- auto MembersStatusText(int count) {
- return tr::lng_chat_status_members(tr::now, lt_count_decimal, count);
- };
- auto OnlineStatusText(int count) {
- return tr::lng_chat_status_online(tr::now, lt_count_decimal, count);
- };
- auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) {
- if (onlineCount > 1 && onlineCount <= fullCount) {
- return tr::lng_chat_status_members_online(
- tr::now,
- lt_members_count,
- MembersStatusText(fullCount),
- lt_online_count,
- OnlineStatusText(onlineCount));
- } else if (fullCount > 0) {
- return isGroup
- ? tr::lng_chat_status_members(
- tr::now,
- lt_count_decimal,
- fullCount)
- : tr::lng_chat_status_subscribers(
- tr::now,
- lt_count_decimal,
- fullCount);
- }
- return isGroup
- ? tr::lng_group_status(tr::now)
- : tr::lng_channel_status(tr::now);
- };
- [[nodiscard]] const style::InfoProfileCover &CoverStyle(
- not_null<PeerData*> peer,
- Data::ForumTopic *topic,
- Cover::Role role) {
- return (role == Cover::Role::EditContact)
- ? st::infoEditContactCover
- : topic
- ? st::infoTopicCover
- : peer->isMegagroup()
- ? st::infoProfileMegagroupCover
- : st::infoProfileCover;
- }
- [[nodiscard]] QMargins LargeCustomEmojiMargins() {
- const auto ratio = style::DevicePixelRatio();
- const auto emoji = Ui::Emoji::GetSizeLarge() / ratio;
- const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large)
- / ratio;
- const auto left = (size - emoji) / 2;
- const auto right = size - emoji - left;
- return { left, left, right, right };
- }
- } // namespace
- class Cover::BadgeTooltip final : public Ui::RpWidget {
- public:
- BadgeTooltip(
- not_null<QWidget*> parent,
- std::shared_ptr<Data::EmojiStatusCollectible> collectible,
- not_null<QWidget*> pointTo);
- void fade(bool shown);
- void finishAnimating();
- [[nodiscard]] crl::time glarePeriod() const;
- private:
- void paintEvent(QPaintEvent *e) override;
- void setupGeometry(not_null<QWidget*> pointTo);
- void prepareImage();
- void showGlare();
- const style::ImportantTooltip &_st;
- std::shared_ptr<Data::EmojiStatusCollectible> _collectible;
- QString _text;
- const style::font &_font;
- QSize _inner;
- QSize _outer;
- int _stroke = 0;
- int _skip = 0;
- QSize _full;
- int _glareSize = 0;
- int _glareRange = 0;
- crl::time _glareDuration = 0;
- base::Timer _glareTimer;
- Ui::Animations::Simple _showAnimation;
- Ui::Animations::Simple _glareAnimation;
- QImage _image;
- int _glareRight = 0;
- int _imageGlareRight = 0;
- int _arrowMiddle = 0;
- int _imageArrowMiddle = 0;
- bool _shown = false;
- };
- Cover::BadgeTooltip::BadgeTooltip(
- not_null<QWidget*> parent,
- std::shared_ptr<Data::EmojiStatusCollectible> collectible,
- not_null<QWidget*> pointTo)
- : Ui::RpWidget(parent)
- , _st(st::infoGiftTooltip)
- , _collectible(std::move(collectible))
- , _text(_collectible->title)
- , _font(st::infoGiftTooltipFont)
- , _inner(_font->width(_text), _font->height)
- , _outer(_inner.grownBy(_st.padding))
- , _stroke(st::lineWidth)
- , _skip(2 * _stroke)
- , _full(_outer + QSize(2 * _skip, _st.arrow + 2 * _skip))
- , _glareSize(_outer.height() * 3)
- , _glareRange(_outer.width() + _glareSize)
- , _glareDuration(_glareRange * kGlareDurationStep / _glareSize)
- , _glareTimer([=] { showGlare(); }) {
- resize(_full + QSize(0, _st.shift));
- setupGeometry(pointTo);
- }
- void Cover::BadgeTooltip::fade(bool shown) {
- if (_shown == shown) {
- return;
- }
- show();
- _shown = shown;
- _showAnimation.start([=] {
- update();
- if (!_showAnimation.animating()) {
- if (!_shown) {
- hide();
- } else {
- showGlare();
- }
- }
- }, _shown ? 0. : 1., _shown ? 1. : 0., _st.duration, anim::easeInCirc);
- }
- void Cover::BadgeTooltip::showGlare() {
- _glareAnimation.start([=] {
- update();
- if (!_glareAnimation.animating()) {
- _glareTimer.callOnce(kGlareTimeout);
- }
- }, 0., 1., _glareDuration);
- }
- void Cover::BadgeTooltip::finishAnimating() {
- _showAnimation.stop();
- if (!_shown) {
- hide();
- }
- }
- crl::time Cover::BadgeTooltip::glarePeriod() const {
- return _glareDuration + kGlareTimeout;
- }
- void Cover::BadgeTooltip::paintEvent(QPaintEvent *e) {
- const auto glare = _glareAnimation.value(0.);
- _glareRight = anim::interpolate(0, _glareRange, glare);
- prepareImage();
- auto p = QPainter(this);
- const auto shown = _showAnimation.value(_shown ? 1. : 0.);
- p.setOpacity(shown);
- const auto imageHeight = _image.height() / _image.devicePixelRatio();
- const auto top = anim::interpolate(0, height() - imageHeight, shown);
- p.drawImage(0, top, _image);
- }
- void Cover::BadgeTooltip::setupGeometry(not_null<QWidget*> pointTo) {
- auto widget = pointTo.get();
- const auto parent = parentWidget();
- const auto refresh = [=] {
- const auto rect = Ui::MapFrom(parent, pointTo, pointTo->rect());
- const auto point = QPoint(rect.center().x(), rect.y());
- const auto left = point.x() - (width() / 2);
- const auto skip = _st.padding.left();
- setGeometry(
- std::min(std::max(left, skip), parent->width() - width() - skip),
- std::max(point.y() - height() - _st.margin.bottom(), skip),
- width(),
- height());
- const auto arrowMiddle = point.x() - x();
- if (_arrowMiddle != arrowMiddle) {
- _arrowMiddle = arrowMiddle;
- update();
- }
- };
- refresh();
- while (widget && widget != parent) {
- base::install_event_filter(this, widget, [=](not_null<QEvent*> e) {
- if (e->type() == QEvent::Resize || e->type() == QEvent::Move || e->type() == QEvent::ZOrderChange) {
- refresh();
- raise();
- }
- return base::EventFilterResult::Continue;
- });
- widget = widget->parentWidget();
- }
- }
- void Cover::BadgeTooltip::prepareImage() {
- const auto ratio = style::DevicePixelRatio();
- const auto arrow = _st.arrow;
- const auto size = _full * ratio;
- if (_image.size() != size) {
- _image = QImage(size, QImage::Format_ARGB32_Premultiplied);
- _image.setDevicePixelRatio(ratio);
- } else if (_imageGlareRight == _glareRight
- && _imageArrowMiddle == _arrowMiddle) {
- return;
- }
- _imageGlareRight = _glareRight;
- _imageArrowMiddle = _arrowMiddle;
- _image.fill(Qt::transparent);
- const auto gfrom = _imageGlareRight - _glareSize;
- const auto gtill = _imageGlareRight;
- auto path = QPainterPath();
- const auto width = _outer.width();
- const auto height = _outer.height();
- const auto radius = (height + 1) / 2;
- const auto diameter = height;
- path.moveTo(radius, 0);
- path.lineTo(width - radius, 0);
- path.arcTo(
- QRect(QPoint(width - diameter, 0), QSize(diameter, diameter)),
- 90,
- -180);
- const auto xarrow = _arrowMiddle - _skip;
- if (xarrow - arrow <= radius || xarrow + arrow >= width - radius) {
- path.lineTo(radius, height);
- } else {
- path.lineTo(xarrow + arrow, height);
- path.lineTo(xarrow, height + arrow);
- path.lineTo(xarrow - arrow, height);
- path.lineTo(radius, height);
- }
- path.arcTo(
- QRect(QPoint(0, 0), QSize(diameter, diameter)),
- -90,
- -180);
- path.closeSubpath();
- auto p = QPainter(&_image);
- auto hq = PainterHighQualityEnabler(p);
- p.setPen(Qt::NoPen);
- if (gtill > 0) {
- auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
- gradient.setStops({
- { 0., _collectible->edgeColor },
- { 0.5, _collectible->centerColor },
- { 1., _collectible->edgeColor },
- });
- p.setBrush(gradient);
- } else {
- p.setBrush(_collectible->edgeColor);
- }
- p.translate(_skip, _skip);
- p.drawPath(path);
- p.setCompositionMode(QPainter::CompositionMode_Source);
- p.setBrush(Qt::NoBrush);
- auto copy = _collectible->textColor;
- copy.setAlpha(0);
- if (gtill > 0) {
- auto gradient = QLinearGradient(gfrom, 0, gtill, 0);
- gradient.setStops({
- { 0., copy },
- { 0.5, _collectible->textColor },
- { 1., copy },
- });
- p.setPen(QPen(gradient, _stroke));
- } else {
- p.setPen(QPen(copy, _stroke));
- }
- p.drawPath(path);
- p.setCompositionMode(QPainter::CompositionMode_SourceOver);
- p.setFont(_font);
- p.setPen(QColor(255, 255, 255));
- p.drawText(_st.padding.left(), _st.padding.top() + _font->ascent, _text);
- }
- TopicIconView::TopicIconView(
- not_null<Data::ForumTopic*> topic,
- Fn<bool()> paused,
- Fn<void()> update)
- : TopicIconView(
- topic,
- std::move(paused),
- std::move(update),
- st::windowSubTextFg) {
- }
- TopicIconView::TopicIconView(
- not_null<Data::ForumTopic*> topic,
- Fn<bool()> paused,
- Fn<void()> update,
- const style::color &generalIconFg)
- : _topic(topic)
- , _generalIconFg(generalIconFg)
- , _paused(std::move(paused))
- , _update(std::move(update)) {
- setup(topic);
- }
- void TopicIconView::paintInRect(QPainter &p, QRect rect) {
- const auto paint = [&](const QImage &image) {
- const auto size = image.size() / style::DevicePixelRatio();
- p.drawImage(
- QRect(
- rect.x() + (rect.width() - size.width()) / 2,
- rect.y() + (rect.height() - size.height()) / 2,
- size.width(),
- size.height()),
- image);
- };
- if (_player && _player->ready()) {
- const auto colored = _playerUsesTextColor
- ? st::windowFg->c
- : QColor(0, 0, 0, 0);
- paint(_player->frame(
- st::infoTopicCover.photo.size,
- colored,
- false,
- crl::now(),
- _paused()).image);
- _player->markFrameShown();
- } else if (!_topic->iconId() && !_image.isNull()) {
- paint(_image);
- }
- }
- void TopicIconView::setup(not_null<Data::ForumTopic*> topic) {
- setupPlayer(topic);
- setupImage(topic);
- }
- void TopicIconView::setupPlayer(not_null<Data::ForumTopic*> topic) {
- IconIdValue(
- topic
- ) | rpl::map([=](DocumentId id) -> rpl::producer<DocumentData*> {
- if (!id) {
- return rpl::single((DocumentData*)nullptr);
- }
- return topic->owner().customEmojiManager().resolve(
- id
- ) | rpl::map([=](not_null<DocumentData*> document) {
- return document.get();
- }) | rpl::map_error_to_done();
- }) | rpl::flatten_latest(
- ) | rpl::map([=](DocumentData *document)
- -> rpl::producer<std::shared_ptr<StickerPlayer>> {
- if (!document) {
- return rpl::single(std::shared_ptr<StickerPlayer>());
- }
- const auto media = document->createMediaView();
- media->checkStickerLarge();
- media->goodThumbnailWanted();
- return rpl::single() | rpl::then(
- document->owner().session().downloaderTaskFinished()
- ) | rpl::filter([=] {
- return media->loaded();
- }) | rpl::take(1) | rpl::map([=] {
- auto result = std::shared_ptr<StickerPlayer>();
- const auto sticker = document->sticker();
- if (sticker->isLottie()) {
- result = std::make_shared<HistoryView::LottiePlayer>(
- ChatHelpers::LottiePlayerFromDocument(
- media.get(),
- ChatHelpers::StickerLottieSize::StickerSet,
- st::infoTopicCover.photo.size,
- Lottie::Quality::High));
- } else if (sticker->isWebm()) {
- result = std::make_shared<HistoryView::WebmPlayer>(
- media->owner()->location(),
- media->bytes(),
- st::infoTopicCover.photo.size);
- } else {
- result = std::make_shared<HistoryView::StaticStickerPlayer>(
- media->owner()->location(),
- media->bytes(),
- st::infoTopicCover.photo.size);
- }
- result->setRepaintCallback(_update);
- _playerUsesTextColor = media->owner()->emojiUsesTextColor();
- return result;
- });
- }) | rpl::flatten_latest(
- ) | rpl::start_with_next([=](std::shared_ptr<StickerPlayer> player) {
- _player = std::move(player);
- if (!_player) {
- _update();
- }
- }, _lifetime);
- }
- void TopicIconView::setupImage(not_null<Data::ForumTopic*> topic) {
- using namespace Data;
- if (topic->isGeneral()) {
- rpl::single(rpl::empty) | rpl::then(
- style::PaletteChanged()
- ) | rpl::start_with_next([=] {
- _image = ForumTopicGeneralIconFrame(
- st::infoForumTopicIcon.size,
- _generalIconFg->c);
- _update();
- }, _lifetime);
- return;
- }
- rpl::combine(
- TitleValue(topic),
- ColorIdValue(topic)
- ) | rpl::map([=](const QString &title, int32 colorId) {
- return ForumTopicIconFrame(colorId, title, st::infoForumTopicIcon);
- }) | rpl::start_with_next([=](QImage &&image) {
- _image = std::move(image);
- _update();
- }, _lifetime);
- }
- TopicIconButton::TopicIconButton(
- QWidget *parent,
- not_null<Window::SessionController*> controller,
- not_null<Data::ForumTopic*> topic)
- : TopicIconButton(parent, topic, [=] {
- return controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
- }) {
- }
- TopicIconButton::TopicIconButton(
- QWidget *parent,
- not_null<Data::ForumTopic*> topic,
- Fn<bool()> paused)
- : AbstractButton(parent)
- , _view(topic, paused, [=] { update(); }) {
- resize(st::infoTopicCover.photo.size);
- paintRequest(
- ) | rpl::start_with_next([=] {
- auto p = QPainter(this);
- _view.paintInRect(p, rect());
- }, lifetime());
- }
- Cover::Cover(
- QWidget *parent,
- not_null<Window::SessionController*> controller,
- not_null<PeerData*> peer,
- Fn<not_null<QWidget*>()> parentForTooltip)
- : Cover(
- parent,
- controller,
- peer,
- nullptr,
- Role::Info,
- NameValue(peer),
- parentForTooltip) {
- }
- Cover::Cover(
- QWidget *parent,
- not_null<Window::SessionController*> controller,
- not_null<Data::ForumTopic*> topic)
- : Cover(
- parent,
- controller,
- topic->channel(),
- topic,
- Role::Info,
- TitleValue(topic),
- nullptr) {
- }
- Cover::Cover(
- QWidget *parent,
- not_null<Window::SessionController*> controller,
- not_null<PeerData*> peer,
- Role role,
- rpl::producer<QString> title)
- : Cover(
- parent,
- controller,
- peer,
- nullptr,
- role,
- std::move(title),
- nullptr) {
- }
- [[nodiscard]] rpl::producer<Badge::Content> BotVerifyBadgeForPeer(
- not_null<PeerData*> peer) {
- return peer->session().changes().peerFlagsValue(
- peer,
- Data::PeerUpdate::Flag::VerifyInfo
- ) | rpl::map([=] {
- const auto info = peer->botVerifyDetails();
- return Badge::Content{
- .badge = info ? BadgeType::BotVerified : BadgeType::None,
- .emojiStatusId = { info ? info->iconId : DocumentId() },
- };
- });
- }
- Cover::Cover(
- QWidget *parent,
- not_null<Window::SessionController*> controller,
- not_null<PeerData*> peer,
- Data::ForumTopic *topic,
- Role role,
- rpl::producer<QString> title,
- Fn<not_null<QWidget*>()> parentForTooltip)
- : FixedHeightWidget(parent, CoverStyle(peer, topic, role).height)
- , _st(CoverStyle(peer, topic, role))
- , _role(role)
- , _controller(controller)
- , _peer(peer)
- , _emojiStatusPanel(peer->isSelf()
- ? std::make_unique<EmojiStatusPanel>()
- : nullptr)
- , _botVerify(
- std::make_unique<Badge>(
- this,
- st::infoPeerBadge,
- &peer->session(),
- BotVerifyBadgeForPeer(peer),
- nullptr,
- [=] {
- return controller->isGifPausedAtLeastFor(
- Window::GifPauseReason::Layer);
- }))
- , _badgeContent(BadgeContentForPeer(peer))
- , _badge(
- std::make_unique<Badge>(
- this,
- st::infoPeerBadge,
- &peer->session(),
- _badgeContent.value(),
- _emojiStatusPanel.get(),
- [=] {
- return controller->isGifPausedAtLeastFor(
- Window::GifPauseReason::Layer);
- }))
- , _verified(
- std::make_unique<Badge>(
- this,
- st::infoPeerBadge,
- &peer->session(),
- VerifiedContentForPeer(peer),
- _emojiStatusPanel.get(),
- [=] {
- return controller->isGifPausedAtLeastFor(
- Window::GifPauseReason::Layer);
- }))
- , _parentForTooltip(std::move(parentForTooltip))
- , _badgeTooltipHide([=] { hideBadgeTooltip(); })
- , _userpic(topic
- ? nullptr
- : object_ptr<Ui::UserpicButton>(
- this,
- controller,
- _peer,
- Ui::UserpicButton::Role::OpenPhoto,
- Ui::UserpicButton::Source::PeerPhoto,
- _st.photo))
- , _changePersonal((role == Role::Info
- || topic
- || !_peer->isUser()
- || _peer->isSelf()
- || _peer->asUser()->isBot())
- ? nullptr
- : CreateUploadSubButton(this, _peer->asUser(), controller).get())
- , _iconButton(topic
- ? object_ptr<TopicIconButton>(this, controller, topic)
- : nullptr)
- , _name(this, _st.name)
- , _status(this, _st.status)
- , _showLastSeen(this, tr::lng_status_lastseen_when(), _st.showLastSeen)
- , _refreshStatusTimer([this] { refreshStatusText(); }) {
- _peer->updateFull();
- _name->setSelectable(true);
- _name->setContextCopyText(tr::lng_profile_copy_fullname(tr::now));
- if (!_peer->isMegagroup()) {
- _status->setAttribute(Qt::WA_TransparentForMouseEvents);
- }
- setupShowLastSeen();
- _badge->setPremiumClickCallback([=] {
- if (const auto panel = _emojiStatusPanel.get()) {
- panel->show(_controller, _badge->widget(), _badge->sizeTag());
- } else {
- ::Settings::ShowEmojiStatusPremium(_controller, _peer);
- }
- });
- rpl::merge(
- _botVerify->updated(),
- _badge->updated(),
- _verified->updated()
- ) | rpl::start_with_next([=] {
- refreshNameGeometry(width());
- }, _name->lifetime());
- initViewers(std::move(title));
- setupChildGeometry();
- setupUniqueBadgeTooltip();
- if (_userpic) {
- } else if (topic->canEdit()) {
- _iconButton->setClickedCallback([=] {
- _controller->show(Box(
- EditForumTopicBox,
- _controller,
- topic->history(),
- topic->rootId()));
- });
- } else {
- _iconButton->setAttribute(Qt::WA_TransparentForMouseEvents);
- }
- }
- void Cover::setupShowLastSeen() {
- const auto user = _peer->asUser();
- if (_st.showLastSeenVisible
- && user
- && !user->isSelf()
- && !user->isBot()
- && !user->isServiceUser()
- && user->session().premiumPossible()) {
- if (user->session().premium()) {
- if (user->lastseen().isHiddenByMe()) {
- user->updateFullForced();
- }
- _showLastSeen->hide();
- return;
- }
- rpl::combine(
- user->session().changes().peerFlagsValue(
- user,
- Data::PeerUpdate::Flag::OnlineStatus),
- Data::AmPremiumValue(&user->session())
- ) | rpl::start_with_next([=](auto, bool premium) {
- const auto wasShown = !_showLastSeen->isHidden();
- const auto hiddenByMe = user->lastseen().isHiddenByMe();
- const auto shown = hiddenByMe
- && !user->lastseen().isOnline(base::unixtime::now())
- && !premium
- && user->session().premiumPossible();
- _showLastSeen->setVisible(shown);
- if (wasShown && premium && hiddenByMe) {
- user->updateFullForced();
- }
- }, _showLastSeen->lifetime());
- _controller->session().api().userPrivacy().value(
- Api::UserPrivacy::Key::LastSeen
- ) | rpl::filter([=](Api::UserPrivacy::Rule rule) {
- return (rule.option == Api::UserPrivacy::Option::Everyone);
- }) | rpl::start_with_next([=] {
- if (user->lastseen().isHiddenByMe()) {
- user->updateFullForced();
- }
- }, _showLastSeen->lifetime());
- } else {
- _showLastSeen->hide();
- }
- using TextTransform = Ui::RoundButton::TextTransform;
- _showLastSeen->setTextTransform(TextTransform::NoTransform);
- _showLastSeen->setFullRadius(true);
- _showLastSeen->setClickedCallback([=] {
- const auto type = Ui::ShowOrPremium::LastSeen;
- auto box = Box(Ui::ShowOrPremiumBox, type, user->shortName(), [=] {
- _controller->session().api().userPrivacy().save(
- ::Api::UserPrivacy::Key::LastSeen,
- {});
- }, [=] {
- ::Settings::ShowPremium(_controller, u"lastseen_hidden"_q);
- });
- _controller->show(std::move(box));
- });
- }
- void Cover::setupChildGeometry() {
- widthValue(
- ) | rpl::start_with_next([this](int newWidth) {
- if (_userpic) {
- _userpic->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
- } else {
- _iconButton->moveToLeft(_st.photoLeft, _st.photoTop, newWidth);
- }
- if (_changePersonal) {
- _changePersonal->moveToLeft(
- (_st.photoLeft
- + _st.photo.photoSize
- - _changePersonal->width()
- + st::infoEditContactPersonalLeft),
- (_userpic->y()
- + _userpic->height()
- - _changePersonal->height()));
- }
- refreshNameGeometry(newWidth);
- refreshStatusGeometry(newWidth);
- }, lifetime());
- }
- Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
- _onlineCount = std::move(count);
- return this;
- }
- std::optional<QImage> Cover::updatedPersonalPhoto() const {
- return _personalChosen;
- }
- void Cover::initViewers(rpl::producer<QString> title) {
- using Flag = Data::PeerUpdate::Flag;
- std::move(
- title
- ) | rpl::start_with_next([=](const QString &title) {
- _name->setText(title);
- refreshNameGeometry(width());
- }, lifetime());
- rpl::combine(
- _peer->session().changes().peerFlagsValue(
- _peer,
- Flag::OnlineStatus | Flag::Members),
- _onlineCount.value()
- ) | rpl::start_with_next([=] {
- refreshStatusText();
- }, lifetime());
- _peer->session().changes().peerFlagsValue(
- _peer,
- (_peer->isUser() ? Flag::IsContact : Flag::Rights)
- ) | rpl::start_with_next([=] {
- refreshUploadPhotoOverlay();
- }, lifetime());
- setupChangePersonal();
- }
- void Cover::refreshUploadPhotoOverlay() {
- if (!_userpic) {
- return;
- } else if (_role == Role::EditContact) {
- _userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
- return;
- }
- const auto canChange = [&] {
- if (const auto chat = _peer->asChat()) {
- return chat->canEditInformation();
- } else if (const auto channel = _peer->asChannel()) {
- return channel->canEditInformation();
- } else if (const auto user = _peer->asUser()) {
- return user->isSelf()
- || (user->isContact()
- && !user->isInaccessible()
- && !user->isServiceUser());
- }
- Unexpected("Peer type in Info::Profile::Cover.");
- }();
- _userpic->switchChangePhotoOverlay(canChange, [=](
- Ui::UserpicButton::ChosenImage chosen) {
- using ChosenType = Ui::UserpicButton::ChosenType;
- auto result = Api::PeerPhoto::UserPhoto{
- base::take<QImage>(chosen.image), // Strange MSVC bug with take.
- chosen.markup.documentId,
- chosen.markup.colors,
- };
- switch (chosen.type) {
- case ChosenType::Set:
- _userpic->showCustom(base::duplicate(result.image));
- _peer->session().api().peerPhoto().upload(
- _peer,
- std::move(result));
- break;
- case ChosenType::Suggest:
- _peer->session().api().peerPhoto().suggest(
- _peer,
- std::move(result));
- break;
- }
- });
- const auto canReport = [=, peer = _peer] {
- if (!peer->hasUserpic()) {
- return false;
- }
- const auto user = peer->asUser();
- if (!user) {
- if (canChange) {
- return false;
- }
- } else if (user->hasPersonalPhoto()
- || user->isSelf()
- || user->isInaccessible()
- || user->isRepliesChat()
- || user->isVerifyCodes()
- || (user->botInfo && user->botInfo->canEditInformation)
- || user->isServiceUser()) {
- return false;
- }
- return true;
- };
- const auto contextMenu = _userpic->lifetime()
- .make_state<base::unique_qptr<Ui::PopupMenu>>();
- const auto showMenu = [=, peer = _peer, controller = _controller](
- not_null<Ui::RpWidget*> parent) {
- if (!canReport()) {
- return false;
- }
- *contextMenu = base::make_unique_q<Ui::PopupMenu>(
- parent,
- st::popupMenuWithIcons);
- contextMenu->get()->addAction(tr::lng_profile_report(tr::now), [=] {
- controller->show(
- ReportProfilePhotoBox(
- peer,
- peer->owner().photo(peer->userpicPhotoId())),
- Ui::LayerOption::CloseOther);
- }, &st::menuIconReport);
- contextMenu->get()->popup(QCursor::pos());
- return true;
- };
- base::install_event_filter(_userpic, [showMenu, raw = _userpic.data()](
- not_null<QEvent*> e) {
- return (e->type() == QEvent::ContextMenu && showMenu(raw))
- ? base::EventFilterResult::Cancel
- : base::EventFilterResult::Continue;
- });
- if (const auto user = _peer->asUser()) {
- _userpic->resetPersonalRequests(
- ) | rpl::start_with_next([=] {
- user->session().api().peerPhoto().clearPersonal(user);
- _userpic->showSource(Ui::UserpicButton::Source::PeerPhoto);
- }, lifetime());
- }
- }
- void Cover::setupChangePersonal() {
- if (!_changePersonal) {
- return;
- }
- _changePersonal->chosenImages(
- ) | rpl::start_with_next([=](Ui::UserpicButton::ChosenImage &&chosen) {
- if (chosen.type == Ui::UserpicButton::ChosenType::Suggest) {
- _peer->session().api().peerPhoto().suggest(
- _peer,
- {
- std::move(chosen.image),
- chosen.markup.documentId,
- chosen.markup.colors,
- });
- } else {
- _personalChosen = std::move(chosen.image);
- _userpic->showCustom(base::duplicate(*_personalChosen));
- _changePersonal->overrideHasPersonalPhoto(true);
- _changePersonal->showSource(
- Ui::UserpicButton::Source::NonPersonalIfHasPersonal);
- }
- }, _changePersonal->lifetime());
- _changePersonal->resetPersonalRequests(
- ) | rpl::start_with_next([=] {
- _personalChosen = QImage();
- _userpic->showSource(
- Ui::UserpicButton::Source::NonPersonalPhoto);
- _changePersonal->overrideHasPersonalPhoto(false);
- _changePersonal->showCustom(QImage());
- }, _changePersonal->lifetime());
- }
- void Cover::refreshStatusText() {
- auto hasMembersLink = [&] {
- if (auto megagroup = _peer->asMegagroup()) {
- return megagroup->canViewMembers();
- }
- return false;
- }();
- auto statusText = [&]() -> TextWithEntities {
- using namespace Ui::Text;
- auto currentTime = base::unixtime::now();
- if (auto user = _peer->asUser()) {
- const auto result = Data::OnlineTextFull(user, currentTime);
- const auto showOnline = Data::OnlineTextActive(user, currentTime);
- const auto updateIn = Data::OnlineChangeTimeout(user, currentTime);
- if (showOnline) {
- _refreshStatusTimer.callOnce(updateIn);
- }
- return showOnline
- ? Ui::Text::Colorized(result)
- : TextWithEntities{ .text = result };
- } else if (auto chat = _peer->asChat()) {
- if (!chat->amIn()) {
- return tr::lng_chat_status_unaccessible({}, WithEntities);
- }
- const auto onlineCount = _onlineCount.current();
- const auto fullCount = std::max(
- chat->count,
- int(chat->participants.size()));
- return { .text = ChatStatusText(fullCount, onlineCount, true) };
- } else if (auto channel = _peer->asChannel()) {
- const auto onlineCount = _onlineCount.current();
- const auto fullCount = qMax(channel->membersCount(), 1);
- auto result = ChatStatusText(
- fullCount,
- onlineCount,
- channel->isMegagroup());
- return hasMembersLink
- ? Ui::Text::Link(result)
- : TextWithEntities{ .text = result };
- }
- return tr::lng_chat_status_unaccessible(tr::now, WithEntities);
- }();
- _status->setMarkedText(statusText);
- if (hasMembersLink) {
- _status->setLink(1, std::make_shared<LambdaClickHandler>([=] {
- _showSection.fire(Section::Type::Members);
- }));
- }
- refreshStatusGeometry(width());
- }
- Cover::~Cover() {
- base::take(_badgeTooltip);
- base::take(_badgeOldTooltips);
- }
- void Cover::refreshNameGeometry(int newWidth) {
- auto nameWidth = newWidth - _st.nameLeft - _st.rightSkip;
- const auto verifiedWidget = _verified->widget();
- const auto badgeWidget = _badge->widget();
- if (verifiedWidget) {
- nameWidth -= verifiedWidget->width();
- }
- if (badgeWidget) {
- nameWidth -= badgeWidget->width();
- }
- if (verifiedWidget || badgeWidget) {
- nameWidth -= st::infoVerifiedCheckPosition.x();
- }
- auto nameLeft = _st.nameLeft;
- const auto badgeTop = _st.nameTop;
- const auto badgeBottom = _st.nameTop + _name->height();
- const auto margins = LargeCustomEmojiMargins();
- _botVerify->move(nameLeft - margins.left(), badgeTop, badgeBottom);
- if (const auto widget = _botVerify->widget()) {
- const auto skip = widget->width()
- + st::infoVerifiedCheckPosition.x();
- nameLeft += skip;
- nameWidth -= skip;
- }
- _name->resizeToNaturalWidth(nameWidth);
- _name->moveToLeft(nameLeft, _st.nameTop, newWidth);
- const auto badgeLeft = nameLeft + _name->width();
- _badge->move(badgeLeft, badgeTop, badgeBottom);
- _verified->move(
- badgeLeft + (badgeWidget ? badgeWidget->width() : 0),
- badgeTop,
- badgeBottom);
- }
- void Cover::refreshStatusGeometry(int newWidth) {
- auto statusWidth = newWidth - _st.statusLeft - _st.rightSkip;
- _status->resizeToWidth(statusWidth);
- _status->moveToLeft(_st.statusLeft, _st.statusTop, newWidth);
- const auto left = _st.statusLeft + _status->textMaxWidth();
- _showLastSeen->moveToLeft(
- left + _st.showLastSeenPosition.x(),
- _st.showLastSeenPosition.y(),
- newWidth);
- }
- void Cover::hideBadgeTooltip() {
- _badgeTooltipHide.cancel();
- if (auto old = base::take(_badgeTooltip)) {
- const auto raw = old.get();
- _badgeOldTooltips.push_back(std::move(old));
- raw->fade(false);
- raw->shownValue(
- ) | rpl::filter(
- !rpl::mappers::_1
- ) | rpl::start_with_next([=] {
- const auto i = ranges::find(
- _badgeOldTooltips,
- raw,
- &std::unique_ptr<BadgeTooltip>::get);
- if (i != end(_badgeOldTooltips)) {
- _badgeOldTooltips.erase(i);
- }
- }, raw->lifetime());
- }
- }
- void Cover::setupUniqueBadgeTooltip() {
- base::timer_once(kWaitBeforeGiftBadge) | rpl::then(
- _badge->updated()
- ) | rpl::start_with_next([=] {
- const auto widget = _badge->widget();
- const auto &content = _badgeContent.current();
- const auto &collectible = content.emojiStatusId.collectible;
- const auto premium = (content.badge == BadgeType::Premium);
- const auto id = (collectible && widget && premium)
- ? collectible->id
- : uint64();
- if (_badgeCollectibleId == id) {
- return;
- }
- hideBadgeTooltip();
- if (!collectible) {
- return;
- }
- const auto parent = _parentForTooltip
- ? _parentForTooltip()
- : _controller->window().widget()->bodyWidget();
- _badgeTooltip = std::make_unique<BadgeTooltip>(
- parent,
- collectible,
- widget);
- const auto raw = _badgeTooltip.get();
- raw->fade(true);
- _badgeTooltipHide.callOnce(kGiftBadgeGlares * raw->glarePeriod()
- - st::infoGiftTooltip.duration * 1.5);
- }, lifetime());
- if (const auto raw = _badgeTooltip.get()) {
- raw->finishAnimating();
- }
- }
- } // namespace Info::Profile
|