feat: service message time badge

This commit is contained in:
AlexeyZavar
2026-04-05 22:49:05 +03:00
parent 172d842635
commit c9fcb8fdb4
4 changed files with 150 additions and 19 deletions
@@ -117,3 +117,10 @@ ghostPickerButton: LinkButton(defaultLinkButton) {
}
ayuBetaBadgePadding: margins(4px, 1px, 4px, 1px);
ayuServiceTimeBadgeFont: font(11px semibold);
ayuServiceTimeBadgeHorizontalPadding: 7px;
ayuServiceTimeBadgeVerticalPadding: 2px;
ayuServiceTimeBadgeGap: 8px;
ayuServiceTimeBadgeTrailingInset: 3px;
ayuServiceTimeBadgeLastLineWidthAdd: 9px;
+1 -10
View File
@@ -1367,20 +1367,11 @@ void HistoryItem::setCommentsItemId(FullMsgId id) {
}
void HistoryItem::setServiceText(PreparedServiceText &&prepared) {
auto text = std::move(prepared.text);
if (date() > 0) {
const auto timeString = QString(" (%1)").arg(formatMessageTime(base::unixtime::parse(_date).time()));
if (!text.text.isEmpty() && !text.text.contains(timeString)) {
text = text.append(timeString);
}
}
AddComponents(HistoryServiceData::Bit());
_flags &= ~MessageFlag::HasTextLinks;
const auto data = Get<HistoryServiceData>();
const auto had = !_text.empty();
_text = std::move(text);
_text = std::move(prepared.text);
data->textLinks = std::move(prepared.links);
if (had) {
_history->owner().requestItemTextRefresh(this);
@@ -32,6 +32,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat.h"
#include "styles/style_info.h"
// AyuGram includes
#include "ayu/utils/telegram_helpers.h"
#include "styles/style_ayu_styles.h"
namespace HistoryView {
namespace {
@@ -200,6 +205,64 @@ void SetText(Ui::Text::String &text, const QString &content) {
text.setText(st::serviceTextStyle, content, EmptyLineOptions);
}
struct ServiceTimeMetrics {
int width = 0;
int height = 0;
int additionalWidth = 0;
};
[[nodiscard]] ServiceTimeMetrics ComputeServiceTimeMetrics(const QString &text) {
auto result = ServiceTimeMetrics();
if (text.isEmpty()) {
return result;
}
const auto &font = st::ayuServiceTimeBadgeFont;
result.width = font->width(text)
+ 2 * st::ayuServiceTimeBadgeHorizontalPadding;
result.height = font->height + 2 * st::ayuServiceTimeBadgeVerticalPadding;
result.additionalWidth = std::max(
st::ayuServiceTimeBadgeGap
+ result.width
+ st::ayuServiceTimeBadgeTrailingInset
- st::msgServicePadding.right(),
0);
return result;
}
void PaintServiceTime(
Painter &p,
not_null<const Ui::ChatStyle*> st,
int left,
int top,
const QString &text,
const ServiceTimeMetrics &metrics) {
if (text.isEmpty() || !metrics.width) {
return;
}
const auto rect = QRect(
left,
top,
metrics.width,
metrics.height);
const auto bg = QColor(0x00, 0x00, 0x00, 0x1e);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(bg);
const auto radius = rect.height() / 2.;
p.drawRoundedRect(
rect,
radius,
radius);
p.setPen(st->msgServiceFg());
p.setFont(st::ayuServiceTimeBadgeFont);
p.drawText(
rect.x() + st::ayuServiceTimeBadgeHorizontalPadding,
rect.y()
+ st::ayuServiceTimeBadgeVerticalPadding
+ st::ayuServiceTimeBadgeFont->ascent,
text);
}
[[nodiscard]] Ui::BubbleRounding KeyboardRounding() {
return Ui::BubbleRounding{
.topLeft = Ui::BubbleCornerRounding::Large,
@@ -311,8 +374,9 @@ void ServiceMessagePainter::PaintComplexBubble(
int left,
int width,
const Ui::Text::String &text,
const QRect &textRect) {
const auto lineWidths = CountLineWidths(text, textRect);
const QRect &textRect,
int lastLineWidthAdd) {
const auto lineWidths = CountLineWidths(text, textRect, lastLineWidthAdd);
int y = st::msgServiceMargin.top(), previousRichWidth = 0;
bool previousShrink = false, forceShrink = false;
@@ -372,7 +436,8 @@ void ServiceMessagePainter::PaintComplexBubble(
std::vector<int> ServiceMessagePainter::CountLineWidths(
const Ui::Text::String &text,
const QRect &textRect) {
const QRect &textRect,
int lastLineWidthAdd) {
const auto linesCount = qMax(
textRect.height() / st::msgServiceFont->height,
1);
@@ -409,6 +474,12 @@ std::vector<int> ServiceMessagePainter::CountLineWidths(
}
}
}
if (lastLineWidthAdd > 0
&& result.size() > 1
&& result.back() > 0
&& result.back() < result[result.size() - 2]) {
result.back() += lastLineWidthAdd;
}
return result;
}
@@ -445,6 +516,18 @@ bool Service::consumeHorizontalScroll(QPoint position, int delta) {
return false;
}
namespace {
[[nodiscard]] QString ServiceTimeText(
const Ui::Text::String &text,
const QDateTime &dateTime) {
return (!text.isEmpty() && dateTime.isValid())
? formatMessageTime(dateTime.time())
: QString();
}
} // namespace
QRect Service::countGeometry() const {
auto result = QRect(0, 0, width(), height());
if (delegate()->elementChatMode() == ElementChatMode::Wide) {
@@ -485,6 +568,9 @@ QSize Service::performCountCurrentSize(int newWidth) {
}
const auto media = this->media();
const auto mediaDisplayed = media && media->isDisplayed();
const auto hideText = mediaDisplayed && media->hideServiceText();
const auto timeText = hideText ? QString() : ServiceTimeText(text(), dateTime());
const auto timeMetrics = ComputeServiceTimeMetrics(timeText);
auto contentWidth = newWidth;
if (delegate()->elementChatMode() == ElementChatMode::Wide) {
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
@@ -493,10 +579,14 @@ QSize Service::performCountCurrentSize(int newWidth) {
if (contentWidth < st::msgServicePadding.left() + st::msgServicePadding.right() + 1) {
contentWidth = st::msgServicePadding.left() + st::msgServicePadding.right() + 1;
}
if (mediaDisplayed && media->hideServiceText()) {
const auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
validateTextSkipBlock(
!timeText.isEmpty(),
timeMetrics.additionalWidth,
qMax(timeMetrics.height, st::msgServiceFont->height));
if (hideText) {
newHeight += media->resizeGetHeight(newWidth) + marginBottom();
} else if (!text().isEmpty()) {
auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
newHeight += (contentWidth >= maxWidth())
? minHeight()
: textHeightFor(nwidth);
@@ -533,7 +623,6 @@ QSize Service::performCountCurrentSize(int newWidth) {
QSize Service::performCountOptimalSize() {
const auto markup = data()->inlineReplyMarkup();
validateText();
validateInlineKeyboard(markup);
if (_reactions) {
_reactions->initDimensions();
@@ -542,9 +631,18 @@ QSize Service::performCountOptimalSize() {
if (const auto media = this->media()) {
media->initDimensions();
if (media->hideServiceText()) {
validateTextSkipBlock(false, 0, 0);
validateInlineKeyboard(markup);
return { media->maxWidth(), media->minHeight() };
}
}
const auto timeText = ServiceTimeText(text(), dateTime());
const auto timeMetrics = ComputeServiceTimeMetrics(timeText);
validateTextSkipBlock(
!timeText.isEmpty(),
timeMetrics.additionalWidth,
qMax(timeMetrics.height, st::msgServiceFont->height));
validateInlineKeyboard(markup);
auto maxWidth = text().maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right();
auto minHeight = text().minHeight();
return { maxWidth, minHeight };
@@ -623,6 +721,8 @@ void Service::draw(Painter &p, const PaintContext &context) const {
const auto media = this->media();
const auto mediaDisplayed = media && media->isDisplayed();
const auto onlyMedia = (mediaDisplayed && media->hideServiceText());
const auto timeText = onlyMedia ? QString() : ServiceTimeText(text(), dateTime());
const auto timeMetrics = ComputeServiceTimeMetrics(timeText);
if (_reactions) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
@@ -665,6 +765,15 @@ void Service::draw(Painter &p, const PaintContext &context) const {
const auto mediaSkip = mediaDisplayed ? (st::msgServiceMargin.top() + media->height()) : 0;
const auto trect = QRect(g.left(), g.top(), g.width(), g.height() - mediaSkip)
- st::msgServicePadding;
auto lineWidths = text().countLineWidths(trect.width());
for (auto &lineWidth : lineWidths) {
lineWidth = qMax(lineWidth, 0);
}
const auto lastLineWidthAdd = (lineWidths.size() > 1
&& lineWidths.back() > timeMetrics.additionalWidth
&& lineWidths.back() < lineWidths[lineWidths.size() - 2])
? st::ayuServiceTimeBadgeLastLineWidthAdd
: 0;
p.translate(0, g.top() - st::msgServiceMargin.top());
ServiceMessagePainter::PaintComplexBubble(
@@ -673,7 +782,8 @@ void Service::draw(Painter &p, const PaintContext &context) const {
g.left(),
g.width(),
text(),
trect);
trect,
lastLineWidthAdd);
p.translate(0, -g.top() + st::msgServiceMargin.top());
p.setBrush(Qt::NoBrush);
@@ -692,6 +802,27 @@ void Service::draw(Painter &p, const PaintContext &context) const {
.fullWidthSelection = false,
.selection = context.selection,
});
if (!timeText.isEmpty() && !lineWidths.empty()) {
const auto lastLineWidth = lineWidths.back();
const auto lastTextWidth = qMax(
lastLineWidth - timeMetrics.additionalWidth,
0);
const auto gap = lastTextWidth ? st::ayuServiceTimeBadgeGap : 0;
const auto lastLineTop = trect.y()
+ (int(lineWidths.size()) - 1) * st::msgServiceFont->height;
PaintServiceTime(
p,
context.st,
trect.x()
+ ((trect.width() - lastLineWidth) / 2)
+ lastTextWidth
+ gap,
lastLineTop
+ ((st::msgServiceFont->height - timeMetrics.height) / 2),
timeText,
timeMetrics);
}
}
if (mediaDisplayed) {
const auto left = g.left() + (g.width() - media->width()) / 2;
@@ -121,12 +121,14 @@ public:
int left,
int width,
const Ui::Text::String &text,
const QRect &textRect);
const QRect &textRect,
int lastLineWidthAdd = 0);
private:
static std::vector<int> CountLineWidths(
const Ui::Text::String &text,
const QRect &textRect);
const QRect &textRect,
int lastLineWidthAdd = 0);
};