mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Implement basic effect animation.
This commit is contained in:
parent
f762634036
commit
a19e71324b
19 changed files with 258 additions and 19 deletions
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
#include "data/data_saved_sublist.h"
|
#include "data/data_saved_sublist.h"
|
||||||
#include "data/stickers/data_custom_emoji.h"
|
#include "data/stickers/data_custom_emoji.h"
|
||||||
|
@ -594,6 +595,9 @@ void Reactions::preloadImageFor(const ReactionId &id) {
|
||||||
} else {
|
} else {
|
||||||
generateImage(set, i->title);
|
generateImage(set, i->title);
|
||||||
}
|
}
|
||||||
|
if (set.effect) {
|
||||||
|
preloadEffect(*i);
|
||||||
|
}
|
||||||
} else if (set.effect && !_waitingForEffects) {
|
} else if (set.effect && !_waitingForEffects) {
|
||||||
_waitingForEffects = true;
|
_waitingForEffects = true;
|
||||||
refreshEffects();
|
refreshEffects();
|
||||||
|
@ -603,6 +607,15 @@ void Reactions::preloadImageFor(const ReactionId &id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Reactions::preloadEffect(const Reaction &effect) {
|
||||||
|
if (effect.aroundAnimation) {
|
||||||
|
effect.aroundAnimation->createMediaView()->checkStickerLarge();
|
||||||
|
} else {
|
||||||
|
const auto premium = effect.selectAnimation;
|
||||||
|
premium->loadVideoThumbnail(premium->stickerSetOrigin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Reactions::preloadAnimationsFor(const ReactionId &id) {
|
void Reactions::preloadAnimationsFor(const ReactionId &id) {
|
||||||
const auto custom = id.custom();
|
const auto custom = id.custom();
|
||||||
const auto document = custom ? _owner->document(custom).get() : nullptr;
|
const auto document = custom ? _owner->document(custom).get() : nullptr;
|
||||||
|
|
|
@ -212,6 +212,7 @@ private:
|
||||||
[[nodiscard]] std::optional<Reaction> parse(
|
[[nodiscard]] std::optional<Reaction> parse(
|
||||||
const MTPAvailableEffect &entry);
|
const MTPAvailableEffect &entry);
|
||||||
|
|
||||||
|
void preloadEffect(const Reaction &effect);
|
||||||
void preloadImageFor(const ReactionId &id);
|
void preloadImageFor(const ReactionId &id);
|
||||||
[[nodiscard]] QImage resolveImageFor(const ReactionId &id);
|
[[nodiscard]] QImage resolveImageFor(const ReactionId &id);
|
||||||
void loadImage(
|
void loadImage(
|
||||||
|
|
|
@ -321,6 +321,8 @@ enum class MessageFlag : uint64 {
|
||||||
ReactionsAreTags = (1ULL << 43),
|
ReactionsAreTags = (1ULL << 43),
|
||||||
|
|
||||||
ShortcutMessage = (1ULL << 44),
|
ShortcutMessage = (1ULL << 44),
|
||||||
|
|
||||||
|
EffectWatchedLocal = (1ULL << 45),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||||
using MessageFlags = base::flags<MessageFlag>;
|
using MessageFlags = base::flags<MessageFlag>;
|
||||||
|
|
|
@ -674,6 +674,11 @@ void InnerWidget::elementStartPremium(
|
||||||
void InnerWidget::elementCancelPremium(not_null<const Element*> view) {
|
void InnerWidget::elementCancelPremium(not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InnerWidget::elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) {
|
||||||
|
}
|
||||||
|
|
||||||
QString InnerWidget::elementAuthorRank(not_null<const Element*> view) {
|
QString InnerWidget::elementAuthorRank(not_null<const Element*> view) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,9 @@ public:
|
||||||
HistoryView::Element *replacing) override;
|
HistoryView::Element *replacing) override;
|
||||||
void elementCancelPremium(
|
void elementCancelPremium(
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
|
void elementStartEffect(
|
||||||
|
not_null<const HistoryView::Element*> view,
|
||||||
|
HistoryView::Element *replacing) override;
|
||||||
QString elementAuthorRank(
|
QString elementAuthorRank(
|
||||||
not_null<const HistoryView::Element*> view) override;
|
not_null<const HistoryView::Element*> view) override;
|
||||||
|
|
||||||
|
|
|
@ -304,12 +304,18 @@ public:
|
||||||
_widget->elementStartPremium(view, replacing);
|
_widget->elementStartPremium(view, replacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void elementCancelPremium(not_null<const Element*> view) override {
|
void elementCancelPremium(not_null<const Element*> view) override {
|
||||||
if (_widget) {
|
if (_widget) {
|
||||||
_widget->elementCancelPremium(view);
|
_widget->elementCancelPremium(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) override {
|
||||||
|
if (_widget) {
|
||||||
|
_widget->elementStartEffect(view, replacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString elementAuthorRank(not_null<const Element*> view) override {
|
QString elementAuthorRank(not_null<const Element*> view) override {
|
||||||
return {};
|
return {};
|
||||||
|
@ -950,6 +956,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
_translateTracker->startBunch();
|
_translateTracker->startBunch();
|
||||||
auto readTill = (HistoryItem*)nullptr;
|
auto readTill = (HistoryItem*)nullptr;
|
||||||
auto readContents = base::flat_set<not_null<HistoryItem*>>();
|
auto readContents = base::flat_set<not_null<HistoryItem*>>();
|
||||||
|
auto startEffects = base::flat_set<not_null<const Element*>>();
|
||||||
const auto markingAsViewed = _widget->markingContentsRead();
|
const auto markingAsViewed = _widget->markingContentsRead();
|
||||||
const auto guard = gsl::finally([&] {
|
const auto guard = gsl::finally([&] {
|
||||||
if (_pinnedItem) {
|
if (_pinnedItem) {
|
||||||
|
@ -958,6 +965,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
_translateTracker->finishBunch();
|
_translateTracker->finishBunch();
|
||||||
if (readTill && _widget->markingMessagesRead()) {
|
if (readTill && _widget->markingMessagesRead()) {
|
||||||
session().data().histories().readInboxTill(readTill);
|
session().data().histories().readInboxTill(readTill);
|
||||||
|
if (!startEffects.empty()) {
|
||||||
|
for (const auto &view : startEffects) {
|
||||||
|
_emojiInteractions->playEffectOnRead(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (markingAsViewed && !readContents.empty()) {
|
if (markingAsViewed && !readContents.empty()) {
|
||||||
session().api().markContentsRead(readContents);
|
session().api().markContentsRead(readContents);
|
||||||
|
@ -991,6 +1003,9 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
session().sponsoredMessages().view(item->fullId());
|
session().sponsoredMessages().view(item->fullId());
|
||||||
} else if (isUnread) {
|
} else if (isUnread) {
|
||||||
readTill = item;
|
readTill = item;
|
||||||
|
if (item->hasUnwatchedEffect()) {
|
||||||
|
startEffects.emplace(view);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (markingAsViewed && item->hasViews()) {
|
if (markingAsViewed && item->hasViews()) {
|
||||||
session().api().views().scheduleIncrement(item);
|
session().api().views().scheduleIncrement(item);
|
||||||
|
@ -3568,6 +3583,12 @@ void HistoryInner::elementCancelPremium(not_null<const Element*> view) {
|
||||||
_emojiInteractions->cancelPremiumEffect(view);
|
_emojiInteractions->cancelPremiumEffect(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryInner::elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) {
|
||||||
|
_emojiInteractions->playEffect(view);
|
||||||
|
}
|
||||||
|
|
||||||
auto HistoryInner::getSelectionState() const
|
auto HistoryInner::getSelectionState() const
|
||||||
-> HistoryView::TopBarWidget::SelectedState {
|
-> HistoryView::TopBarWidget::SelectedState {
|
||||||
auto result = HistoryView::TopBarWidget::SelectedState {};
|
auto result = HistoryView::TopBarWidget::SelectedState {};
|
||||||
|
|
|
@ -170,6 +170,9 @@ public:
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
Element *replacing);
|
Element *replacing);
|
||||||
void elementCancelPremium(not_null<const Element*> view);
|
void elementCancelPremium(not_null<const Element*> view);
|
||||||
|
void elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing);
|
||||||
|
|
||||||
void updateBotInfo(bool recount = true);
|
void updateBotInfo(bool recount = true);
|
||||||
|
|
||||||
|
|
|
@ -687,6 +687,9 @@ HistoryItem::HistoryItem(
|
||||||
if (isHistoryEntry() && IsClientMsgId(id)) {
|
if (isHistoryEntry() && IsClientMsgId(id)) {
|
||||||
_history->registerClientSideMessage(this);
|
_history->registerClientSideMessage(this);
|
||||||
}
|
}
|
||||||
|
if (_effectId) {
|
||||||
|
_history->owner().reactions().preloadEffectImageFor(_effectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HistoryItem::HistoryItem(
|
HistoryItem::HistoryItem(
|
||||||
|
@ -1283,6 +1286,21 @@ bool HistoryItem::hasUnreadReaction() const {
|
||||||
return (_flags & MessageFlag::HasUnreadReaction);
|
return (_flags & MessageFlag::HasUnreadReaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::hasUnwatchedEffect() const {
|
||||||
|
return !out()
|
||||||
|
&& effectId()
|
||||||
|
&& !(_flags & MessageFlag::EffectWatchedLocal)
|
||||||
|
&& unread(history());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::markEffectWatched() {
|
||||||
|
if (!hasUnwatchedEffect()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_flags |= MessageFlag::EffectWatchedLocal;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryItem::mentionsMe() const {
|
bool HistoryItem::mentionsMe() const {
|
||||||
if (Has<HistoryServicePinned>()
|
if (Has<HistoryServicePinned>()
|
||||||
&& !Core::App().settings().notifyAboutPinned()) {
|
&& !Core::App().settings().notifyAboutPinned()) {
|
||||||
|
|
|
@ -241,6 +241,8 @@ public:
|
||||||
[[nodiscard]] bool mentionsMe() const;
|
[[nodiscard]] bool mentionsMe() const;
|
||||||
[[nodiscard]] bool isUnreadMention() const;
|
[[nodiscard]] bool isUnreadMention() const;
|
||||||
[[nodiscard]] bool hasUnreadReaction() const;
|
[[nodiscard]] bool hasUnreadReaction() const;
|
||||||
|
[[nodiscard]] bool hasUnwatchedEffect() const;
|
||||||
|
bool markEffectWatched();
|
||||||
[[nodiscard]] bool isUnreadMedia() const;
|
[[nodiscard]] bool isUnreadMedia() const;
|
||||||
[[nodiscard]] bool isIncomingUnreadMedia() const;
|
[[nodiscard]] bool isIncomingUnreadMedia() const;
|
||||||
[[nodiscard]] bool hasUnreadMediaFlag() const;
|
[[nodiscard]] bool hasUnreadMediaFlag() const;
|
||||||
|
|
|
@ -1389,7 +1389,10 @@ int HistoryWidget::itemTopForHighlight(
|
||||||
const auto itemTop = _list->itemTop(view);
|
const auto itemTop = _list->itemTop(view);
|
||||||
Assert(itemTop >= 0);
|
Assert(itemTop >= 0);
|
||||||
|
|
||||||
const auto reactionCenter = view->data()->hasUnreadReaction()
|
const auto item = view->data();
|
||||||
|
const auto unwatchedEffect = item->hasUnwatchedEffect();
|
||||||
|
const auto showReactions = item->hasUnreadReaction() || unwatchedEffect;
|
||||||
|
const auto reactionCenter = showReactions
|
||||||
? view->reactionButtonParameters({}, {}).center.y()
|
? view->reactionButtonParameters({}, {}).center.y()
|
||||||
: -1;
|
: -1;
|
||||||
|
|
||||||
|
@ -2376,8 +2379,6 @@ void HistoryWidget::showHistory(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
session().data().reactions().refreshEffects();
|
|
||||||
|
|
||||||
_scroll->hide();
|
_scroll->hide();
|
||||||
_list = _scroll->setOwnedWidget(
|
_list = _scroll->setOwnedWidget(
|
||||||
object_ptr<HistoryInner>(this, _scroll, controller(), _history));
|
object_ptr<HistoryInner>(this, _scroll, controller(), _history));
|
||||||
|
|
|
@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/view/history_view_message.h"
|
#include "history/view/history_view_message.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "chat_helpers/emoji_interactions.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "lottie/lottie_icon.h"
|
#include "lottie/lottie_icon.h"
|
||||||
|
@ -104,10 +105,11 @@ bool BottomInfo::isWide() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextState BottomInfo::textState(
|
TextState BottomInfo::textState(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const Message*> view,
|
||||||
QPoint position) const {
|
QPoint position) const {
|
||||||
|
const auto item = view->data();
|
||||||
auto result = TextState(item);
|
auto result = TextState(item);
|
||||||
if (const auto link = replayEffectLink(item, position)) {
|
if (const auto link = replayEffectLink(view, position)) {
|
||||||
result.link = link;
|
result.link = link;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +160,7 @@ TextState BottomInfo::textState(
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr BottomInfo::replayEffectLink(
|
ClickHandlerPtr BottomInfo::replayEffectLink(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const Message*> view,
|
||||||
QPoint position) const {
|
QPoint position) const {
|
||||||
if (!_effect) {
|
if (!_effect) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -189,7 +191,7 @@ ClickHandlerPtr BottomInfo::replayEffectLink(
|
||||||
st::msgDateFont->height);
|
st::msgDateFont->height);
|
||||||
if (image.contains(position)) {
|
if (image.contains(position)) {
|
||||||
if (!_replayLink) {
|
if (!_replayLink) {
|
||||||
_replayLink = replayEffectLink(item);
|
_replayLink = replayEffectLink(view);
|
||||||
}
|
}
|
||||||
return _replayLink;
|
return _replayLink;
|
||||||
}
|
}
|
||||||
|
@ -200,15 +202,17 @@ ClickHandlerPtr BottomInfo::replayEffectLink(
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickHandlerPtr BottomInfo::replayEffectLink(
|
ClickHandlerPtr BottomInfo::replayEffectLink(
|
||||||
not_null<const HistoryItem*> item) const {
|
not_null<const Message*> view) const {
|
||||||
|
const auto item = view->data();
|
||||||
const auto itemId = item->fullId();
|
const auto itemId = item->fullId();
|
||||||
const auto sessionId = item->history()->session().uniqueId();
|
const auto sessionId = item->history()->session().uniqueId();
|
||||||
return std::make_shared<LambdaClickHandler>([=](
|
const auto weak = base::make_weak(view);
|
||||||
ClickContext context) {
|
return std::make_shared<LambdaClickHandler>([=](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()) {
|
||||||
controller->showToast("playing nice effect..");
|
if (const auto strong = weak.get()) {
|
||||||
AssertIsDebug();
|
strong->delegate()->elementStartEffect(strong, nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ public:
|
||||||
[[nodiscard]] int firstLineWidth() const;
|
[[nodiscard]] int firstLineWidth() const;
|
||||||
[[nodiscard]] bool isWide() const;
|
[[nodiscard]] bool isWide() const;
|
||||||
[[nodiscard]] TextState textState(
|
[[nodiscard]] TextState textState(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const Message*> view,
|
||||||
QPoint position) const;
|
QPoint position) const;
|
||||||
[[nodiscard]] bool isSignedAuthorElided() const;
|
[[nodiscard]] bool isSignedAuthorElided() const;
|
||||||
|
|
||||||
|
@ -106,10 +106,10 @@ private:
|
||||||
|
|
||||||
[[nodiscard]] Effect prepareEffectWithId(EffectId id);
|
[[nodiscard]] Effect prepareEffectWithId(EffectId id);
|
||||||
[[nodiscard]] ClickHandlerPtr replayEffectLink(
|
[[nodiscard]] ClickHandlerPtr replayEffectLink(
|
||||||
not_null<const HistoryItem*> item,
|
not_null<const Message*> view,
|
||||||
QPoint position) const;
|
QPoint position) const;
|
||||||
[[nodiscard]] ClickHandlerPtr replayEffectLink(
|
[[nodiscard]] ClickHandlerPtr replayEffectLink(
|
||||||
not_null<const HistoryItem*> item) const;
|
not_null<const Message*> view) const;
|
||||||
|
|
||||||
const not_null<::Data::Reactions*> _reactionsOwner;
|
const not_null<::Data::Reactions*> _reactionsOwner;
|
||||||
Data _data;
|
Data _data;
|
||||||
|
|
|
@ -192,6 +192,11 @@ void DefaultElementDelegate::elementCancelPremium(
|
||||||
not_null<const Element*> view) {
|
not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DefaultElementDelegate::elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) {
|
||||||
|
}
|
||||||
|
|
||||||
QString DefaultElementDelegate::elementAuthorRank(
|
QString DefaultElementDelegate::elementAuthorRank(
|
||||||
not_null<const Element*> view) {
|
not_null<const Element*> view) {
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -113,6 +113,9 @@ public:
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
Element *replacing) = 0;
|
Element *replacing) = 0;
|
||||||
virtual void elementCancelPremium(not_null<const Element*> view) = 0;
|
virtual void elementCancelPremium(not_null<const Element*> view) = 0;
|
||||||
|
virtual void elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) = 0;
|
||||||
virtual QString elementAuthorRank(not_null<const Element*> view) = 0;
|
virtual QString elementAuthorRank(not_null<const Element*> view) = 0;
|
||||||
|
|
||||||
virtual ~ElementDelegate() {
|
virtual ~ElementDelegate() {
|
||||||
|
@ -163,6 +166,9 @@ public:
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
Element *replacing) override;
|
Element *replacing) override;
|
||||||
void elementCancelPremium(not_null<const Element*> view) override;
|
void elementCancelPremium(not_null<const Element*> view) override;
|
||||||
|
void elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) override;
|
||||||
QString elementAuthorRank(not_null<const Element*> view) override;
|
QString elementAuthorRank(not_null<const Element*> view) override;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/media/history_view_sticker.h"
|
#include "history/view/media/history_view_sticker.h"
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
#include "chat_helpers/stickers_emoji_pack.h"
|
#include "chat_helpers/stickers_emoji_pack.h"
|
||||||
#include "chat_helpers/emoji_interactions.h"
|
#include "chat_helpers/emoji_interactions.h"
|
||||||
#include "chat_helpers/stickers_lottie.h"
|
#include "chat_helpers/stickers_lottie.h"
|
||||||
|
@ -17,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_document_media.h"
|
#include "data/data_document_media.h"
|
||||||
|
#include "data/data_message_reactions.h"
|
||||||
#include "lottie/lottie_common.h"
|
#include "lottie/lottie_common.h"
|
||||||
#include "lottie/lottie_single_player.h"
|
#include "lottie/lottie_single_player.h"
|
||||||
#include "base/random.h"
|
#include "base/random.h"
|
||||||
|
@ -45,8 +47,8 @@ constexpr auto kDropDelayedAfterDelay = crl::time(2000);
|
||||||
EmojiInteractions::EmojiInteractions(
|
EmojiInteractions::EmojiInteractions(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
Fn<int(not_null<const Element*>)> itemTop)
|
Fn<int(not_null<const Element*>)> itemTop)
|
||||||
: _session(session)
|
: _session(session)
|
||||||
, _itemTop(std::move(itemTop)) {
|
, _itemTop(std::move(itemTop)) {
|
||||||
_session->data().viewRemoved(
|
_session->data().viewRemoved(
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return !_plays.empty() || !_delayed.empty();
|
return !_plays.empty() || !_delayed.empty();
|
||||||
|
@ -56,6 +58,11 @@ EmojiInteractions::EmojiInteractions(
|
||||||
ranges::remove(_delayed, view, &Delayed::view),
|
ranges::remove(_delayed, view, &Delayed::view),
|
||||||
end(_delayed));
|
end(_delayed));
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
|
_session->data().reactions().effectsUpdates(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
checkPendingEffects();
|
||||||
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiInteractions::~EmojiInteractions() = default;
|
EmojiInteractions::~EmojiInteractions() = default;
|
||||||
|
@ -143,6 +150,121 @@ void EmojiInteractions::play(
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::playEffectOnRead(not_null<const Element*> view) {
|
||||||
|
if (view->data()->markEffectWatched()) {
|
||||||
|
playEffect(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::playEffect(not_null<const Element*> view) {
|
||||||
|
if (const auto resolved = resolveEffect(view)) {
|
||||||
|
playEffect(view, resolved);
|
||||||
|
} else if (view->data()->effectId()) {
|
||||||
|
if (resolved.document && !_downloadLifetime) {
|
||||||
|
_downloadLifetime = _session->downloaderTaskFinished(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
checkPendingEffects();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addPendingEffect(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiInteractions::ResolvedEffect EmojiInteractions::resolveEffect(
|
||||||
|
not_null<const Element*> view) {
|
||||||
|
const auto item = view->data();
|
||||||
|
const auto effectId = item->effectId();
|
||||||
|
if (!effectId) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
using Type = Data::Reactions::Type;
|
||||||
|
const auto &effects = _session->data().reactions().list(Type::Effects);
|
||||||
|
const auto i = ranges::find(
|
||||||
|
effects,
|
||||||
|
Data::ReactionId{ effectId },
|
||||||
|
&Data::Reaction::id);
|
||||||
|
if (i == end(effects)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto document = (DocumentData*)nullptr;
|
||||||
|
auto content = QByteArray();
|
||||||
|
auto filepath = QString();
|
||||||
|
if ((document = i->aroundAnimation)) {
|
||||||
|
content = document->createMediaView()->bytes();
|
||||||
|
filepath = document->filepath();
|
||||||
|
} else {
|
||||||
|
document = i->selectAnimation;
|
||||||
|
content = document->createMediaView()->videoThumbnailContent();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
.emoticon = i->title,
|
||||||
|
.document = document,
|
||||||
|
.content = content,
|
||||||
|
.filepath = filepath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::playEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
const ResolvedEffect &resolved) {
|
||||||
|
play(
|
||||||
|
resolved.emoticon,
|
||||||
|
view,
|
||||||
|
resolved.document,
|
||||||
|
resolved.content,
|
||||||
|
resolved.filepath,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::addPendingEffect(not_null<const Element*> view) {
|
||||||
|
auto found = false;
|
||||||
|
const auto predicate = [&](base::weak_ptr<const Element> weak) {
|
||||||
|
const auto strong = weak.get();
|
||||||
|
if (strong == view) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
return !strong;
|
||||||
|
};
|
||||||
|
_pendingEffects.erase(
|
||||||
|
ranges::remove_if(_pendingEffects, predicate),
|
||||||
|
end(_pendingEffects));
|
||||||
|
if (!found) {
|
||||||
|
_pendingEffects.push_back(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmojiInteractions::checkPendingEffects() {
|
||||||
|
auto waitingDownload = false;
|
||||||
|
const auto predicate = [&](base::weak_ptr<const Element> weak) {
|
||||||
|
const auto strong = weak.get();
|
||||||
|
if (!strong) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto resolved = resolveEffect(strong);
|
||||||
|
if (resolved) {
|
||||||
|
playEffect(strong, resolved);
|
||||||
|
return true;
|
||||||
|
} else if (!strong->data()->effectId()) {
|
||||||
|
return true;
|
||||||
|
} else if (resolved.document) {
|
||||||
|
waitingDownload = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
_pendingEffects.erase(
|
||||||
|
ranges::remove_if(_pendingEffects, predicate),
|
||||||
|
end(_pendingEffects));
|
||||||
|
if (!waitingDownload) {
|
||||||
|
_downloadLifetime.destroy();
|
||||||
|
} else if (!_downloadLifetime) {
|
||||||
|
_downloadLifetime = _session->downloaderTaskFinished(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
checkPendingEffects();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EmojiInteractions::play(
|
void EmojiInteractions::play(
|
||||||
QString emoticon,
|
QString emoticon,
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
|
|
|
@ -43,6 +43,9 @@ public:
|
||||||
void cancelPremiumEffect(not_null<const Element*> view);
|
void cancelPremiumEffect(not_null<const Element*> view);
|
||||||
void visibleAreaUpdated(int visibleTop, int visibleBottom);
|
void visibleAreaUpdated(int visibleTop, int visibleBottom);
|
||||||
|
|
||||||
|
void playEffectOnRead(not_null<const Element*> view);
|
||||||
|
void playEffect(not_null<const Element*> view);
|
||||||
|
|
||||||
void paint(QPainter &p);
|
void paint(QPainter &p);
|
||||||
[[nodiscard]] rpl::producer<QRect> updateRequests() const;
|
[[nodiscard]] rpl::producer<QRect> updateRequests() const;
|
||||||
[[nodiscard]] rpl::producer<QString> playStarted() const;
|
[[nodiscard]] rpl::producer<QString> playStarted() const;
|
||||||
|
@ -68,6 +71,16 @@ private:
|
||||||
crl::time shouldHaveStartedAt = 0;
|
crl::time shouldHaveStartedAt = 0;
|
||||||
bool incoming = false;
|
bool incoming = false;
|
||||||
};
|
};
|
||||||
|
struct ResolvedEffect {
|
||||||
|
QString emoticon;
|
||||||
|
DocumentData *document = nullptr;
|
||||||
|
QByteArray content;
|
||||||
|
QString filepath;
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return document && (!content.isEmpty() || !filepath.isEmpty());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] QRect computeRect(const Play &play) const;
|
[[nodiscard]] QRect computeRect(const Play &play) const;
|
||||||
|
|
||||||
|
@ -85,6 +98,14 @@ private:
|
||||||
bool incoming,
|
bool incoming,
|
||||||
bool premium);
|
bool premium);
|
||||||
void checkDelayed();
|
void checkDelayed();
|
||||||
|
void addPendingEffect(not_null<const Element*> view);
|
||||||
|
|
||||||
|
[[nodiscard]] ResolvedEffect resolveEffect(
|
||||||
|
not_null<const Element*> view);
|
||||||
|
void playEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
const ResolvedEffect &resolved);
|
||||||
|
void checkPendingEffects();
|
||||||
|
|
||||||
const not_null<Main::Session*> _session;
|
const not_null<Main::Session*> _session;
|
||||||
const Fn<int(not_null<const Element*>)> _itemTop;
|
const Fn<int(not_null<const Element*>)> _itemTop;
|
||||||
|
@ -97,6 +118,9 @@ private:
|
||||||
rpl::event_stream<QRect> _updateRequests;
|
rpl::event_stream<QRect> _updateRequests;
|
||||||
rpl::event_stream<QString> _playStarted;
|
rpl::event_stream<QString> _playStarted;
|
||||||
|
|
||||||
|
std::vector<base::weak_ptr<const Element>> _pendingEffects;
|
||||||
|
rpl::lifetime _downloadLifetime;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1857,6 +1857,12 @@ void ListWidget::elementCancelPremium(not_null<const Element*> view) {
|
||||||
_emojiInteractions->cancelPremiumEffect(view);
|
_emojiInteractions->cancelPremiumEffect(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ListWidget::elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) {
|
||||||
|
_emojiInteractions->playEffect(view);
|
||||||
|
}
|
||||||
|
|
||||||
QString ListWidget::elementAuthorRank(not_null<const Element*> view) {
|
QString ListWidget::elementAuthorRank(not_null<const Element*> view) {
|
||||||
return _delegate->listElementAuthorRank(view);
|
return _delegate->listElementAuthorRank(view);
|
||||||
}
|
}
|
||||||
|
|
|
@ -419,6 +419,9 @@ public:
|
||||||
not_null<const Element*> view,
|
not_null<const Element*> view,
|
||||||
Element *replacing) override;
|
Element *replacing) override;
|
||||||
void elementCancelPremium(not_null<const Element*> view) override;
|
void elementCancelPremium(not_null<const Element*> view) override;
|
||||||
|
void elementStartEffect(
|
||||||
|
not_null<const Element*> view,
|
||||||
|
Element *replacing) override;
|
||||||
QString elementAuthorRank(not_null<const Element*> view) override;
|
QString elementAuthorRank(not_null<const Element*> view) override;
|
||||||
|
|
||||||
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
|
void setEmptyInfoWidget(base::unique_qptr<Ui::RpWidget> &&w);
|
||||||
|
|
|
@ -3038,7 +3038,7 @@ TextState Message::bottomInfoTextState(
|
||||||
const auto infoLeft = infoRight - size.width();
|
const auto infoLeft = infoRight - size.width();
|
||||||
const auto infoTop = infoBottom - size.height();
|
const auto infoTop = infoBottom - size.height();
|
||||||
return _bottomInfo.textState(
|
return _bottomInfo.textState(
|
||||||
data(),
|
this,
|
||||||
point - QPoint{ infoLeft, infoTop });
|
point - QPoint{ infoLeft, infoTop });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue