Implement suggested profile photo message.

This commit is contained in:
John Preston 2022-12-09 21:35:45 +04:00
parent 5fe9c93cb6
commit 48cf0a4382
24 changed files with 768 additions and 356 deletions

View File

@ -673,8 +673,10 @@ PRIVATE
history/view/media/history_view_photo.h
history/view/media/history_view_poll.cpp
history/view/media/history_view_poll.h
history/view/media/history_view_service_media_gift.cpp
history/view/media/history_view_service_media_gift.h
history/view/media/history_view_premium_gift.cpp
history/view/media/history_view_premium_gift.h
history/view/media/history_view_service_box.cpp
history/view/media/history_view_service_box.h
history/view/media/history_view_slot_machine.cpp
history/view/media/history_view_slot_machine.h
history/view/media/history_view_sticker.cpp
@ -684,6 +686,8 @@ PRIVATE
history/view/media/history_view_sticker_player_abstract.h
history/view/media/history_view_theme_document.cpp
history/view/media/history_view_theme_document.h
history/view/media/history_view_userpic_suggestion.cpp
history/view/media/history_view_userpic_suggestion.h
history/view/media/history_view_web_page.cpp
history/view/media/history_view_web_page.h
history/view/reactions/history_view_reactions.cpp

View File

@ -1561,6 +1561,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_webview_data_done" = "You have just successfully transferred data from the «{text}» button to the bot.";
"lng_action_gift_received" = "{user} sent you a gift for {cost}";
"lng_action_gift_received_me" = "You sent to {user} a gift for {cost}";
"lng_action_suggested_photo_title" = "Suggested Photo";
"lng_action_suggested_photo_me" = "You suggested {user} to use this photo for their account.";
"lng_action_suggested_photo" = "{user} suggests you to use this photo for your account.";
"lng_action_topic_created_inside" = "Topic created";
"lng_action_topic_closed_inside" = "Topic closed";
"lng_action_topic_reopened_inside" = "Topic reopened";

View File

@ -116,6 +116,17 @@ void PeerPhoto::upload(not_null<PeerData*> peer, QImage &&image) {
upload(peer, std::move(image), false);
}
void PeerPhoto::updateSelf(not_null<PhotoData*> photo) {
_api.request(MTPphotos_UpdateProfilePhoto(
photo->mtpInput()
)).done([=](const MTPphotos_Photo &result) {
result.match([&](const MTPDphotos_photo &data) {
_session->data().processPhoto(data.vphoto());
_session->data().processUsers(data.vusers());
});
}).send();
}
void PeerPhoto::upload(
not_null<PeerData*> peer,
QImage &&image,
@ -195,8 +206,7 @@ void PeerPhoto::clearPersonal(not_null<UserData*> user) {
_session->data().processUsers(data.vusers());
});
}).send();
if (!user->userpicPhotoUnknown()
&& (user->flags() & UserDataFlag::PersonalPhoto)) {
if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) {
_session->storage().remove(Storage::UserPhotosRemoveOne(
peerToUser(user->id),
user->userpicPhotoId()));
@ -331,8 +341,7 @@ void PeerPhoto::requestUserPhotos(
}
return photoIds;
});
if (!user->userpicPhotoUnknown()
&& (user->flags() & UserDataFlag::PersonalPhoto)) {
if (!user->userpicPhotoUnknown() && user->hasPersonalPhoto()) {
const auto photo = owner.photo(user->userpicPhotoId());
if (!photo->isNull()) {
++fullCount;

View File

@ -25,6 +25,7 @@ public:
explicit PeerPhoto(not_null<ApiWrap*> api);
void upload(not_null<PeerData*> peer, QImage &&image);
void updateSelf(not_null<PhotoData*> photo);
void suggest(not_null<PeerData*> peer, QImage &&image);
void clear(not_null<PhotoData*> photo);
void clearPersonal(not_null<UserData*> user);

View File

@ -27,7 +27,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_theme_document.h"
#include "history/view/media/history_view_slot_machine.h"
#include "history/view/media/history_view_dice.h"
#include "history/view/media/history_view_service_media_gift.h"
#include "history/view/media/history_view_service_box.h"
#include "history/view/media/history_view_premium_gift.h"
#include "history/view/media/history_view_userpic_suggestion.h"
#include "dialogs/ui/dialogs_message_view.h"
#include "ui/image/image.h"
#include "ui/effects/spoiler_mess.h"
@ -702,6 +704,15 @@ std::unique_ptr<HistoryView::Media> MediaPhoto::createView(
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (_chat) {
if (realParent->isUserpicSuggestion()) {
return std::make_unique<HistoryView::ServiceBox>(
message,
std::make_unique<HistoryView::UserpicSuggestion>(
message,
_chat,
_photo,
st::msgServicePhotoWidth));
}
return std::make_unique<HistoryView::Photo>(
message,
_chat,
@ -1907,7 +1918,9 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
return std::make_unique<HistoryView::MediaGift>(message, this);
return std::make_unique<HistoryView::ServiceBox>(
message,
std::make_unique<HistoryView::PremiumGift>(message, this));
}
bool MediaGiftBox::activated() const {

View File

@ -285,6 +285,9 @@ enum class MessageFlag : uint64 {
// Optimization for item text custom emoji repainting.
CustomEmojiRepainting = (1ULL << 32),
// Profile photo suggestion, views have special media type.
IsUserpicSuggestion = (1ULL << 33),
};
inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>;

View File

@ -298,6 +298,10 @@ bool UserData::applyMinPhoto() const {
return !(flags() & UserDataFlag::DiscardMinPhoto);
}
bool UserData::hasPersonalPhoto() const {
return (flags() & UserDataFlag::PersonalPhoto);
}
bool UserData::canAddContact() const {
return canShareThisContact() && !isContact();
}

View File

@ -114,6 +114,7 @@ public:
[[nodiscard]] bool isInaccessible() const;
[[nodiscard]] bool canWrite() const;
[[nodiscard]] bool applyMinPhoto() const;
[[nodiscard]] bool hasPersonalPhoto() const;
[[nodiscard]] bool canShareThisContact() const;
[[nodiscard]] bool canAddContact() const;

View File

@ -123,7 +123,8 @@ void PrepareProfilePhoto(
.cropType = (radius == ImageRoundRadius::Ellipse
? EditorData::CropType::Ellipse
: EditorData::CropType::RoundedRect),
.keepAspectRatio = true, }),
.keepAspectRatio = true,
}),
Ui::LayerOption::KeepOther);
}

View File

@ -1318,6 +1318,10 @@ bool HistoryItem::skipNotification() const {
return false;
}
bool HistoryItem::isUserpicSuggestion() const {
return (_flags & MessageFlag::IsUserpicSuggestion);
}
void HistoryItem::destroy() {
_history->destroyMessage(this);
}
@ -3872,12 +3876,20 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
auto prepareSuggestProfilePhoto = [this](const MTPDmessageActionSuggestProfilePhoto &action) {
auto result = PreparedServiceText{};
result.links.push_back(fromLink());
result.text = tr::lng_action_changed_photo(
tr::now,
lt_from,
fromLinkText(), // Link 1.
Ui::Text::WithEntities);
const auto isSelf = (_from->id == _from->session().userPeerId());
const auto peer = isSelf ? history()->peer : _from;
const auto user = peer->asUser();
const auto name = (user && !user->firstName.isEmpty())
? user->firstName
: peer->name();
result.links.push_back(peer->createOpenLink());
result.text = (isSelf
? tr::lng_action_suggested_photo_me
: tr::lng_action_suggested_photo)(
tr::now,
lt_user,
Ui::Text::Link(name, 1), // Link 1.
Ui::Text::WithEntities);
return result;
};
@ -4009,6 +4021,15 @@ void HistoryItem::applyAction(const MTPMessageAction &action) {
this,
_from,
data.vmonths().v);
}, [&](const MTPDmessageActionSuggestProfilePhoto &data) {
data.vphoto().match([&](const MTPDphoto &photo) {
_flags |= MessageFlag::IsUserpicSuggestion;
_media = std::make_unique<Data::MediaPhoto>(
this,
history()->peer,
history()->owner().processPhoto(photo));
}, [](const MTPDphotoEmpty &) {
});
}, [](const auto &) {
});
}

View File

