Show suggested service info.

This commit is contained in:
John Preston 2025-06-19 23:19:22 +04:00
parent bf9492e083
commit ebce4d0f31
9 changed files with 209 additions and 64 deletions

View file

@ -4436,7 +4436,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_suggest_action_your" = "You suggest to post this message."; "lng_suggest_action_your" = "You suggest to post this message.";
"lng_suggest_action_his" = "{from} suggests to post this message."; "lng_suggest_action_his" = "{from} suggests to post this message.";
"lng_suggest_action_price_label" = "Price"; "lng_suggest_action_price_label" = "Price";
"lng_suggest_action_price_free" = "Free";
"lng_suggest_action_time_label" = "Time"; "lng_suggest_action_time_label" = "Time";
"lng_suggest_action_time_any" = "Anytime";
"lng_suggest_action_agreement" = "Agreement reached!"; "lng_suggest_action_agreement" = "Agreement reached!";
"lng_suggest_action_agree_date" = "The post will be automatically published on {channel} {date}."; "lng_suggest_action_agree_date" = "The post will be automatically published on {channel} {date}.";
"lng_suggest_action_your_charged" = "You have been charged {amount}."; "lng_suggest_action_your_charged" = "You have been charged {amount}.";

View file

@ -602,7 +602,8 @@ void MonoforumSenderBar::Paint(
void ServicePreMessage::init( void ServicePreMessage::init(
PreparedServiceText string, PreparedServiceText string,
ClickHandlerPtr fullClickHandler) { ClickHandlerPtr fullClickHandler,
std::unique_ptr<Media> media) {
text = Ui::Text::String( text = Ui::Text::String(
st::serviceTextStyle, st::serviceTextStyle,
string.text, string.text,
@ -612,6 +613,7 @@ void ServicePreMessage::init(
for (auto i = 0; i != int(string.links.size()); ++i) { for (auto i = 0; i != int(string.links.size()); ++i) {
text.setLink(i + 1, string.links[i]); text.setLink(i + 1, string.links[i]);
} }
this->media = std::move(media);
} }
int ServicePreMessage::resizeToWidth(int newWidth, ElementChatMode mode) { int ServicePreMessage::resizeToWidth(int newWidth, ElementChatMode mode) {
@ -621,6 +623,15 @@ int ServicePreMessage::resizeToWidth(int newWidth, ElementChatMode mode) {
width, width,
st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
} }
if (media) {
media->initDimensions();
media->resizeGetHeight(width);
}
if (media && media->hideServiceText()) {
height = media->height() + st::msgServiceMargin.bottom();
} else {
auto contentWidth = width; auto contentWidth = width;
contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.right(); contentWidth -= st::msgServiceMargin.left() + st::msgServiceMargin.right();
if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) { if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
@ -642,6 +653,8 @@ int ServicePreMessage::resizeToWidth(int newWidth, ElementChatMode mode) {
+ st::msgServicePadding.bottom() + st::msgServicePadding.bottom()
+ st::msgServiceMargin.top() + st::msgServiceMargin.top()
+ st::msgServiceMargin.bottom(); + st::msgServiceMargin.bottom();
}
return height; return height;
} }
@ -650,6 +663,14 @@ void ServicePreMessage::paint(
const PaintContext &context, const PaintContext &context,
QRect g, QRect g,
ElementChatMode mode) const { ElementChatMode mode) const {
if (media && media->hideServiceText()) {
const auto left = (width - media->width()) / 2;
const auto top = g.top() - height - st::msgMargin.bottom();
const auto position = QPoint(left, top);
p.translate(position);
media->draw(p, context.translated(-position).withSelection({}));
p.translate(-position);
} else {
const auto top = g.top() - height - st::msgMargin.top(); const auto top = g.top() - height - st::msgMargin.top();
p.translate(0, top); p.translate(0, top);
@ -679,12 +700,16 @@ void ServicePreMessage::paint(
}); });
p.translate(0, -top); p.translate(0, -top);
}
} }
ClickHandlerPtr ServicePreMessage::textState( ClickHandlerPtr ServicePreMessage::textState(
QPoint point, QPoint point,
const StateRequest &request, const StateRequest &request,
QRect g) const { QRect g) const {
if (media && media->hideServiceText()) {
return {};
}
const auto top = g.top() - height - st::msgMargin.top(); const auto top = g.top() - height - st::msgMargin.top();
const auto rect = QRect(0, top, width, height) const auto rect = QRect(0, top, width, height)
- st::msgServiceMargin; - st::msgServiceMargin;
@ -1082,6 +1107,7 @@ void Element::refreshMedia(Element *replacing) {
.maxWidth = st::chatSuggestInfoWidth, .maxWidth = st::chatSuggestInfoWidth,
.serviceLink = decision->lnk, .serviceLink = decision->lnk,
.service = true, .service = true,
.fullAreaLink = true,
.hideServiceText = true, .hideServiceText = true,
}); });
} else { } else {
@ -1639,11 +1665,15 @@ void Element::setDisplayDate(bool displayDate) {
void Element::setServicePreMessage( void Element::setServicePreMessage(
PreparedServiceText text, PreparedServiceText text,
ClickHandlerPtr fullClickHandler) { ClickHandlerPtr fullClickHandler,
if (!text.text.empty()) { std::unique_ptr<Media> media) {
if (!text.text.empty() || media) {
AddComponents(ServicePreMessage::Bit()); AddComponents(ServicePreMessage::Bit());
const auto service = Get<ServicePreMessage>(); const auto service = Get<ServicePreMessage>();
service->init(std::move(text), std::move(fullClickHandler)); service->init(
std::move(text),
std::move(fullClickHandler),
std::move(media));
setPendingResize(); setPendingResize();
} else if (Has<ServicePreMessage>()) { } else if (Has<ServicePreMessage>()) {
RemoveComponents(ServicePreMessage::Bit()); RemoveComponents(ServicePreMessage::Bit());

View file

@ -309,7 +309,10 @@ private:
// Any HistoryView::Element can have this Component for // Any HistoryView::Element can have this Component for
// displaying some text in layout of a service message above the message. // displaying some text in layout of a service message above the message.
struct ServicePreMessage : RuntimeComponent<ServicePreMessage, Element> { struct ServicePreMessage : RuntimeComponent<ServicePreMessage, Element> {
void init(PreparedServiceText string, ClickHandlerPtr fullClickHandler); void init(
PreparedServiceText string,
ClickHandlerPtr fullClickHandler,
std::unique_ptr<Media> media = nullptr);
int resizeToWidth(int newWidth, ElementChatMode mode); int resizeToWidth(int newWidth, ElementChatMode mode);
@ -323,6 +326,7 @@ struct ServicePreMessage : RuntimeComponent<ServicePreMessage, Element> {
const StateRequest &request, const StateRequest &request,
QRect g) const; QRect g) const;
std::unique_ptr<Media> media;
Ui::Text::String text; Ui::Text::String text;
ClickHandlerPtr handler; ClickHandlerPtr handler;
int width = 0; int width = 0;
@ -459,7 +463,8 @@ public:
void setDisplayDate(bool displayDate); void setDisplayDate(bool displayDate);
void setServicePreMessage( void setServicePreMessage(
PreparedServiceText text, PreparedServiceText text,
ClickHandlerPtr fullClickHandler = nullptr); ClickHandlerPtr fullClickHandler = nullptr,
std::unique_ptr<Media> media = nullptr);
bool computeIsAttachToPrevious(not_null<Element*> previous); bool computeIsAttachToPrevious(not_null<Element*> previous);

View file

@ -14,7 +14,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item_helpers.h" #include "history/history_item_helpers.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_web_page.h" #include "history/view/media/history_view_web_page.h"
#include "history/view/media/history_view_suggest_decision.h"
#include "history/view/reactions/history_view_reactions.h" #include "history/view/reactions/history_view_reactions.h"
#include "history/view/reactions/history_view_reactions_button.h" #include "history/view/reactions/history_view_reactions_button.h"
#include "history/view/history_view_group_call_bar.h" // UserpicInRow. #include "history/view/history_view_group_call_bar.h" // UserpicInRow.
@ -458,17 +460,14 @@ void Message::initPaidInformation() {
if (!item->history()->peer->isUser()) { if (!item->history()->peer->isUser()) {
if (const auto suggest = item->Get<HistoryMessageSuggestedPost>()) { if (const auto suggest = item->Get<HistoryMessageSuggestedPost>()) {
auto text = PreparedServiceText(); setServicePreMessage({}, {}, std::make_unique<MediaGeneric>(
if (!suggest->stars && !suggest->date) { this,
text = { { u"suggestion to publish for free anytime"_q } }; GenerateSuggestRequestMedia(this, suggest),
} else if (!suggest->date) { MediaGenericDescriptor{
text = { { u"suggestion to publish for %1 stars anytime"_q.arg(suggest->stars) } }; .maxWidth = st::chatSuggestWidth,
} else if (!suggest->stars) { .service = true,
text = { { u"suggestion to publish for free %1"_q.arg(langDateTime(base::unixtime::parse(suggest->date))) } }; .hideServiceText = true,
} else { }));
text = { { u"suggestion to publish for %1 stars %2"_q.arg(suggest->stars).arg(langDateTime(base::unixtime::parse(suggest->date))) } };
}
setServicePreMessage(std::move(text));
} }
return; return;

View file

@ -79,6 +79,7 @@ MediaGeneric::MediaGeneric(
, _paintBg(std::move(descriptor.paintBg)) , _paintBg(std::move(descriptor.paintBg))
, _maxWidthCap(descriptor.maxWidth) , _maxWidthCap(descriptor.maxWidth)
, _service(descriptor.service) , _service(descriptor.service)
, _fullAreaLink(descriptor.fullAreaLink)
, _hideServiceText(descriptor.hideServiceText) { , _hideServiceText(descriptor.hideServiceText) {
generate(this, [&](std::unique_ptr<Part> part) { generate(this, [&](std::unique_ptr<Part> part) {
_entries.push_back({ _entries.push_back({
@ -157,6 +158,14 @@ TextState MediaGeneric::textState(
return result; return result;
} }
if (_fullAreaLink && QRect(0, 0, width(), height()).contains(point)) {
const auto link = _parent->data()->Get<HistoryServiceCustomLink>();
if (link) {
result.link = link->link;
return result;
}
}
for (const auto &entry : _entries) { for (const auto &entry : _entries) {
const auto raw = entry.object.get(); const auto raw = entry.object.get();
const auto height = raw->height(); const auto height = raw->height();

View file

@ -59,6 +59,7 @@ struct MediaGenericDescriptor {
not_null<const MediaGeneric*>)> paintBg; not_null<const MediaGeneric*>)> paintBg;
ClickHandlerPtr serviceLink; ClickHandlerPtr serviceLink;
bool service = false; bool service = false;
bool fullAreaLink = false;
bool hideServiceText = false; bool hideServiceText = false;
}; };
@ -130,6 +131,7 @@ private:
not_null<const MediaGeneric*>)> _paintBg; not_null<const MediaGeneric*>)> _paintBg;
int _maxWidthCap = 0; int _maxWidthCap = 0;
bool _service : 1 = false; bool _service : 1 = false;
bool _fullAreaLink : 1 = false;
bool _hideServiceText : 1 = false; bool _hideServiceText : 1 = false;
}; };

View file

@ -11,11 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "history/view/media/history_view_media_generic.h" #include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_unique_gift.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/chat/chat_style.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -24,6 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
namespace { namespace {
constexpr auto kFadedOpacity = 0.85;
enum EmojiType { enum EmojiType {
kAgreement, kAgreement,
kCalendar, kCalendar,
@ -114,12 +118,17 @@ auto GenerateSuggestDecisionMedia(
? st::chatSuggestInfoTitleMargin ? st::chatSuggestInfoTitleMargin
: st::chatSuggestInfoFullMargin)); : st::chatSuggestInfoFullMargin));
if (withComment) { if (withComment) {
pushText( const auto fadedFg = [](const PaintContext &context) {
auto result = context.st->msgServiceFg()->c;
result.setAlphaF(result.alphaF() * kFadedOpacity);
return result;
};
push(std::make_unique<TextPartColored>(
TextWithEntities().append('"').append( TextWithEntities().append('"').append(
decision->rejectComment decision->rejectComment
).append('"'), ).append('"'),
st::chatSuggestInfoLastMargin, st::chatSuggestInfoLastMargin,
style::al_top); fadedFg));
} }
} else { } else {
const auto stars = decision->stars; const auto stars = decision->stars;
@ -130,6 +139,7 @@ auto GenerateSuggestDecisionMedia(
), ),
st::chatSuggestInfoTitleMargin, st::chatSuggestInfoTitleMargin,
style::al_top); style::al_top);
const auto date = base::unixtime::parse(decision->date);
pushText( pushText(
TextWithEntities( TextWithEntities(
).append(Emoji(kCalendar)).append(' ').append( ).append(Emoji(kCalendar)).append(' ').append(
@ -138,8 +148,16 @@ auto GenerateSuggestDecisionMedia(
lt_channel, lt_channel,
Ui::Text::Bold(broadcast->name()), Ui::Text::Bold(broadcast->name()),
lt_date, lt_date,
Ui::Text::Bold(Ui::FormatDateTime( Ui::Text::Bold(tr::lng_mediaview_date_time(
base::unixtime::parse(decision->date))), tr::now,
lt_date,
QLocale().toString(
date.date(),
QLocale::ShortFormat),
lt_time,
QLocale().toString(
date.time(),
QLocale::ShortFormat))),
Ui::Text::WithEntities)), Ui::Text::WithEntities)),
(stars (stars
? st::chatSuggestInfoMiddleMargin ? st::chatSuggestInfoMiddleMargin
@ -189,4 +207,75 @@ auto GenerateSuggestDecisionMedia(
}; };
} }
auto GenerateSuggestRequestMedia(
not_null<Element*> parent,
not_null<const HistoryMessageSuggestedPost*> suggest)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
const auto normalFg = [](const PaintContext &context) {
return context.st->msgServiceFg()->c;
};
const auto fadedFg = [](const PaintContext &context) {
auto result = context.st->msgServiceFg()->c;
result.setAlphaF(result.alphaF() * kFadedOpacity);
return result;
};
const auto from = parent->data()->from();
const auto peer = parent->history()->peer;
auto pushText = [&](
TextWithEntities text,
QMargins margins = {},
style::align align = style::al_left,
const base::flat_map<uint16, ClickHandlerPtr> &links = {}) {
push(std::make_unique<MediaGenericTextPart>(
std::move(text),
margins,
st::defaultTextStyle,
links,
Ui::Text::MarkedContext(),
align));
};
pushText(
(from->isSelf()
? tr::lng_suggest_action_your(
tr::now,
Ui::Text::WithEntities)
: tr::lng_suggest_action_his(
tr::now,
lt_from,
Ui::Text::Bold(from->shortName()),
Ui::Text::WithEntities)),
st::chatSuggestInfoTitleMargin,
style::al_top);
auto entries = std::vector<AttributeTable::Entry>();
entries.push_back({
tr::lng_suggest_action_price_label(tr::now),
Ui::Text::Bold(suggest->stars
? tr::lng_prize_credits_amount(
tr::now,
lt_count,
suggest->stars)
: tr::lng_suggest_action_price_free(tr::now)),
});
entries.push_back({
tr::lng_suggest_action_time_label(tr::now),
Ui::Text::Bold(suggest->date
? Ui::FormatDateTime(base::unixtime::parse(suggest->date))
: tr::lng_suggest_action_time_any(tr::now)),
});
push(std::make_unique<AttributeTable>(
std::move(entries),
st::chatSuggestInfoLastMargin,
fadedFg,
normalFg));
};
}
} // namespace HistoryView } // namespace HistoryView

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
struct HistoryMessageSuggestedPost;
struct HistoryServiceSuggestDecision; struct HistoryServiceSuggestDecision;
namespace HistoryView { namespace HistoryView {
@ -22,4 +23,11 @@ auto GenerateSuggestDecisionMedia(
not_null<MediaGeneric*>, not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>; Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
auto GenerateSuggestRequestMedia(
not_null<Element*> parent,
not_null<const HistoryMessageSuggestedPost*> suggest
) -> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
} // namespace HistoryView } // namespace HistoryView

View file

@ -1052,6 +1052,7 @@ chatSimilarName: TextStyle(defaultTextStyle) {
chatSimilarWidthMax: 424px; chatSimilarWidthMax: 424px;
chatSimilarSkip: 12px; chatSimilarSkip: 12px;
chatSuggestWidth: 216px;
chatSuggestInfoWidth: 272px; chatSuggestInfoWidth: 272px;
chatSuggestInfoTitleMargin: margins(16px, 16px, 16px, 6px); chatSuggestInfoTitleMargin: margins(16px, 16px, 16px, 6px);
chatSuggestInfoMiddleMargin: margins(16px, 4px, 16px, 4px); chatSuggestInfoMiddleMargin: margins(16px, 4px, 16px, 4px);