Expandable quote snippets in replies.

This commit is contained in:
John Preston 2023-11-02 20:20:04 +04:00
parent 3b40bc6297
commit 5c98406e1a
4 changed files with 188 additions and 57 deletions

View file

@ -55,6 +55,7 @@ public:
rpl::producer<DefaultIcon> value, rpl::producer<DefaultIcon> value,
Fn<void()> repaint); Fn<void()> repaint);
int width() override;
QString entityData() override; QString entityData() override;
void paint(QPainter &p, const Context &context) override; void paint(QPainter &p, const Context &context) override;
@ -80,6 +81,10 @@ DefaultIconEmoji::DefaultIconEmoji(
}, _lifetime); }, _lifetime);
} }
int DefaultIconEmoji::width() {
return st::emojiSize + 2 * st::emojiPadding;
}
QString DefaultIconEmoji::entityData() { QString DefaultIconEmoji::entityData() {
return u"topic_icon:%1"_q.arg(_icon.colorId); return u"topic_icon:%1"_q.arg(_icon.colorId);
} }

View file

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
@ -54,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
constexpr auto kNonExpandedLinesLimit = 5;
const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_"; const auto kPsaForwardedPrefix = "cloud_lng_forwarded_psa_";
void ValidateBackgroundEmoji( void ValidateBackgroundEmoji(
@ -469,7 +471,10 @@ FullReplyTo ReplyToFromMTP(
}); });
} }
HistoryMessageReply::HistoryMessageReply() = default; HistoryMessageReply::HistoryMessageReply()
: _name(st::maxSignatureSize / 2)
, _text(st::maxSignatureSize / 2) {
}
HistoryMessageReply &HistoryMessageReply::operator=( HistoryMessageReply &HistoryMessageReply::operator=(
HistoryMessageReply &&other) = default; HistoryMessageReply &&other) = default;
@ -528,8 +533,7 @@ bool HistoryMessageReply::updateData(
.customEmojiRepaint = repaint, .customEmojiRepaint = repaint,
}; };
const auto external = this->external(); const auto external = this->external();
_multiline = 0; _multiline = !_fields.storyId && (external || !_fields.quote.empty());
// #TODO !_fields.storyId && (external || !_fields.quote.empty());
const auto displaying = resolvedMessage const auto displaying = resolvedMessage
|| resolvedStory || resolvedStory
@ -607,29 +611,70 @@ void HistoryMessageReply::updateFields(
} }
} }
bool HistoryMessageReply::expand() {
if (!_expandable || _expanded) {
return false;
}
_expanded = true;
return true;
}
void HistoryMessageReply::setLinkFrom( void HistoryMessageReply::setLinkFrom(
not_null<HistoryItem*> holder) { not_null<HistoryItem*> holder) {
const auto externalPeerId = _fields.externalSenderId; const auto externalChannelId = peerToChannel(_fields.externalPeerId);
const auto external = externalPeerId const auto messageId = _fields.messageId;
|| !_fields.externalSenderName.isEmpty(); const auto quote = _fields.manualQuote
? _fields.quote
: TextWithEntities();
const auto returnToId = holder->fullId();
const auto externalLink = [=](ClickContext context) { const auto externalLink = [=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
if (externalPeerId) { auto error = QString();
controller->showPeerInfo( const auto owner = &controller->session().data();
controller->session().data().peer(externalPeerId)); if (const auto item = owner->message(returnToId)) {
if (const auto reply = item->Get<HistoryMessageReply>()) {
if (reply->expand()) {
owner->requestItemResize(item);
return;
}
}
}
if (externalChannelId) {
const auto channel = owner->channel(externalChannelId);
if (!channel->isForbidden()) {
if (messageId) {
JumpToMessageClickHandler(
channel,
messageId,
returnToId,
quote
)->onClick(context);
} else {
controller->showPeerInfo(channel);
}
} else if (channel->isBroadcast()) {
error = tr::lng_channel_not_accessible(tr::now);
} else {
error = tr::lng_group_not_accessible(tr::now);
}
} else {
error = tr::lng_reply_from_private_chat(tr::now);
}
if (!error.isEmpty()) {
controller->showToast(error);
} }
controller->showToast(tr::lng_reply_from_private_chat(tr::now));
} }
}; };
_link = resolvedMessage _link = resolvedMessage
? JumpToMessageClickHandler( ? JumpToMessageClickHandler(
resolvedMessage.get(), resolvedMessage.get(),
holder->fullId(), returnToId,
_fields.manualQuote ? _fields.quote : TextWithEntities()) quote)
: resolvedStory : resolvedStory
? JumpToStoryClickHandler(resolvedStory.get()) ? JumpToStoryClickHandler(resolvedStory.get())
: (external && !_fields.messageId) : (external()
&& (!_fields.messageId || (_unavailable && externalChannelId)))
? std::make_shared<LambdaClickHandler>(externalLink) ? std::make_shared<LambdaClickHandler>(externalLink)
: nullptr; : nullptr;
} }
@ -753,11 +798,13 @@ void HistoryMessageReply::updateName(
|| (resolvedMessage || (resolvedMessage
&& resolvedMessage->media() && resolvedMessage->media()
&& resolvedMessage->media()->hasReplyPreview()); && resolvedMessage->media()->hasReplyPreview());
const auto previewSkip = st::messageQuoteStyle.outline const auto previewSkip = hasPreview
+ st::historyReplyPreviewMargin.left() ? (st::messageQuoteStyle.outline
+ st::historyReplyPreview + st::historyReplyPreviewMargin.left()
+ st::historyReplyPreviewMargin.right() + st::historyReplyPreview
- st::historyReplyPadding.left(); + st::historyReplyPreviewMargin.right()
- st::historyReplyPadding.left())
: 0;
const auto peerIcon = [](PeerData *peer) { const auto peerIcon = [](PeerData *peer) {
return !peer return !peer
? &st::historyReplyUser ? &st::historyReplyUser
@ -774,7 +821,7 @@ void HistoryMessageReply::updateName(
*peerIcon(peer))); *peerIcon(peer)));
}; };
auto nameFull = TextWithEntities(); auto nameFull = TextWithEntities();
if (!groupNameAdded && !_fields.storyId) { if (!groupNameAdded && external() && !_fields.storyId) {
nameFull.append(peerEmoji(sender)); nameFull.append(peerEmoji(sender));
} }
nameFull.append(name); nameFull.append(name);
@ -836,23 +883,61 @@ int HistoryMessageReply::resizeToWidth(int width) const {
|| (resolvedMessage || (resolvedMessage
&& resolvedMessage->media() && resolvedMessage->media()
&& resolvedMessage->media()->hasReplyPreview()); && resolvedMessage->media()->hasReplyPreview());
const auto textLeft = hasPreview const auto previewSkip = hasPreview
? (st::messageQuoteStyle.outline ? (st::messageQuoteStyle.outline
+ st::historyReplyPreviewMargin.left() + st::historyReplyPreviewMargin.left()
+ st::historyReplyPreview + st::historyReplyPreview
+ st::historyReplyPreviewMargin.right()) + st::historyReplyPreviewMargin.right()
: st::historyReplyPadding.left(); - st::historyReplyPadding.left())
: 0;
if (width >= _maxWidth || !_multiline) { if (width >= _maxWidth || !_multiline) {
_nameTwoLines = 0;
_expandable = 0;
_height = _minHeight; _height = _minHeight;
return height(); return height();
} }
// #TODO const auto innerw = width
_height = _minHeight; - st::historyReplyPadding.left()
- st::historyReplyPadding.right();
const auto namew = innerw - previewSkip;
const auto desiredNameHeight = _name.countHeight(namew);
_nameTwoLines = (desiredNameHeight > st::semiboldFont->height) ? 1 : 0;
const auto nameh = (_nameTwoLines ? 2 : 1) * st::semiboldFont->height;
const auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
auto lineCounter = 0;
auto elided = false;
const auto texth = _text.countDimensions(
textGeometry(innerw, firstLineSkip, &lineCounter, &elided)).height;
_expandable = (_multiline && elided) ? 1 : 0;
_height = st::historyReplyPadding.top()
+ nameh
+ (elided ? kNonExpandedLinesLimit * st::normalFont->height : texth)
+ st::historyReplyPadding.bottom();
return height(); return height();
} }
Ui::Text::GeometryDescriptor HistoryMessageReply::textGeometry(
int available,
int firstLineSkip,
not_null<int*> line,
not_null<bool*> outElided) const {
return { .layout = [=](Ui::Text::LineGeometry in) {
const auto skip = (*line ? 0 : firstLineSkip);
++*line;
*outElided = *outElided
|| !_multiline
|| (!_expanded
&& (*line == kNonExpandedLinesLimit)
&& in.width > available - skip);
in.width = available - skip;
in.left += skip;
in.elided = *outElided;
return in;
} };
}
int HistoryMessageReply::height() const { int HistoryMessageReply::height() const {
return _height + st::historyReplyTop + st::historyReplyBottom; return _height + st::historyReplyTop + st::historyReplyBottom;
} }
QMargins HistoryMessageReply::margins() const { QMargins HistoryMessageReply::margins() const {
@ -882,8 +967,13 @@ bool HistoryMessageReply::hasQuoteIcon() const {
} }
QSize HistoryMessageReply::countMultilineOptimalSize( QSize HistoryMessageReply::countMultilineOptimalSize(
int firstLineSkip) const { int previewSkip) const {
return QSize(); // #TODO auto elided = false;
auto lineCounter = 0;
const auto max = previewSkip + _text.maxWidth();
const auto result = _text.countDimensions(
textGeometry(max, previewSkip, &lineCounter, &elided));
return { result.width, result.height };
} }
void HistoryMessageReply::paint( void HistoryMessageReply::paint(
@ -972,23 +1062,33 @@ void HistoryMessageReply::paint(
} }
} }
const auto withPreviewLeft = st::messageQuoteStyle.outline auto hasPreview = (resolvedStory
+ st::historyReplyPreviewMargin.left() && resolvedStory->hasReplyPreview())
+ st::historyReplyPreview || (resolvedMessage
+ st::historyReplyPreviewMargin.right(); && resolvedMessage->media()
auto textLeft = st::historyReplyPadding.left(); && resolvedMessage->media()->hasReplyPreview());
auto previewSkip = hasPreview
? (st::messageQuoteStyle.outline
+ st::historyReplyPreviewMargin.left()
+ st::historyReplyPreview
+ st::historyReplyPreviewMargin.right()
- st::historyReplyPadding.left())
: 0;
if (hasPreview && w <= st::historyReplyPadding.left() + previewSkip) {
hasPreview = false;
previewSkip = 0;
}
const auto pausedSpoiler = context.paused const auto pausedSpoiler = context.paused
|| On(PowerSaving::kChatSpoiler); || On(PowerSaving::kChatSpoiler);
if (w > textLeft) { auto textLeft = x + st::historyReplyPadding.left();
auto textTop = y
+ st::historyReplyPadding.top()
+ (st::msgServiceNameFont->height * (_nameTwoLines ? 2 : 1));
if (w > st::historyReplyPadding.left()) {
if (resolvedMessage || resolvedStory || !_text.isEmpty()) { if (resolvedMessage || resolvedStory || !_text.isEmpty()) {
const auto media = resolvedMessage ? resolvedMessage->media() : nullptr; const auto media = resolvedMessage ? resolvedMessage->media() : nullptr;
auto hasPreview = (media && media->hasReplyPreview())
|| (resolvedStory && resolvedStory->hasReplyPreview());
if (hasPreview && w <= withPreviewLeft) {
hasPreview = false;
}
if (hasPreview) { if (hasPreview) {
textLeft = withPreviewLeft;
const auto image = media const auto image = media
? media->replyPreview() ? media->replyPreview()
: resolvedStory->replyPreview(); : resolvedStory->replyPreview();
@ -1021,22 +1121,29 @@ void HistoryMessageReply::paint(
} }
} }
} }
if (w > textLeft + st::historyReplyPadding.right()) { const auto textw = w
w -= textLeft + st::historyReplyPadding.right(); - st::historyReplyPadding.left()
- st::historyReplyPadding.right();
const auto namew = textw - previewSkip;
auto firstLineSkip = _nameTwoLines ? 0 : previewSkip;
if (namew > 0) {
p.setPen(!inBubble p.setPen(!inBubble
? st->msgImgReplyBarColor()->c ? st->msgImgReplyBarColor()->c
: useColorIndex : useColorIndex
? FromNameFg(context, colorIndexPlusOne - 1) ? FromNameFg(context, colorIndexPlusOne - 1)
: stm->msgServiceFg->c); : stm->msgServiceFg->c);
_name.drawLeftElided(p, x + textLeft, y + st::historyReplyPadding.top(), w, w + 2 * x + 2 * textLeft); _name.drawLeftElided(
p,
x + st::historyReplyPadding.left() + previewSkip,
y + st::historyReplyPadding.top(),
namew,
w + 2 * x,
_nameTwoLines ? 2 : 1);
p.setPen(inBubble p.setPen(inBubble
? stm->historyTextFg ? stm->historyTextFg
: st->msgImgReplyBarColor()); : st->msgImgReplyBarColor());
holder->prepareCustomEmojiPaint(p, context, _text); holder->prepareCustomEmojiPaint(p, context, _text);
auto replyToTextPosition = QPoint(
x + textLeft,
y + st::historyReplyPadding.top() + st::msgServiceNameFont->height);
auto replyToTextPalette = &(!inBubble auto replyToTextPalette = &(!inBubble
? st->imgReplyTextPalette() ? st->imgReplyTextPalette()
: useColorIndex : useColorIndex
@ -1045,13 +1152,12 @@ void HistoryMessageReply::paint(
if (_fields.storyId) { if (_fields.storyId) {
st::dialogsMiniReplyStory.icon.icon.paint( st::dialogsMiniReplyStory.icon.icon.paint(
p, p,
replyToTextPosition, textLeft + firstLineSkip,
w + 2 * x + 2 * textLeft, textTop,
w + 2 * x,
replyToTextPalette->linkFg->c); replyToTextPalette->linkFg->c);
replyToTextPosition += QPoint( firstLineSkip += st::dialogsMiniReplyStory.skipText
st::dialogsMiniReplyStory.skipText + st::dialogsMiniReplyStory.icon.icon.width();
+ st::dialogsMiniReplyStory.icon.icon.width(),
0);
} }
auto owned = std::optional<style::owned_color>(); auto owned = std::optional<style::owned_color>();
auto copy = std::optional<style::TextPalette>(); auto copy = std::optional<style::TextPalette>();
@ -1061,9 +1167,11 @@ void HistoryMessageReply::paint(
copy->linkFg = owned->color(); copy->linkFg = owned->color();
replyToTextPalette = &*copy; replyToTextPalette = &*copy;
} }
auto l = 0;
auto e = false;
_text.draw(p, { _text.draw(p, {
.position = replyToTextPosition, .position = { textLeft, textTop },
.availableWidth = w, .geometry = textGeometry(textw, firstLineSkip, &l, &e),
.palette = replyToTextPalette, .palette = replyToTextPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = context.now, .now = context.now,
@ -1078,9 +1186,9 @@ void HistoryMessageReply::paint(
p.setFont(st::msgDateFont); p.setFont(st::msgDateFont);
p.setPen(cache->icon); p.setPen(cache->icon);
p.drawTextLeft( p.drawTextLeft(
x + textLeft, textLeft,
(y + (_height - st::msgDateFont->height) / 2), y + st::historyReplyPadding.top() + (st::msgDateFont->height / 2),
w + 2 * x + 2 * textLeft, w + 2 * x,
st::msgDateFont->elided( st::msgDateFont->elided(
statePhrase(), statePhrase(),
w - textLeft - st::historyReplyPadding.right())); w - textLeft - st::historyReplyPadding.right()));

View file

@ -25,6 +25,10 @@ struct PeerUserpicView;
class SpoilerAnimation; class SpoilerAnimation;
} // namespace Ui } // namespace Ui
namespace Ui::Text {
struct GeometryDescriptor;
} // namespace Ui::Text
namespace Data { namespace Data {
class Session; class Session;
class Story; class Story;
@ -296,6 +300,8 @@ struct HistoryMessageReply
not_null<HistoryItem*> holder, not_null<HistoryItem*> holder,
not_null<Data::Story*> removed); not_null<Data::Story*> removed);
bool expand();
void paint( void paint(
Painter &p, Painter &p,
not_null<const HistoryView::Element*> holder, not_null<const HistoryView::Element*> holder,
@ -353,6 +359,11 @@ struct HistoryMessageReply
private: private:
[[nodiscard]] bool hasQuoteIcon() const; [[nodiscard]] bool hasQuoteIcon() const;
[[nodiscard]] Ui::Text::GeometryDescriptor textGeometry(
int available,
int firstLineSkip,
not_null<int*> line,
not_null<bool*> outElided) const;
[[nodiscard]] QSize countMultilineOptimalSize( [[nodiscard]] QSize countMultilineOptimalSize(
int firstLineSkip) const; int firstLineSkip) const;
@ -368,7 +379,9 @@ private:
uint8 _unavailable : 1 = 0; uint8 _unavailable : 1 = 0;
uint8 _displaying : 1 = 0; uint8 _displaying : 1 = 0;
uint8 _multiline : 1 = 0; uint8 _multiline : 1 = 0;
uint8 _expandable : 1 = 0; mutable uint8 _expandable : 1 = 0;
uint8 _expanded : 1 = 0;
mutable uint8 _nameTwoLines : 1 = 0;
}; };

View file

@ -47,6 +47,7 @@ public:
QPoint shift, QPoint shift,
int index); int index);
int width() override;
QString entityData() override; QString entityData() override;
void paint(QPainter &p, const Context &context) override; void paint(QPainter &p, const Context &context) override;
void unload() override; void unload() override;
@ -73,6 +74,10 @@ StripEmoji::StripEmoji(
, _index(index) { , _index(index) {
} }
int StripEmoji::width() {
return _wrapped->width();
}
QString StripEmoji::entityData() { QString StripEmoji::entityData() {
return _wrapped->entityData(); return _wrapped->entityData();
} }