@ -199,6 +199,7 @@ public:
[[nodiscard]] bool isScheduled() const;
[[nodiscard]] bool isSponsored() const;
[[nodiscard]] bool skipNotification() const;
[[nodiscard]] bool isUserpicSuggestion() const;
void addLogEntryOriginal(
WebPageId localId,

View File

@ -427,8 +427,10 @@ QSize Service::performCountCurrentSize(int newWidth) {
if (isHidden()) {
return { newWidth, newHeight };
}
if (!text().isEmpty()) {
const auto media = this->media();
if (media && data()->isUserpicSuggestion()) {
newHeight = st::msgServiceMargin.top() + media->resizeGetHeight(newWidth) + st::msgServiceMargin.bottom();
} else if (!text().isEmpty()) {
auto contentWidth = newWidth;
if (delegate()->elementIsChatWide()) {
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
@ -443,7 +445,7 @@ QSize Service::performCountCurrentSize(int newWidth) {
? minHeight()
: textHeightFor(nwidth);
newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom();
if (const auto media = this->media()) {
if (media) {
newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth());
}
}
@ -454,11 +456,14 @@ QSize Service::performCountCurrentSize(int newWidth) {
QSize Service::performCountOptimalSize() {
validateText();
auto maxWidth = text().maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right();
auto minHeight = text().minHeight();
if (const auto media = this->media()) {
media->initDimensions();
if (data()->isUserpicSuggestion()) {
return { media->maxWidth(), media->minHeight() };
}
}
auto maxWidth = text().maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right();
auto minHeight = text().minHeight();
return { maxWidth, minHeight };
}
@ -519,40 +524,42 @@ void Service::draw(Painter &p, const PaintContext &context) const {
p.setTextPalette(st->serviceTextPalette());
const auto media = this->media();
if (media) {
height -= margin.top() + media->height();
const auto onlyMedia = (media && data()->isUserpicSuggestion());
if (!onlyMedia) {
if (media) {
height -= margin.top() + media->height();
}
const auto trect = QRect(g.left(), margin.top(), g.width(), height)
- st::msgServicePadding;
ServiceMessagePainter::PaintComplexBubble(
p,
context.st,
g.left(),
g.width(),
text(),
trect);
p.setBrush(Qt::NoBrush);
p.setPen(st->msgServiceFg());
p.setFont(st::msgServiceFont);
prepareCustomEmojiPaint(p, context, text());
text().draw(p, {
.position = trect.topLeft(),
.availableWidth = trect.width(),
.align = style::al_top,
.palette = &st->serviceTextPalette(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,
.paused = context.paused,
.selection = context.selection,
.fullWidthSelection = false,
});
}
const auto trect = QRect(g.left(), margin.top(), g.width(), height)
- st::msgServicePadding;
ServiceMessagePainter::PaintComplexBubble(
p,
context.st,
g.left(),
g.width(),
text(),
trect);
p.setBrush(Qt::NoBrush);
p.setPen(st->msgServiceFg());
p.setFont(st::msgServiceFont);
prepareCustomEmojiPaint(p, context, text());
text().draw(p, {
.position = trect.topLeft(),
.availableWidth = trect.width(),
.align = style::al_top,
.palette = &st->serviceTextPalette(),
.spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now,
.paused = context.paused,
.selection = context.selection,
.fullWidthSelection = false,
});
if (media) {
const auto left = margin.left() + (g.width() - media->maxWidth()) / 2;
const auto top = margin.top() + height + margin.top();
const auto top = margin.top() + (onlyMedia ? 0 : (height + margin.top()));
p.translate(left, top);
media->draw(p, context.translated(-left, -top).withSelection({}));
p.translate(-left, -top);
@ -591,6 +598,7 @@ PointState Service::pointState(QPoint point) const {
TextState Service::textState(QPoint point, StateRequest request) const {
const auto item = data();
const auto media = this->media();
const auto onlyMedia = (media && data()->isUserpicSuggestion());
auto result = TextState(item);
@ -609,7 +617,9 @@ TextState Service::textState(QPoint point, StateRequest request) const {
g.setHeight(g.height() - unreadbarh);
}
if (media) {
if (onlyMedia) {
return media->textState(point - QPoint(st::msgServiceMargin.left() + (g.width() - media->maxWidth()) / 2, st::msgServiceMargin.top()), request);
} else if (media) {
g.setHeight(g.height() - (st::msgServiceMargin.top() + media->height()));
}
auto trect = g.marginsAdded(-st::msgServicePadding);

View File

@ -176,6 +176,10 @@ Storage::SharedMediaTypesMask Media::sharedMediaTypes() const {
return {};
}
not_null<Element*> Media::parent() const {
return _parent;
}
not_null<History*> Media::history() const {
return _parent->history();
}

View File

@ -79,6 +79,7 @@ public:
explicit Media(not_null<Element*> parent) : _parent(parent) {
}
[[nodiscard]] not_null<Element*> parent() const;
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] virtual TextForMimeData selectedText(

View File

@ -59,6 +59,7 @@ public:
PhotoData *getPhoto() const override {
return _data;
}
void showPhoto(FullMsgId id);
QSize sizeForGroupingOptimal(int maxWidth) const override;
QSize sizeForGrouping(int width) const override;
@ -103,8 +104,6 @@ protected:
private:
struct Streamed;
void showPhoto(FullMsgId id);
void create(FullMsgId contextId, PeerData *chat = nullptr);
void playAnimation(bool autoplay) override;

View File

@ -0,0 +1,127 @@
/*
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 "history/view/media/history_view_premium_gift.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_document.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/settings_premium.h" // Settings::ShowGiftPremium
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
namespace HistoryView {
namespace {
[[nodiscard]] QString FormatGiftMonths(int months) {
return (months < 12)
? tr::lng_premium_gift_duration_months(tr::now, lt_count, months)
: tr::lng_premium_gift_duration_years(
tr::now,
lt_count,
std::round(months / 12.));
}
} // namespace
PremiumGift::PremiumGift(
not_null<Element*> parent,
not_null<Data::MediaGiftBox*> gift)
: _parent(parent)
, _gift(gift) {
}
PremiumGift::~PremiumGift() = default;
int PremiumGift::top() {
return st::msgServiceGiftBoxStickerTop;
}
QSize PremiumGift::size() {
return st::msgServiceGiftBoxStickerSize;
}
QString PremiumGift::title() {
return tr::lng_premium_summary_title(tr::now);
}
QString PremiumGift::subtitle() {
return FormatGiftMonths(_gift->months());
}
ClickHandlerPtr PremiumGift::createViewLink() {
const auto from = _gift->from();
const auto to = _parent->history()->peer;
const auto months = _gift->months();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
const auto me = (from->id == controller->session().userPeerId());
const auto peer = me ? to : from;
Settings::ShowGiftPremium(controller, peer, months, me);
}
});
}
void PremiumGift::draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) {
if (_sticker) {
_sticker->draw(p, context, geometry);
} else {
ensureStickerCreated();
}
}
void PremiumGift::stickerClearLoopPlayed() {
if (_sticker) {
_sticker->stickerClearLoopPlayed();
}
}
std::unique_ptr<StickerPlayer> PremiumGift::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker
? _sticker->stickerTakePlayer(data, replacements)
: nullptr;
}
bool PremiumGift::hasHeavyPart() {
return (_sticker ? _sticker->hasHeavyPart() : false);
}
void PremiumGift::unloadHeavyPart() {
if (_sticker) {
_sticker->unloadHeavyPart();
}
}
void PremiumGift::ensureStickerCreated() const {
if (_sticker) {
return;
}
const auto &session = _parent->history()->session();
auto &packs = session.giftBoxStickersPacks();
if (const auto document = packs.lookup(_gift->months())) {
if (const auto sticker = document->sticker()) {
const auto skipPremiumEffect = false;
_sticker.emplace(_parent, document, skipPremiumEffect, _parent);
_sticker->setDiceIndex(sticker->alt, 1);
_sticker->setGiftBoxSticker(true);
_sticker->initSize();
}
}
}
} // namespace HistoryView

View File

@ -0,0 +1,53 @@
/*
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
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_service_box.h"
namespace Data {
class MediaGiftBox;
} // namespace Data
namespace HistoryView {
class PremiumGift final : public ServiceBoxContent {
public:
PremiumGift(
not_null<Element*> parent,
not_null<Data::MediaGiftBox*> gift);
~PremiumGift();
int top() override;
QSize size() override;
QString title() override;
QString subtitle() override;
void draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) override;
ClickHandlerPtr createViewLink() override;
void stickerClearLoopPlayed() override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
private:
void ensureStickerCreated() const;
const not_null<Element*> _parent;
const not_null<Data::MediaGiftBox*> _gift;
mutable std::optional<Sticker> _sticker;
};
} // namespace HistoryView

View File

@ -0,0 +1,240 @@
/*
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 "history/view/media/history_view_service_box.h"
//
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "lang/lang_keys.h"
#include "ui/chat/chat_style.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "styles/style_chat.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
namespace HistoryView {
ServiceBox::ServiceBox(
not_null<Element*> parent,
std::unique_ptr<ServiceBoxContent> content)
: Media(parent)
, _parent(parent)
, _content(std::move(content))
, _button([&] {
auto result = Button();
result.repaint = [=] { repaint(); };
result.text.setText(
st::semiboldTextStyle,
tr::lng_sticker_premium_view(tr::now));
const auto height = st::msgServiceGiftBoxButtonHeight;
const auto &padding = st::msgServiceGiftBoxButtonPadding;
result.size = QSize(
result.text.maxWidth()
+ height
+ padding.left()
+ padding.right(),
height);
result.link = _content->createViewLink();
return result;
}())
, _maxWidth(st::msgServiceGiftBoxSize.width()
- st::msgPadding.left()
- st::msgPadding.right())
, _title(
st::settingsSubsectionTitle.style,
_content->title(),
kDefaultTextOptions,
_maxWidth)
, _subtitle(
st::premiumPreviewAbout.style,
_content->subtitle(),
kDefaultTextOptions,
_maxWidth)
, _size(
st::msgServiceGiftBoxSize.width(),
(st::msgServiceGiftBoxTopSkip
+ _content->top()
+ _content->size().height()
+ st::msgServiceGiftBoxTitlePadding.top()
+ _title.countHeight(_maxWidth)
+ st::msgServiceGiftBoxTitlePadding.bottom()
+ _subtitle.countHeight(_maxWidth)
+ st::msgServiceGiftBoxButtonMargins.top()
+ _button.size.height()
+ st::msgServiceGiftBoxButtonMargins.bottom()))
, _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) {
}
ServiceBox::~ServiceBox() = default;
QSize ServiceBox::countOptimalSize() {
return _size;
}
QSize ServiceBox::countCurrentSize(int newWidth) {
return _size;
}
void ServiceBox::draw(Painter &p, const PaintContext &context) const {
p.translate(0, st::msgServiceGiftBoxTopSkip);
PainterHighQualityEnabler hq(p);
const auto radius = st::msgServiceGiftBoxRadius;
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
p.drawRoundedRect(QRect(QPoint(), _innerSize), radius, radius);
const auto content = contentRect();
auto top = content.top() + content.height();
{
p.setPen(context.st->msgServiceFg());
const auto &padding = st::msgServiceGiftBoxTitlePadding;
top += padding.top();
_title.draw(p, st::msgPadding.left(), top, _maxWidth, style::al_top);
top += _title.countHeight(_maxWidth) + padding.bottom();
_subtitle.draw(p, st::msgPadding.left(), top, _maxWidth, style::al_top);
top += _subtitle.countHeight(_maxWidth) + padding.bottom();
}
{
const auto position = buttonRect().topLeft();
p.translate(position);
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg()); // ?
_button.drawBg(p);
p.setPen(context.st->msgServiceFg());
if (_button.ripple) {
const auto opacity = p.opacity();
p.setOpacity(st::historyPollRippleOpacity);
_button.ripple->paint(
p,
0,
0,
width(),
&context.messageStyle()->msgWaveformInactive->c);
p.setOpacity(opacity);
}
_button.text.draw(
p,
0,
(_button.size.height() - _button.text.minHeight()) / 2,
_button.size.width(),
style::al_top);
p.translate(-position);
}
_content->draw(p, context, content);
p.translate(0, -st::msgServiceGiftBoxTopSkip);
}
TextState ServiceBox::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
{
const auto rect = buttonRect();
if (rect.contains(point)) {
result.link = _button.link;
_button.lastPoint = point - rect.topLeft();
}
}
return result;
}
bool ServiceBox::toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const {
return false;
}
bool ServiceBox::dragItemByHandler(const ClickHandlerPtr &p) const {
return false;
}
void ServiceBox::clickHandlerPressedChanged(
const ClickHandlerPtr &handler,
bool pressed) {
if (!handler) {
return;
}
if (handler == _button.link) {
_button.toggleRipple(pressed);
}
}
void ServiceBox::stickerClearLoopPlayed() {
_content->stickerClearLoopPlayed();
}
std::unique_ptr<StickerPlayer> ServiceBox::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _content->stickerTakePlayer(data, replacements);
}
bool ServiceBox::needsBubble() const {
return false;
}
bool ServiceBox::customInfoLayout() const {
return false;
}
bool ServiceBox::hasHeavyPart() const {
return _content->hasHeavyPart();
}
void ServiceBox::unloadHeavyPart() {
_content->unloadHeavyPart();
}
QRect ServiceBox::buttonRect() const {
const auto &padding = st::msgServiceGiftBoxButtonMargins;
const auto position = QPoint(
(width() - _button.size.width()) / 2,
height() - padding.bottom() - _button.size.height());
return QRect(position, _button.size);
}
QRect ServiceBox::contentRect() const {
const auto size = _content->size();
const auto top = _content->top();
return QRect(QPoint((width() - size.width()) / 2, top), size);
}
void ServiceBox::Button::toggleRipple(bool pressed) {
if (pressed) {
const auto linkWidth = size.width();
const auto linkHeight = size.height();
if (!ripple) {
const auto drawMask = [&](QPainter &p) { drawBg(p); };
auto mask = Ui::RippleAnimation::MaskByDrawer(
QSize(linkWidth, linkHeight),
false,
drawMask);
ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
std::move(mask),
repaint);
}
ripple->add(lastPoint);
} else if (ripple) {
ripple->lastStop();
}
}
void ServiceBox::Button::drawBg(QPainter &p) const {
const auto radius = size.height() / 2.;
p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);
}
} // namespace HistoryView

View File

@ -8,12 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_media_unwrapped.h"
#include "history/view/media/history_view_sticker.h"
namespace Data {
class MediaGiftBox;
} // namespace Data
namespace Ui {
class RippleAnimation;
@ -21,10 +15,35 @@ class RippleAnimation;
namespace HistoryView {
class MediaGift final : public Media {
class ServiceBoxContent {
public:
MediaGift(not_null<Element*> parent, not_null<Data::MediaGiftBox*> gift);
~MediaGift();
virtual ~ServiceBoxContent() = default;
[[nodiscard]] virtual int top() = 0;
[[nodiscard]] virtual QSize size() = 0;
[[nodiscard]] virtual QString title() = 0;
[[nodiscard]] virtual QString subtitle() = 0;
virtual void draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) = 0;
[[nodiscard]] virtual ClickHandlerPtr createViewLink() = 0;
virtual void stickerClearLoopPlayed() = 0;
[[nodiscard]] virtual std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) = 0;
[[nodiscard]] virtual bool hasHeavyPart() = 0;
virtual void unloadHeavyPart() = 0;
};
class ServiceBox final : public Media {
public:
ServiceBox(
not_null<Element*> parent,
std::unique_ptr<ServiceBoxContent> content);
~ServiceBox();
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
@ -53,14 +72,11 @@ public:
void unloadHeavyPart() override;
private:
void ensureStickerCreated() const;
[[nodiscard]] QRect buttonRect() const;
[[nodiscard]] QRect stickerRect() const;
[[nodiscard]] QRect contentRect() const;
const not_null<Element*> _parent;
const not_null<Data::MediaGiftBox*> _gift;
const QSize &_size;
const QSize _innerSize;
const std::unique_ptr<ServiceBoxContent> _content;
struct Button {
void drawBg(QPainter &p) const;
@ -77,10 +93,11 @@ private:
mutable QPoint lastPoint;
} _button;
const int _maxWidth = 0;
Ui::Text::String _title;
Ui::Text::String _subtitle;
mutable std::optional<Sticker> _sticker;
const QSize _size;
const QSize _innerSize;
};

View File

@ -1,282 +0,0 @@
/*
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 "history/view/media/history_view_service_media_gift.h"
#include "chat_helpers/stickers_gift_box_pack.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_document.h"
#include "data/data_media_types.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/history_item_components.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_element.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_common.h"
#include "lottie/lottie_single_player.h"
#include "main/main_session.h"
#include "settings/settings_premium.h" // Settings::ShowGiftPremium
#include "ui/chat/chat_style.h"
#include "ui/effects/ripple_animation.h"
#include "ui/painter.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
namespace HistoryView {
namespace {
[[nodiscard]] QString FormatGiftMonths(int months) {
return (months < 12)
? tr::lng_premium_gift_duration_months(tr::now, lt_count, months)
: tr::lng_premium_gift_duration_years(
tr::now,
lt_count,
std::round(months / 12.));
}
} // namespace
MediaGift::MediaGift(
not_null<Element*> parent,
not_null<Data::MediaGiftBox*> gift)
: Media(parent)
, _parent(parent)
, _gift(gift)
, _size(st::msgServiceGiftBoxSize)
, _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip))
, _button([&] {
auto result = Button();
result.repaint = [=] { repaint(); };
result.text.setText(
st::semiboldTextStyle,
tr::lng_sticker_premium_view(tr::now));
const auto height = st::msgServiceGiftBoxButtonHeight;
const auto &margins = st::msgServiceGiftBoxButtonMargins;
result.size = QSize(
result.text.maxWidth()
+ height
+ margins.left()
+ margins.right(),
height);
const auto from = _gift->from();
const auto to = _parent->history()->peer;
const auto months = _gift->months();
result.link = std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
const auto me = (from->id == controller->session().userPeerId());
Settings::ShowGiftPremium(controller, me ? to : from, months, me);
}
});
return result;
}())
, _title(
st::settingsSubsectionTitle.style,
tr::lng_premium_summary_title(tr::now))
, _subtitle(
st::premiumPreviewAbout.style,
FormatGiftMonths(gift->months())) {
}
MediaGift::~MediaGift() = default;
QSize MediaGift::countOptimalSize() {
return _size;
}
QSize MediaGift::countCurrentSize(int newWidth) {
return _size;
}
void MediaGift::draw(Painter &p, const PaintContext &context) const {
p.translate(0, st::msgServiceGiftBoxTopSkip);
PainterHighQualityEnabler hq(p);
const auto radius = st::msgServiceGiftBoxRadius;
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg());
p.drawRoundedRect(QRect(QPoint(), _innerSize), radius, radius);
{
p.setPen(context.st->msgServiceFg());
const auto &padding = st::msgServiceGiftBoxTitlePadding;
const auto titleTop = padding.top();
_title.draw(p, 0, titleTop, _innerSize.width(), style::al_top);
const auto subtitleTop = titleTop
+ _title.minHeight()
+ padding.bottom();
_subtitle.draw(p, 0, subtitleTop, _innerSize.width(), style::al_top);
}
{
const auto position = buttonRect().topLeft();
p.translate(position);
p.setPen(Qt::NoPen);
p.setBrush(context.st->msgServiceBg()); // ?
_button.drawBg(p);
p.setPen(context.st->msgServiceFg());
if (_button.ripple) {
const auto opacity = p.opacity();
p.setOpacity(st::historyPollRippleOpacity);
_button.ripple->paint(
p,
0,
0,
width(),
&context.messageStyle()->msgWaveformInactive->c);
p.setOpacity(opacity);
}
_button.text.draw(
p,
0,
(_button.size.height() - _button.text.minHeight()) / 2,
_button.size.width(),
style::al_top);
p.translate(-position);
}
if (_sticker) {
_sticker->draw(p, context, stickerRect());
} else {
ensureStickerCreated();
}
p.translate(0, -st::msgServiceGiftBoxTopSkip);
}
TextState MediaGift::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
{
const auto rect = buttonRect();
if (rect.contains(point)) {
result.link = _button.link;
_button.lastPoint = point - rect.topLeft();
}
}
return result;
}
bool MediaGift::toggleSelectionByHandlerClick(
const ClickHandlerPtr &p) const {
return false;
}
bool MediaGift::dragItemByHandler(const ClickHandlerPtr &p) const {
return false;
}
void MediaGift::clickHandlerPressedChanged(
const ClickHandlerPtr &handler,
bool pressed) {
if (!handler) {
return;
}
if (handler == _button.link) {
_button.toggleRipple(pressed);
}
}
void MediaGift::stickerClearLoopPlayed() {
if (_sticker) {
_sticker->stickerClearLoopPlayed();
}
}
std::unique_ptr<StickerPlayer> MediaGift::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return _sticker
? _sticker->stickerTakePlayer(data, replacements)
: nullptr;
}
bool MediaGift::needsBubble() const {
return false;
}
bool MediaGift::customInfoLayout() const {
return false;
}
bool MediaGift::hasHeavyPart() const {
return (_sticker ? _sticker->hasHeavyPart() : false);
}
void MediaGift::unloadHeavyPart() {
if (_sticker) {
_sticker->unloadHeavyPart();
}
}
void MediaGift::ensureStickerCreated() const {
if (_sticker) {
return;
}
const auto &session = _parent->history()->session();
auto &packs = session.giftBoxStickersPacks();
if (const auto document = packs.lookup(_gift->months())) {
if (const auto sticker = document->sticker()) {
const auto skipPremiumEffect = false;
_sticker.emplace(_parent, document, skipPremiumEffect, _parent);
_sticker->setDiceIndex(sticker->alt, 1);
_sticker->setGiftBoxSticker(true);
_sticker->initSize();
}
}
}
QRect MediaGift::buttonRect() const {
const auto &padding = st::msgServiceGiftBoxButtonPadding;
const auto position = QPoint(
(width() - _button.size.width()) / 2,
height() - padding.bottom() - _button.size.height());
return QRect(position, _button.size);
}
QRect MediaGift::stickerRect() const {
const auto &size = st::msgServiceGiftBoxStickerSize;
const auto top = st::msgServiceGiftBoxStickerTop;
return QRect(QPoint((width() - size.width()) / 2, top), size);
}
void MediaGift::Button::toggleRipple(bool pressed) {
if (pressed) {
const auto linkWidth = size.width();
const auto linkHeight = size.height();
if (!ripple) {
const auto drawMask = [&](QPainter &p) { drawBg(p); };
auto mask = Ui::RippleAnimation::MaskByDrawer(
QSize(linkWidth, linkHeight),
false,
drawMask);
ripple = std::make_unique<Ui::RippleAnimation>(
st::defaultRippleAnimation,
std::move(mask),
repaint);
}
ripple->add(lastPoint);
} else if (ripple) {
ripple->lastStop();
}
}
void MediaGift::Button::drawBg(QPainter &p) const {
const auto radius = size.height() / 2.;
p.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);
}
} // namespace HistoryView

View File

@ -0,0 +1,129 @@
/*
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 "history/view/media/history_view_userpic_suggestion.h"
#include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_document.h"
#include "data/data_photo.h"
#include "data/data_user.h"
#include "data/data_photo_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_session.h"
#include "editor/photo_editor_layer_widget.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "history/view/history_view_element.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "window/window_session_controller.h"
#include "ui/painter.h"
#include "mainwidget.h"
#include "apiwrap.h"
#include "api/api_peer_photo.h"
#include "styles/style_chat.h"
namespace HistoryView {
UserpicSuggestion::UserpicSuggestion(
not_null<Element*> parent,
not_null<PeerData*> chat,
not_null<PhotoData*> photo,
int width)
: _photo(parent, chat, photo, width) {
_photo.initDimensions();
_photo.resizeGetHeight(_photo.maxWidth());
}
UserpicSuggestion::~UserpicSuggestion() = default;
int UserpicSuggestion::top() {
return st::msgServiceGiftBoxButtonMargins.top();
}
QSize UserpicSuggestion::size() {
return { _photo.maxWidth(), _photo.minHeight() };
}
QString UserpicSuggestion::title() {
return tr::lng_action_suggested_photo_title(tr::now);
}
QString UserpicSuggestion::subtitle() {
return _photo.parent()->data()->notificationText().text;
}
ClickHandlerPtr UserpicSuggestion::createViewLink() {
const auto out = _photo.parent()->data()->out();
const auto photo = _photo.getPhoto();
const auto itemId = _photo.parent()->data()->fullId();
const auto show = crl::guard(&_photo, [=](FullMsgId id) {
_photo.showPhoto(id);
});
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
const auto media = photo->activeMediaView();
if (media->loaded()) {
if (out) {
PhotoOpenClickHandler(photo, show, itemId).onClick(context);
} else {
const auto original = std::make_shared<QImage>(
media->image(Data::PhotoSize::Large)->original());
const auto callback = [=](QImage &&image) {
const auto session = &photo->session();
const auto user = session->user();
auto &peerPhotos = session->api().peerPhoto();
if (original->size() == image.size()
&& original->constBits() == image.constBits()) {
peerPhotos.updateSelf(photo);
} else {
peerPhotos.upload(user, std::move(image));
}
};
Editor::PrepareProfilePhoto(
controller->content(),
&controller->window(),
ImageRoundRadius::Ellipse,
callback,
base::duplicate(*original));
}
} else if (!photo->loading()) {
PhotoSaveClickHandler(photo, itemId).onClick(context);
}
}
});
}
void UserpicSuggestion::draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) {
p.translate(geometry.topLeft());
_photo.draw(p, context);
p.translate(-geometry.topLeft());
}
void UserpicSuggestion::stickerClearLoopPlayed() {
}
std::unique_ptr<StickerPlayer> UserpicSuggestion::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return nullptr;
}
bool UserpicSuggestion::hasHeavyPart() {
return _photo.hasHeavyPart();
}
void UserpicSuggestion::unloadHeavyPart() {
_photo.unloadHeavyPart();
}
} // namespace HistoryView

View File

@ -0,0 +1,53 @@
/*
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
#include "history/view/media/history_view_media.h"
#include "history/view/media/history_view_media_unwrapped.h"
#include "history/view/media/history_view_photo.h"
#include "history/view/media/history_view_service_box.h"
namespace Data {
class MediaGiftBox;
} // namespace Data
namespace HistoryView {
class UserpicSuggestion final : public ServiceBoxContent {
public:
UserpicSuggestion(
not_null<Element*> parent,
not_null<PeerData*> chat,
not_null<PhotoData*> photo,
int width);
~UserpicSuggestion();
int top() override;
QSize size() override;
QString title() override;
QString subtitle() override;
void draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) override;
ClickHandlerPtr createViewLink() override;
void stickerClearLoopPlayed() override;
std::unique_ptr<StickerPlayer> stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) override;
bool hasHeavyPart() override;
void unloadHeavyPart() override;
private:
Photo _photo;
};
} // namespace HistoryView

View File

@ -1242,9 +1242,9 @@ msgServiceGiftBoxSize: size(206px, 231px); // Plus msgServiceGiftBoxTopSkip.
msgServiceGiftBoxRadius: 12px;
msgServiceGiftBoxTopSkip: 4px;
msgServiceGiftBoxButtonHeight: 32px;
msgServiceGiftBoxButtonMargins: margins(2px, 0px, 2px, 0px);
msgServiceGiftBoxButtonPadding: margins(0px, 17px, 0px, 17px);
msgServiceGiftBoxTitlePadding: margins(0px, 126px, 0px, 2px);
msgServiceGiftBoxButtonPadding: margins(2px, 0px, 2px, 0px);
msgServiceGiftBoxButtonMargins: margins(0px, 13px, 0px, 17px);
msgServiceGiftBoxTitlePadding: margins(0px, 5px, 0px, 2px);
msgServiceGiftBoxStickerTop: -19px;
msgServiceGiftBoxStickerSize: size(140px, 140px);

View File

@ -317,7 +317,7 @@ void UserpicButton::choosePhotoLocally() {
_menu->addAction(
tr::lng_profile_suggest_photo(tr::now, lt_user, name),
[=] { chooseFile(ChosenType::Suggest); });
if (user->flags() & UserDataFlag::PersonalPhoto) {
if (user->hasPersonalPhoto()) {
_menu->addAction(
tr::lng_profile_photo_reset(tr::now),
[=] { user->session().api().peerPhoto().clearPersonal(