mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Show effect preview before sending.
This commit is contained in:
parent
e120ae6ae6
commit
144109db05
4 changed files with 232 additions and 45 deletions
|
@ -563,6 +563,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
"lng_effect_add_title" = "Add an animated effect";
|
||||
"lng_effect_stickers_title" = "Message Effects";
|
||||
"lng_effect_send" = "Send with Effect";
|
||||
|
||||
"lng_languages" = "Languages";
|
||||
"lng_languages_none" = "No languages found.";
|
||||
|
|
|
@ -228,6 +228,9 @@ void ScheduledMessages::sendNowSimpleMessage(
|
|||
: MTPDmessage::Flag(0))
|
||||
| ((localFlags & MessageFlag::Outgoing)
|
||||
? MTPDmessage::Flag::f_out
|
||||
: MTPDmessage::Flag(0))
|
||||
| (local->effectId()
|
||||
? MTPDmessage::Flag::f_effect
|
||||
: MTPDmessage::Flag(0));
|
||||
const auto views = 1;
|
||||
const auto forwards = 0;
|
||||
|
|
|
@ -11,15 +11,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "base/event_filter.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "chat_helpers/compose/compose_show.h"
|
||||
#include "chat_helpers/stickers_emoji_pack.h"
|
||||
#include "core/shortcuts.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/reactions/history_view_reactions_selector.h"
|
||||
#include "history/view/history_view_schedule_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/shadow.h"
|
||||
#include "ui/painter.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_forum.h"
|
||||
#include "data/data_forum_topic.h"
|
||||
|
@ -48,25 +55,77 @@ public:
|
|||
Details details,
|
||||
QPoint position,
|
||||
const Data::Reaction &effect,
|
||||
Fn<void(Action, Details)> action);
|
||||
Fn<void(Action, Details)> action,
|
||||
Fn<void()> done);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
|
||||
void setupGeometry(QPoint position);
|
||||
void setupBackground();
|
||||
void repaintBackground();
|
||||
void setupLottie();
|
||||
void setupSend(Details details);
|
||||
void createLottie();
|
||||
|
||||
[[nodiscard]] bool checkReady();
|
||||
|
||||
const Data::Reaction _effect;
|
||||
const std::shared_ptr<ChatHelpers::Show> _show;
|
||||
const std::shared_ptr<Ui::ChatTheme> _theme;
|
||||
const std::unique_ptr<Ui::ChatStyle> _chatStyle;
|
||||
const std::unique_ptr<Ui::FlatButton> _send;
|
||||
const Fn<void(Action, Details)> _actionWithEffect;
|
||||
|
||||
QImage _icon;
|
||||
std::shared_ptr<Data::DocumentMedia> _media;
|
||||
QByteArray _bytes;
|
||||
QString _filepath;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _lottie;
|
||||
|
||||
QRect _inner;
|
||||
QImage _bg;
|
||||
|
||||
rpl::lifetime _readyCheckLifetime;
|
||||
|
||||
};
|
||||
|
||||
class BottomRounded final : public Ui::FlatButton {
|
||||
public:
|
||||
using FlatButton::FlatButton;
|
||||
|
||||
private:
|
||||
QImage prepareRippleMask() const override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
};
|
||||
|
||||
QImage BottomRounded::prepareRippleMask() const {
|
||||
const auto fill = false;
|
||||
return Ui::RippleAnimation::MaskByDrawer(size(), fill, [&](QPainter &p) {
|
||||
const auto radius = st::previewMenu.radius;
|
||||
const auto expanded = rect().marginsAdded({ 0, 2 * radius, 0, 0 });
|
||||
p.drawRoundedRect(expanded, radius, radius);
|
||||
});
|
||||
}
|
||||
|
||||
void BottomRounded::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto radius = st::previewMenu.radius;
|
||||
const auto expanded = rect().marginsAdded({ 0, 2 * radius, 0, 0 });
|
||||
p.setPen(Qt::NoPen);
|
||||
const auto &st = st::previewMarkRead;
|
||||
if (isOver()) {
|
||||
p.setBrush(st.overBgColor);
|
||||
}
|
||||
p.drawRoundedRect(expanded, radius, radius);
|
||||
p.end();
|
||||
|
||||
Ui::FlatButton::paintEvent(e);
|
||||
}
|
||||
|
||||
[[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleEffects(
|
||||
not_null<Main::Session*> session) {
|
||||
auto result = Data::PossibleItemReactionsRef();
|
||||
|
@ -85,35 +144,19 @@ private:
|
|||
return result;
|
||||
}
|
||||
|
||||
void ShowEffectPreview(
|
||||
not_null<QWidget*> parent,
|
||||
std::shared_ptr<ChatHelpers::Show> show,
|
||||
Details details,
|
||||
QPoint position,
|
||||
const Data::Reaction &effect,
|
||||
Fn<void(Action, Details)> action) {
|
||||
const auto widget = Ui::CreateChild<EffectPreview>(
|
||||
parent,
|
||||
show,
|
||||
details,
|
||||
position,
|
||||
effect,
|
||||
action);
|
||||
widget->raise();
|
||||
widget->show();
|
||||
}
|
||||
|
||||
[[nodiscard]] Fn<void(Action, Details)> ComposeActionWithEffect(
|
||||
Fn<void(Action, Details)> sendAction,
|
||||
EffectId id) {
|
||||
if (!id) {
|
||||
return sendAction;
|
||||
}
|
||||
EffectId id,
|
||||
Fn<void()> done) {
|
||||
return [=](Action action, Details details) {
|
||||
if (const auto options = std::get_if<Api::SendOptions>(&action)) {
|
||||
options->effectId = id;
|
||||
}
|
||||
const auto onstack = done;
|
||||
sendAction(action, details);
|
||||
if (onstack) {
|
||||
onstack();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -123,27 +166,49 @@ EffectPreview::EffectPreview(
|
|||
Details details,
|
||||
QPoint position,
|
||||
const Data::Reaction &effect,
|
||||
Fn<void(Action, Details)> action)
|
||||
Fn<void(Action, Details)> action,
|
||||
Fn<void()> done)
|
||||
: RpWidget(parent)
|
||||
, _effect(effect)
|
||||
, _show(show)
|
||||
, _theme(Window::Theme::DefaultChatThemeOn(lifetime()))
|
||||
, _chatStyle(
|
||||
std::make_unique<Ui::ChatStyle>(
|
||||
_show->session().colorIndicesValue()))
|
||||
, _send(
|
||||
std::make_unique<Ui::FlatButton>(
|
||||
std::make_unique<BottomRounded>(
|
||||
this,
|
||||
u"Send with Effect"_q,AssertIsDebug()
|
||||
st::previewMarkRead))
|
||||
, _actionWithEffect(ComposeActionWithEffect(action, effect.id.custom())) {
|
||||
tr::lng_effect_send(tr::now),
|
||||
st::effectPreviewSend))
|
||||
, _actionWithEffect(
|
||||
ComposeActionWithEffect(
|
||||
action,
|
||||
effect.id.custom(),
|
||||
done)) {
|
||||
setupGeometry(position);
|
||||
setupBackground();
|
||||
setupLottie();
|
||||
setupSend(details);
|
||||
}
|
||||
|
||||
void EffectPreview::paintEvent(QPaintEvent *e) {
|
||||
auto p = QPainter(this);
|
||||
p.drawImage(0, 0, _bg);
|
||||
|
||||
if (_lottie && _lottie->ready()) {
|
||||
const auto factor = style::DevicePixelRatio();
|
||||
auto request = Lottie::FrameRequest();
|
||||
request.box = _inner.size() * factor;
|
||||
const auto rightAligned = false;// hasRightLayout();
|
||||
if (!rightAligned) {
|
||||
request.mirrorHorizontal = true;
|
||||
}
|
||||
const auto frame = _lottie->frameInfo(request);
|
||||
p.drawImage(
|
||||
QRect(_inner.topLeft(), frame.image.size() / factor),
|
||||
frame.image);
|
||||
_lottie->markFrameShown();
|
||||
}
|
||||
}
|
||||
|
||||
void EffectPreview::mousePressEvent(QMouseEvent *e) {
|
||||
|
@ -156,7 +221,8 @@ void EffectPreview::setupGeometry(QPoint position) {
|
|||
const auto shadow = st::previewMenu.shadow;
|
||||
const auto extend = shadow.extend;
|
||||
_inner = QRect(QPoint(extend.left(), extend.top()), innerSize);
|
||||
const auto size = _inner.marginsAdded(extend).size();
|
||||
const auto size = _inner.marginsAdded(extend).size()
|
||||
+ QSize(0, _send->height());
|
||||
const auto left = std::max(
|
||||
std::min(
|
||||
position.x() - size.width() / 2,
|
||||
|
@ -168,29 +234,112 @@ void EffectPreview::setupGeometry(QPoint position) {
|
|||
position.y() - size.height() / 2,
|
||||
parent->height() - size.height()),
|
||||
topMin);
|
||||
setGeometry(left, top, size.width(), size.height() + _send->height());
|
||||
_send->setGeometry(0, size.height(), size.width(), _send->height());
|
||||
setGeometry(left, top, size.width(), size.height());
|
||||
_send->setGeometry(
|
||||
_inner.x(),
|
||||
_inner.y() + _inner.height(),
|
||||
_inner.width(),
|
||||
_send->height());
|
||||
}
|
||||
|
||||
void EffectPreview::setupBackground() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
_bg = QImage(
|
||||
_inner.size() * ratio,
|
||||
size() * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
_bg.setDevicePixelRatio(ratio);
|
||||
repaintBackground();
|
||||
_theme->repaintBackgroundRequests() | rpl::start_with_next([=] {
|
||||
repaintBackground();
|
||||
update();
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
const auto paint = [=] {
|
||||
auto p = QPainter(&_bg);
|
||||
void EffectPreview::repaintBackground() {
|
||||
const auto ratio = style::DevicePixelRatio();
|
||||
const auto inner = _inner.size() + QSize(0, _send->height());
|
||||
auto bg = QImage(
|
||||
inner * ratio,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
bg.setDevicePixelRatio(ratio);
|
||||
|
||||
{
|
||||
auto p = QPainter(&bg);
|
||||
Window::SectionWidget::PaintBackground(
|
||||
p,
|
||||
_theme.get(),
|
||||
QSize(width(), height() * 5),
|
||||
QRect(QPoint(), size()));
|
||||
};
|
||||
paint();
|
||||
_theme->repaintBackgroundRequests() | rpl::start_with_next([=] {
|
||||
paint();
|
||||
update();
|
||||
}, lifetime());
|
||||
QSize(inner.width(), inner.height() * 5),
|
||||
QRect(QPoint(), inner));
|
||||
|
||||
{ // bubble
|
||||
const auto radius = st::bubbleRadiusLarge;
|
||||
const auto out = 2 * radius;
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(_chatStyle->msgInShadow());
|
||||
const auto skip = st::msgPadding.bottom() - st::msgDateDelta.y();
|
||||
p.drawRoundedRect(-out, -out, out + inner.width() / 3, out + inner.height() / 2 + st::normalFont->height / 2 + st::msgShadow, radius, radius);
|
||||
p.setBrush(_chatStyle->msgInBg());
|
||||
p.drawRoundedRect(-out, -out, out + inner.width() / 3, out + inner.height() / 2 + st::normalFont->height / 2, radius, radius);
|
||||
|
||||
if (!_icon.isNull()) {
|
||||
p.drawImage(
|
||||
inner.width() / 3 - _icon.width(),
|
||||
inner.height() / 2 + st::normalFont->height / 2 - _icon.height(),
|
||||
_icon);
|
||||
}
|
||||
}
|
||||
|
||||
p.fillRect(
|
||||
QRect(0, _inner.height(), _inner.width(), _send->height()),
|
||||
st::previewMarkRead.bgColor);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
auto roundRect = Ui::RoundRect(st::previewMenu.radius, st::menuBg);
|
||||
roundRect.paint(p, QRect(QPoint(), inner), RectPart::AllCorners);
|
||||
}
|
||||
|
||||
_bg.fill(Qt::transparent);
|
||||
auto p = QPainter(&_bg);
|
||||
const auto &shadow = st::previewMenu.animation.shadow;
|
||||
const auto shadowed = QRect(_inner.topLeft(), inner);
|
||||
Ui::Shadow::paint(p, shadowed, width(), shadow);
|
||||
p.drawImage(_inner.topLeft(), bg);
|
||||
}
|
||||
|
||||
void EffectPreview::setupLottie() {
|
||||
const auto id = _effect.id.custom();
|
||||
const auto reactions = &_show->session().data().reactions();
|
||||
reactions->preloadEffectImageFor(id);
|
||||
|
||||
if (const auto document = _effect.aroundAnimation) {
|
||||
_media = document->createMediaView();
|
||||
} else {
|
||||
_media = _effect.selectAnimation->createMediaView();
|
||||
}
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
_show->session().downloaderTaskFinished()
|
||||
) | rpl::start_with_next([=] {
|
||||
if (checkReady()) {
|
||||
_readyCheckLifetime.destroy();
|
||||
createLottie();
|
||||
}
|
||||
}, _readyCheckLifetime);
|
||||
}
|
||||
|
||||
void EffectPreview::createLottie() {
|
||||
_lottie = _show->session().emojiStickersPack().effectPlayer(
|
||||
_media->owner(),
|
||||
_bytes,
|
||||
_filepath,
|
||||
Stickers::EffectType::MessageEffect);
|
||||
const auto raw = _lottie.get();
|
||||
raw->updates(
|
||||
) | rpl::start_with_next([=](Lottie::Update update) {
|
||||
v::match(update.data, [&](const Lottie::Information &information) {
|
||||
}, [&](const Lottie::DisplayFrameRequest &request) {
|
||||
this->update();
|
||||
});
|
||||
}, raw->lifetime());
|
||||
}
|
||||
|
||||
void EffectPreview::setupSend(Details details) {
|
||||
|
@ -203,6 +352,22 @@ void EffectPreview::setupSend(Details details) {
|
|||
}, _actionWithEffect);
|
||||
}
|
||||
|
||||
bool EffectPreview::checkReady() {
|
||||
if (_icon.isNull()) {
|
||||
const auto reactions = &_show->session().data().reactions();
|
||||
_icon = reactions->resolveEffectImageFor(_effect.id.custom());
|
||||
repaintBackground();
|
||||
update();
|
||||
}
|
||||
if (_effect.aroundAnimation) {
|
||||
_bytes = _media->bytes();
|
||||
_filepath = _media->owner()->filepath();
|
||||
} else {
|
||||
_bytes = _media->videoThumbnailContent();
|
||||
}
|
||||
return !_icon.isNull() && (!_bytes.isEmpty() || !_filepath.isEmpty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Fn<void(Action, Details)> DefaultCallback(
|
||||
|
@ -281,19 +446,32 @@ FillMenuResult FillSendMenu(
|
|||
return FillMenuResult::Prepared;
|
||||
}
|
||||
|
||||
const auto effect = std::make_shared<QPointer<EffectPreview>>();
|
||||
(*selector)->chosen(
|
||||
) | rpl::start_with_next([=](ChosenReaction chosen) {
|
||||
const auto &reactions = showForEffect->session().data().reactions();
|
||||
const auto &effects = reactions.list(Data::Reactions::Type::Effects);
|
||||
const auto i = ranges::find(effects, chosen.id, &Data::Reaction::id);
|
||||
if (i != end(effects)) {
|
||||
ShowEffectPreview(
|
||||
if (const auto strong = effect->data()) {
|
||||
delete strong;
|
||||
}
|
||||
const auto weak = Ui::MakeWeak(menu);
|
||||
const auto done = [=] {
|
||||
delete effect->data();
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->hideMenu(true);
|
||||
}
|
||||
};
|
||||
*effect = Ui::CreateChild<EffectPreview>(
|
||||
menu,
|
||||
showForEffect,
|
||||
details,
|
||||
menu->mapFromGlobal(chosen.globalGeometry.center()),
|
||||
*i,
|
||||
action);
|
||||
action,
|
||||
crl::guard(menu, done));
|
||||
(*effect)->show();
|
||||
}
|
||||
}, menu->lifetime());
|
||||
|
||||
|
|
|
@ -1101,7 +1101,7 @@ previewTop: PeerListItem(defaultPeerListItem) {
|
|||
photoSize: 40px;
|
||||
}
|
||||
previewMarkRead: FlatButton(historyComposeButton) {
|
||||
height: 40px;
|
||||
height: 39px;
|
||||
textTop: 10px;
|
||||
}
|
||||
previewName: FlatLabel(defaultFlatLabel) {
|
||||
|
@ -1115,3 +1115,8 @@ previewUserpic: UserpicButton(defaultUserpicButton) {
|
|||
size: size(40px, 40px);
|
||||
photoSize: 40px;
|
||||
}
|
||||
|
||||
effectPreviewSend: FlatButton(previewMarkRead) {
|
||||
bgColor: transparent;
|
||||
overBgColor: transparent;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue