Make messages nicer, sending animation.

This commit is contained in:
John Preston
2025-09-17 13:04:06 +04:00
parent 4436e3ba69
commit 9b3acf7162
6 changed files with 174 additions and 51 deletions
+2
View File
@@ -1687,6 +1687,8 @@ groupCallInviteLinkIcon: icon {{ "info/edit/group_manage_links", mediaviewTextLi
groupCallMessagePadding: margins(8px, 2px, 8px, 2px);
groupCallMessageSkip: 8px;
groupCallUserpic: 20px;
groupCallUserpicPadding: margins(2px, 2px, 4px, 2px);
confcallLinkMenu: IconButton(boxTitleClose) {
icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
@@ -20,10 +20,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h"
namespace Calls::Group {
namespace {
constexpr auto kMessageLifetime = TimeId(20);
} // namespace
Messages::Messages(not_null<GroupCall*> call, not_null<MTP::Sender*> api)
: _call(call)
, _api(api) {
, _api(api)
, _destroyTimer([=] { checkDestroying(); }) {
Ui::PostponeCall(_call, [=] {
_call->real(
) | rpl::start_with_next([=](not_null<Data::GroupCall*> call) {
@@ -71,9 +77,13 @@ void Messages::send(TextWithTags text) {
_call->inputCall(),
serialized
)).done([=](const MTPBool &, const MTP::Response &response) {
if (rand() % 256 < 64) {
AssertIsDebug();
failed(id, response);
} else
sent(id, response);
}).fail([=] {
failed(id);
}).fail([=](const MTP::Error &, const MTP::Response &response) {
failed(id, response);
}).send();
} else {
auto counter = ::tl::details::LengthCounter();
@@ -94,11 +104,11 @@ void Messages::send(TextWithTags text) {
MTP_bytes(bytes::make_span(encrypted))
)).done([=](const MTPBool &, const MTP::Response &response) {
sent(id, response);
}).fail([=] {
failed(id);
}).fail([=](const MTP::Error &, const MTP::Response &response) {
failed(id, response);
}).send();
}
pushChanges();
checkDestroying(true);
}
void Messages::received(const MTPDupdateGroupCallMessage &data) {
@@ -152,6 +162,39 @@ void Messages::received(
.peer = peer->owner().peer(peerFromMTP(from)),
.text = Api::ParseTextWithEntities(&peer->session(), message),
});
checkDestroying(true);
}
void Messages::checkDestroying(bool afterChanges) {
auto next = TimeId();
const auto now = base::unixtime::now();
const auto destroyTime = now - kMessageLifetime;
const auto initial = _messages.size();
for (auto i = begin(_messages); i != end(_messages);) {
const auto date = i->date;
if (!date) {
++i;
} else if (date <= destroyTime) {
i = _messages.erase(i);
} else if (!next) {
next = date + kMessageLifetime - now;
++i;
} else {
++i;
}
}
if (!next) {
_destroyTimer.cancel();
} else {
const auto delay = next * crl::time(1000);
if (!_destroyTimer.isActive()
|| (_destroyTimer.remainingTime() > delay)) {
_destroyTimer.callOnce(delay);
}
}
if (afterChanges || (_messages.size() < initial)) {
pushChanges();
}
}
rpl::producer<std::vector<Message>> Messages::listValue() const {
@@ -174,15 +217,16 @@ void Messages::sent(int id, const MTP::Response &response) {
const auto i = ranges::find(_messages, id, &Message::id);
if (i != end(_messages)) {
i->date = Api::UnixtimeFromMsgId(response.outerMsgId);
pushChanges();
checkDestroying(true);
}
}
void Messages::failed(int id) {
void Messages::failed(int id, const MTP::Response &response) {
const auto i = ranges::find(_messages, id, &Message::id);
if (i != end(_messages)) {
i->date = Api::UnixtimeFromMsgId(response.outerMsgId);
i->failed = true;
pushChanges();
checkDestroying(true);
}
}
@@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
namespace Calls {
class GroupCall;
} // namespace Calls
@@ -45,10 +47,11 @@ private:
[[nodiscard]] bool ready() const;
void sendPending();
void pushChanges();
void checkDestroying(bool afterChanges = false);
void received(const MTPPeer &from, const MTPTextWithEntities &message);
void sent(int id, const MTP::Response &response);
void failed(int id);
void failed(int id, const MTP::Response &response);
const not_null<GroupCall*> _call;
const not_null<MTP::Sender*> _api;
@@ -57,6 +60,7 @@ private:
std::vector<TextWithTags> _pending;
base::Timer _destroyTimer;
std::vector<Message> _messages;
rpl::event_stream<std::vector<Message>> _changes;
@@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/emoji_button.h"
#include "ui/controls/send_button.h"
#include "ui/effects/animations.h"
#include "ui/effects/radial_animation.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/scroll_area.h"
@@ -55,7 +56,7 @@ struct MessagesUi::MessageView {
PeerData *from = nullptr;
Ui::Animations::Simple toggleAnimation;
Ui::Animations::Simple sentAnimation;
Ui::Animations::Basic sendingAnimation;
std::unique_ptr<Ui::InfiniteRadialAnimation> sendingAnimation;
Ui::PeerUserpicView view;
Ui::Text::String text;
int top = 0;
@@ -97,8 +98,10 @@ void MessagesUi::setupList(rpl::producer<std::vector<Message>> messages) {
const auto i = ranges::find(from, till, id, &Message::id);
if (i == till) {
toggleMessage(entry, false);
continue;
} else if (entry.failed != i->failed) {
setContentFailed(entry);
updateMessageSize(entry);
repaintMessage(entry.id);
} else if (entry.sending != (i->date == 0)) {
animateMessageSent(entry);
@@ -121,33 +124,56 @@ void MessagesUi::animateMessageSent(MessageView &entry) {
entry.sending = false;
entry.sentAnimation.start([=] {
repaintMessage(id);
}, 0., 1, st::slideWrapDuration, anim::easeOutCirc);
}, 0., 1, st::slideDuration, anim::easeOutCirc);
repaintMessage(id);
}
void MessagesUi::updateMessageSize(MessageView &entry) {
const auto &padding = st::groupCallMessagePadding;
const auto widthSkip = padding.left() + padding.right();
const auto hasUserpic = !entry.failed;
const auto userpicPadding = st::groupCallUserpicPadding;
const auto userpicSize = st::groupCallUserpic;
const auto leftSkip = hasUserpic
? (userpicPadding.left() + userpicSize + userpicPadding.right())
: padding.left();
const auto widthSkip = leftSkip + padding.right();
const auto inner = _width - widthSkip;
entry.width = std::min(inner, entry.text.maxWidth()) + widthSkip;
const auto size = Ui::Text::CountOptimalTextSize(
entry.text,
std::min(st::groupCallWidth / 2, inner),
inner);
const auto textHeight = size.height();
entry.width = size.width() + widthSkip;
entry.left = (_width - entry.width) / 2;
const auto contentHeight = padding.top() + textHeight + padding.bottom();
const auto userpicHeight = hasUserpic
? (userpicPadding.top() + userpicSize + userpicPadding.bottom())
: 0;
const auto skip = st::groupCallMessageSkip;
const auto textHeight = entry.text.countHeight(entry.width);
entry.realHeight = skip
+ padding.top()
+ textHeight
+ padding.bottom();
entry.height = entry.toggleAnimation.animating()
entry.realHeight = skip + std::max(contentHeight, userpicHeight);
}
bool MessagesUi::updateMessageHeight(MessageView &entry) {
const auto height = entry.toggleAnimation.animating()
? anim::interpolate(
0,
entry.realHeight,
entry.toggleAnimation.value(entry.removed ? 0. : 1.))
: entry.realHeight;
if (entry.height == height) {
return false;
}
entry.height = height;
return true;
}
void MessagesUi::setContentFailed(MessageView &entry) {
const auto manager = &entry.from->owner().customEmojiManager();
entry.failed = true;
entry.text = Ui::Text::String(
st::messageTextStyle,
TextWithEntities().append(
@@ -155,7 +181,21 @@ void MessagesUi::setContentFailed(MessageView &entry) {
).append(' ').append(
Ui::Text::Italic(u"Failed to send the message."_q)),
kMarkupTextOptions,
st::groupCallWidth / 2);
st::groupCallWidth / 4);
}
void MessagesUi::setContent(
MessageView &entry,
const TextWithEntities &text) {
entry.text = Ui::Text::String(
st::messageTextStyle,
text,
kMarkupTextOptions,
st::groupCallWidth / 4,
Core::TextContext({
.session = &_show->session(),
.repaint = [this, id = entry.id] { repaintMessage(id); },
}));
}
void MessagesUi::toggleMessage(MessageView &entry, bool shown) {
@@ -166,7 +206,8 @@ void MessagesUi::toggleMessage(MessageView &entry, bool shown) {
shown ? 0. : 1.,
shown ? 1. : 0.,
st::slideWrapDuration,
anim::easeOutCirc);
shown ? anim::easeOutCirc : anim::easeInCirc);
repaintMessage(id);
}
void MessagesUi::repaintMessage(int id) {
@@ -180,13 +221,10 @@ void MessagesUi::repaintMessage(int id) {
return;
}
if (!i->sending && !i->sentAnimation.animating()) {
i->sendingAnimation.stop();
i->sendingAnimation = nullptr;
}
if (i->toggleAnimation.animating() || i->height != i->realHeight) {
const auto shown = i->toggleAnimation.value(i->removed ? 0. : 1.);
const auto height = anim::interpolate(0, i->realHeight, shown);
if (i->height != height) {
i->height = height;
if (updateMessageHeight(*i)) {
recountHeights(i, i->top);
return;
}
@@ -227,30 +265,23 @@ void MessagesUi::appendMessage(const Message &data) {
};
entry.from = data.peer;
entry.sending = !data.date;
entry.failed = data.failed;
if (entry.failed) {
if (data.failed) {
setContentFailed(entry);
} else {
const auto manager = &entry.from->owner().customEmojiManager();
entry.text = Ui::Text::String(
st::messageTextStyle,
Ui::Text::SingleCustomEmoji(
manager->peerUserpicEmojiData(entry.from),
u"@"_q).append(' ').append(
Ui::Text::Bold(data.peer->shortName())
).append(' ').append(data.text),
kMarkupTextOptions,
st::groupCallWidth / 2,
Core::TextContext({
.session = &_show->session(),
.repaint = repaint,
}));
setContent(
entry,
Ui::Text::Bold(
data.peer->shortName()).append(' ').append(data.text));
}
entry.top = top;
updateMessageSize(entry);
if (entry.sending) {
entry.sendingAnimation.init(repaint);
entry.sendingAnimation.start();
using namespace Ui;
const auto &st = st::defaultInfiniteRadialAnimation;
entry.sendingAnimation = std::make_unique<InfiniteRadialAnimation>(
repaint,
st);
entry.sendingAnimation->start(0);
}
entry.height = 0;
toggleMessage(entry, true);
@@ -262,8 +293,7 @@ void MessagesUi::setupMessagesWidget() {
object_ptr<Ui::RpWidget>(_scroll.get()));
_messages->paintRequest() | rpl::start_with_next([=](QRect clip) {
auto p = QPainter(_messages);
auto hq = PainterHighQualityEnabler(p);
auto p = Painter(_messages);
const auto skip = st::groupCallMessageSkip;
const auto padding = st::groupCallMessagePadding;
const auto widthSkip = padding.left() + padding.right();
@@ -289,10 +319,49 @@ void MessagesUi::setupMessagesWidget() {
}
_messageBgRect.paint(p, { entry.left, entry.top, width, use });
auto leftSkip = padding.left();
const auto hasUserpic = !entry.failed;
if (hasUserpic) {
const auto userpicSize = st::groupCallUserpic;
const auto userpicPadding = st::groupCallUserpicPadding;
const auto position = QPoint(
entry.left + userpicPadding.left(),
entry.top + userpicPadding.top());
const auto rect = QRect(
position,
QSize(userpicSize, userpicSize));
entry.from->paintUserpic(p, entry.view, {
.position = position,
.size = userpicSize,
.shape = Ui::PeerUserpicShape::Circle,
});
if (const auto animation = entry.sendingAnimation.get()) {
auto hq = PainterHighQualityEnabler(p);
auto pen = st::groupCallBg->p;
const auto shift = userpicPadding.left();
pen.setWidthF(shift);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
const auto state = animation->computeState();
const auto sent = entry.sending
? 0.
: entry.sentAnimation.value(1.);
p.setOpacity(state.shown * (1. - sent));
p.drawArc(
rect.marginsRemoved({ shift, shift, shift, shift }),
state.arcFrom,
state.arcLength);
p.setOpacity(1.);
}
leftSkip = userpicPadding.left()
+ userpicSize
+ userpicPadding.right();
}
p.setPen(st::radialFg);
entry.text.draw(p, {
.position = {
entry.left + padding.left(),
entry.left + leftSkip,
entry.top + padding.top()
},
.availableWidth = entry.width - widthSkip,
@@ -313,8 +382,11 @@ void MessagesUi::applyWidth() {
}
auto top = 0;
for (auto &entry : _views) {
updateMessageSize(entry);
entry.top = top;
updateMessageSize(entry);
updateMessageHeight(entry);
top += entry.height;
}
updateGeometries();
@@ -48,7 +48,9 @@ private:
void setupList(rpl::producer<std::vector<Message>> messages);
void toggleMessage(MessageView &entry, bool shown);
void setContentFailed(MessageView &entry);
void setContent(MessageView &entry, const TextWithEntities &text);
void updateMessageSize(MessageView &entry);
bool updateMessageHeight(MessageView &entry);
void animateMessageSent(MessageView &entry);
void repaintMessage(int id);
void recountHeights(std::vector<MessageView>::iterator i, int top);
@@ -14,7 +14,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "ui/chat/chat_style.h"
#include "ui/widgets/tooltip.h"
#include "ui/dynamic_image.h"
#include "ui/dynamic_thumbnails.h"
#include "ui/painter.h"