mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Fly + effects when choosing an emoji status.
This commit is contained in:
parent
d4810713cb
commit
5cc6275fc3
38 changed files with 941 additions and 1055 deletions
|
@ -807,8 +807,12 @@ PRIVATE
|
|||
info/polls/info_polls_results_widget.h
|
||||
info/profile/info_profile_actions.cpp
|
||||
info/profile/info_profile_actions.h
|
||||
info/profile/info_profile_badge.cpp
|
||||
info/profile/info_profile_badge.h
|
||||
info/profile/info_profile_cover.cpp
|
||||
info/profile/info_profile_cover.h
|
||||
info/profile/info_profile_emoji_status_panel.cpp
|
||||
info/profile/info_profile_emoji_status_panel.h
|
||||
info/profile/info_profile_icon.cpp
|
||||
info/profile/info_profile_icon.h
|
||||
info/profile/info_profile_inner_widget.cpp
|
||||
|
|
|
@ -1708,6 +1708,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_premium_unlock_reactions" = "Unlock Premium Reactions";
|
||||
"lng_premium_unlock_stickers" = "Unlock Premium Stickers";
|
||||
"lng_premium_unlock_emoji" = "Unlock Animated Emoji";
|
||||
"lng_premium_unlock_status" = "Unlock Emoji Status";
|
||||
|
||||
"lng_premium_subscribe_months_12" = "Annual";
|
||||
"lng_premium_subscribe_months_6" = "Semiannual";
|
||||
|
@ -1741,8 +1742,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_premium_summary_about_voice_to_text" = "Ability to read the transcript of any incoming voice message.";
|
||||
"lng_premium_summary_subtitle_no_ads" = "No Ads";
|
||||
"lng_premium_summary_about_no_ads" = "No more ads in public channels where Telegram sometimes shows ads.";
|
||||
"lng_premium_summary_subtitle_unique_reactions" = "Unique Reactions";
|
||||
"lng_premium_summary_about_unique_reactions" = "Additional animated reactions on messages available only to the Premium subscribers.";
|
||||
"lng_premium_summary_subtitle_emoji_status" = "Emoji Status";
|
||||
"lng_premium_summary_about_emoji_status" = "Add any of thousands emoji next to your name to display current activity.";
|
||||
"lng_premium_summary_subtitle_infinite_reactions" = "Infinite Reactions";
|
||||
"lng_premium_summary_about_infinite_reactions" = "React with thousands of emoji — with multiple reactions per message.";
|
||||
"lng_premium_summary_subtitle_premium_stickers" = "Premium Stickers";
|
||||
"lng_premium_summary_about_premium_stickers" = "Exclusive enlarged stickers featuring additional effects, updated monthly.";
|
||||
"lng_premium_summary_subtitle_animated_emoji" = "Animated Emoji";
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/peers/edit_peer_common.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone
|
||||
#include "ui/text/text_utilities.h"
|
||||
|
|
|
@ -12,7 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_file_origin.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_message_reactions.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_peer_values.h"
|
||||
|
@ -63,7 +62,6 @@ using Data::ReactionId;
|
|||
struct Descriptor {
|
||||
PremiumPreview section = PremiumPreview::Stickers;
|
||||
DocumentData *requestedSticker = nullptr;
|
||||
base::flat_map<ReactionId, ReactionDisableType> disabled;
|
||||
bool fromSettings = false;
|
||||
Fn<void()> hiddenCallback;
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shownCallback;
|
||||
|
@ -72,7 +70,6 @@ struct Descriptor {
|
|||
bool operator==(const Descriptor &a, const Descriptor &b) {
|
||||
return (a.section == b.section)
|
||||
&& (a.requestedSticker == b.requestedSticker)
|
||||
&& (a.disabled == b.disabled)
|
||||
&& (a.fromSettings == b.fromSettings);
|
||||
}
|
||||
|
||||
|
@ -120,8 +117,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
|||
return tr::lng_premium_summary_subtitle_voice_to_text();
|
||||
case PremiumPreview::NoAds:
|
||||
return tr::lng_premium_summary_subtitle_no_ads();
|
||||
case PremiumPreview::Reactions:
|
||||
return tr::lng_premium_summary_subtitle_unique_reactions();
|
||||
case PremiumPreview::EmojiStatus:
|
||||
return tr::lng_premium_summary_subtitle_emoji_status();
|
||||
case PremiumPreview::InfiniteReactions:
|
||||
return tr::lng_premium_summary_subtitle_infinite_reactions();
|
||||
case PremiumPreview::Stickers:
|
||||
return tr::lng_premium_summary_subtitle_premium_stickers();
|
||||
case PremiumPreview::AnimatedEmoji:
|
||||
|
@ -146,8 +145,10 @@ void PreloadSticker(const std::shared_ptr<Data::DocumentMedia> &media) {
|
|||
return tr::lng_premium_summary_about_voice_to_text();
|
||||
case PremiumPreview::NoAds:
|
||||
return tr::lng_premium_summary_about_no_ads();
|
||||
case PremiumPreview::Reactions:
|
||||
return tr::lng_premium_summary_about_unique_reactions();
|
||||
case PremiumPreview::EmojiStatus:
|
||||
return tr::lng_premium_summary_about_emoji_status();
|
||||
case PremiumPreview::InfiniteReactions:
|
||||
return tr::lng_premium_summary_about_infinite_reactions();
|
||||
case PremiumPreview::Stickers:
|
||||
return tr::lng_premium_summary_about_premium_stickers();
|
||||
case PremiumPreview::AnimatedEmoji:
|
||||
|
@ -465,6 +466,8 @@ struct VideoPreviewDocument {
|
|||
case PremiumPreview::AnimatedEmoji: return "animated_emoji";
|
||||
case PremiumPreview::AdvancedChatManagement:
|
||||
return "advanced_chat_management";
|
||||
case PremiumPreview::EmojiStatus: return "emoji_status";
|
||||
case PremiumPreview::InfiniteReactions: return "infinite_reactions";
|
||||
case PremiumPreview::ProfileBadge: return "profile_badge";
|
||||
case PremiumPreview::AnimatedUserpics: return "animated_userpics";
|
||||
}
|
||||
|
@ -726,423 +729,12 @@ struct VideoPreviewDocument {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
class ReactionPreview final {
|
||||
public:
|
||||
ReactionPreview(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Reaction &reaction,
|
||||
ReactionDisableType type,
|
||||
Fn<void()> update,
|
||||
QPoint position);
|
||||
|
||||
[[nodiscard]] bool playsEffect() const;
|
||||
void paint(Painter &p);
|
||||
void paintEffect(QPainter &p);
|
||||
|
||||
void setOver(bool over);
|
||||
void startAnimations();
|
||||
void cancelAnimations();
|
||||
[[nodiscard]] bool ready() const;
|
||||
[[nodiscard]] bool disabled() const;
|
||||
[[nodiscard]] QRect geometry() const;
|
||||
|
||||
private:
|
||||
void checkReady();
|
||||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const Fn<void()> _update;
|
||||
const QPoint _position;
|
||||
Ui::Animations::Simple _scale;
|
||||
std::shared_ptr<Data::DocumentMedia> _centerMedia;
|
||||
std::shared_ptr<Data::DocumentMedia> _aroundMedia;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _center;
|
||||
std::unique_ptr<Lottie::SinglePlayer> _around;
|
||||
std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
||||
QImage _cache1;
|
||||
QImage _cache2;
|
||||
bool _over = false;
|
||||
bool _disabled = false;
|
||||
bool _playRequested = false;
|
||||
bool _aroundPlaying = false;
|
||||
bool _centerPlaying = false;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
ReactionPreview::ReactionPreview(
|
||||
not_null<Window::SessionController*> controller,
|
||||
const Data::Reaction &reaction,
|
||||
ReactionDisableType type,
|
||||
Fn<void()> update,
|
||||
QPoint position)
|
||||
: _controller(controller)
|
||||
, _update(std::move(update))
|
||||
, _position(position)
|
||||
, _centerMedia(reaction.centerIcon->createMediaView())
|
||||
, _aroundMedia(reaction.aroundAnimation->createMediaView())
|
||||
, _pathGradient(
|
||||
HistoryView::MakePathShiftGradient(
|
||||
controller->chatStyle(),
|
||||
_update))
|
||||
, _disabled(type != ReactionDisableType::None) {
|
||||
_centerMedia->checkStickerLarge();
|
||||
_aroundMedia->checkStickerLarge();
|
||||
checkReady();
|
||||
if (!_center || !_around) {
|
||||
_controller->session().downloaderTaskFinished(
|
||||
) | rpl::take_while([=] {
|
||||
checkReady();
|
||||
return !_center || !_around;
|
||||
}) | rpl::start(_lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
QRect ReactionPreview::geometry() const {
|
||||
const auto xsize = st::premiumReactionWidthSkip;
|
||||
const auto ysize = st::premiumReactionHeightSkip;
|
||||
return { _position - QPoint(xsize / 2, ysize / 2), QSize(xsize, ysize) };
|
||||
}
|
||||
|
||||
void ReactionPreview::checkReady() {
|
||||
const auto make = [&](
|
||||
const std::shared_ptr<Data::DocumentMedia> &media,
|
||||
int size) {
|
||||
const auto bytes = media->bytes();
|
||||
const auto filepath = media->owner()->filepath();
|
||||
auto result = ChatHelpers::LottiePlayerFromDocument(
|
||||
media.get(),
|
||||
nullptr,
|
||||
ChatHelpers::StickerLottieSize::PremiumReactionPreview,
|
||||
QSize(size, size) * style::DevicePixelRatio(),
|
||||
Lottie::Quality::Default);
|
||||
result->updates() | rpl::start_with_next(_update, _lifetime);
|
||||
return result;
|
||||
};
|
||||
if (!_center && _centerMedia->loaded()) {
|
||||
_center = make(_centerMedia, st::premiumReactionSize);
|
||||
}
|
||||
if (!_around && _aroundMedia->loaded()) {
|
||||
_around = make(_aroundMedia, st::premiumReactionAround);
|
||||
}
|
||||
}
|
||||
|
||||
void ReactionPreview::setOver(bool over) {
|
||||
if (_over == over || _disabled) {
|
||||
return;
|
||||
}
|
||||
_over = over;
|
||||
const auto from = st::premiumReactionScale;
|
||||
_scale.start(
|
||||
_update,
|
||||
over ? from : 1.,
|
||||
over ? 1. : from,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
void ReactionPreview::startAnimations() {
|
||||
if (_disabled) {
|
||||
return;
|
||||
}
|
||||
_playRequested = true;
|
||||
if (!_center || !_center->ready() || !_around || !_around->ready()) {
|
||||
return;
|
||||
}
|
||||
_update();
|
||||
}
|
||||
|
||||
void ReactionPreview::cancelAnimations() {
|
||||
_playRequested = false;
|
||||
}
|
||||
|
||||
bool ReactionPreview::ready() const {
|
||||
return _center && _center->ready();
|
||||
}
|
||||
|
||||
bool ReactionPreview::disabled() const {
|
||||
return _disabled;
|
||||
}
|
||||
|
||||
void ReactionPreview::paint(Painter &p) {
|
||||
const auto center = st::premiumReactionSize;
|
||||
const auto scale = _scale.value(_over ? 1. : st::premiumReactionScale);
|
||||
const auto inner = QRect(
|
||||
-center / 2,
|
||||
-center / 2,
|
||||
center,
|
||||
center
|
||||
).translated(_position);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
const auto centerReady = _center && _center->ready();
|
||||
const auto staticCenter = centerReady && !_centerPlaying;
|
||||
const auto use1 = staticCenter && scale == 1.;
|
||||
const auto use2 = staticCenter && scale == st::premiumReactionScale;
|
||||
const auto useScale = (!use1 && !use2 && scale != 1.);
|
||||
if (useScale) {
|
||||
p.save();
|
||||
p.translate(inner.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-inner.center());
|
||||
}
|
||||
if (_disabled) {
|
||||
p.setOpacity(kDisabledOpacity);
|
||||
}
|
||||
checkReady();
|
||||
if (centerReady) {
|
||||
if (use1 || use2) {
|
||||
auto &cache = use1 ? _cache1 : _cache2;
|
||||
const auto use = int(std::round(center * scale));
|
||||
const auto rect = QRect(-use / 2, -use / 2, use, use).translated(
|
||||
_position);
|
||||
if (cache.isNull()) {
|
||||
cache = _center->frame().scaledToWidth(
|
||||
use * style::DevicePixelRatio(),
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
p.drawImage(rect, cache);
|
||||
} else {
|
||||
p.drawImage(inner, _center->frame());
|
||||
}
|
||||
if (_centerPlaying) {
|
||||
const auto almost = (_center->frameIndex() + 1)
|
||||
== _center->framesCount();
|
||||
const auto marked = _center->markFrameShown();
|
||||
if (almost && marked) {
|
||||
_centerPlaying = false;
|
||||
}
|
||||
}
|
||||
if (_around
|
||||
&& _around->ready()
|
||||
&& !_aroundPlaying
|
||||
&& !_centerPlaying
|
||||
&& _playRequested) {
|
||||
_aroundPlaying = _centerPlaying = true;
|
||||
_playRequested = false;
|
||||
}
|
||||
} else {
|
||||
p.setBrush(_controller->chatStyle()->msgServiceBg());
|
||||
ChatHelpers::PaintStickerThumbnailPath(
|
||||
p,
|
||||
_centerMedia.get(),
|
||||
inner,
|
||||
_pathGradient.get());
|
||||
}
|
||||
if (useScale) {
|
||||
p.restore();
|
||||
} else if (_disabled) {
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReactionPreview::playsEffect() const {
|
||||
return _aroundPlaying;
|
||||
}
|
||||
|
||||
void ReactionPreview::paintEffect(QPainter &p) {
|
||||
if (!_aroundPlaying) {
|
||||
return;
|
||||
}
|
||||
const auto size = st::premiumReactionAround;
|
||||
const auto outer = QRect(-size/2, -size/2, size, size).translated(
|
||||
_position);
|
||||
const auto scale = _scale.value(_over ? 1. : st::premiumReactionScale);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
if (scale != 1.) {
|
||||
p.save();
|
||||
p.translate(outer.center());
|
||||
p.scale(scale, scale);
|
||||
p.translate(-outer.center());
|
||||
}
|
||||
p.drawImage(outer, _around->frame());
|
||||
if (scale != 1.) {
|
||||
p.restore();
|
||||
}
|
||||
if (_aroundPlaying) {
|
||||
const auto almost = (_around->frameIndex() + 1)
|
||||
== _around->framesCount();
|
||||
const auto marked = _around->markFrameShown();
|
||||
if (almost && marked) {
|
||||
_aroundPlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> ReactionsPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
const base::flat_map<ReactionId, ReactionDisableType> &disabled,
|
||||
Fn<void()> readyCallback) {
|
||||
struct State {
|
||||
std::vector<std::unique_ptr<ReactionPreview>> entries;
|
||||
Ui::Text::String bottom;
|
||||
int selected = -1;
|
||||
bool readyInvoked = false;
|
||||
};
|
||||
const auto result = Ui::CreateChild<Ui::RpWidget>(parent.get());
|
||||
result->show();
|
||||
|
||||
auto &lifetime = result->lifetime();
|
||||
const auto state = lifetime.make_state<State>();
|
||||
|
||||
result->setMouseTracking(true);
|
||||
|
||||
parent->sizeValue(
|
||||
) | rpl::start_with_next([=] {
|
||||
result->setGeometry(parent->rect());
|
||||
}, result->lifetime());
|
||||
|
||||
using namespace HistoryView;
|
||||
const auto list = controller->session().data().reactions().list(
|
||||
Data::Reactions::Type::Active);
|
||||
const auto count = ranges::count(list, true, &Data::Reaction::premium);
|
||||
const auto rows = (count + kReactionsPerRow - 1) / kReactionsPerRow;
|
||||
const auto inrowmax = rows ? ((count + rows - 1) / rows) : 1;
|
||||
const auto inrowless = (inrowmax * rows - count);
|
||||
const auto inrowmore = rows - inrowless;
|
||||
const auto inmaxrows = inrowmore * inrowmax;
|
||||
auto index = 0;
|
||||
auto disableType = ReactionDisableType::None;
|
||||
for (const auto &reaction : list) {
|
||||
if (!reaction.premium) {
|
||||
continue;
|
||||
}
|
||||
const auto inrow = (index < inmaxrows) ? inrowmax : (inrowmax - 1);
|
||||
const auto row = (index < inmaxrows)
|
||||
? (index / inrow)
|
||||
: (inrowmore + ((index - inmaxrows) / inrow));
|
||||
const auto column = (index < inmaxrows)
|
||||
? (index % inrow)
|
||||
: ((index - inmaxrows) % inrow);
|
||||
++index;
|
||||
if (!reaction.centerIcon || !reaction.aroundAnimation) {
|
||||
continue;
|
||||
}
|
||||
const auto i = disabled.find(reaction.id);
|
||||
const auto disable = (i != end(disabled))
|
||||
? i->second
|
||||
: ReactionDisableType::None;
|
||||
if (disable != ReactionDisableType::None) {
|
||||
disableType = disable;
|
||||
}
|
||||
state->entries.push_back(std::make_unique<ReactionPreview>(
|
||||
controller,
|
||||
reaction,
|
||||
disable,
|
||||
[=] { result->update(); },
|
||||
QPoint(ComputeX(column, inrow), ComputeY(row, rows))));
|
||||
}
|
||||
|
||||
const auto bottom1 = tr::lng_reaction_premium_info(tr::now);
|
||||
const auto bottom2 = (disableType == ReactionDisableType::None)
|
||||
? QString()
|
||||
: (disableType == ReactionDisableType::Group)
|
||||
? tr::lng_reaction_premium_no_group(tr::now)
|
||||
: tr::lng_reaction_premium_no_channel(tr::now);
|
||||
state->bottom.setText(
|
||||
st::defaultTextStyle,
|
||||
(bottom1 + '\n' + bottom2).trimmed());
|
||||
|
||||
result->paintRequest(
|
||||
) | rpl::start_with_next([=] {
|
||||
auto p = Painter(result);
|
||||
auto effects = std::vector<Fn<void()>>();
|
||||
auto ready = 0;
|
||||
for (const auto &entry : state->entries) {
|
||||
entry->paint(p);
|
||||
if (entry->ready()) {
|
||||
++ready;
|
||||
}
|
||||
if (entry->playsEffect()) {
|
||||
effects.push_back([&] {
|
||||
entry->paintEffect(p);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!state->readyInvoked
|
||||
&& readyCallback
|
||||
&& ready > 0
|
||||
&& ready == state->entries.size()) {
|
||||
state->readyInvoked = true;
|
||||
readyCallback();
|
||||
|
||||
}
|
||||
const auto padding = st::boxRowPadding;
|
||||
const auto available = parent->width()
|
||||
- padding.left()
|
||||
- padding.right();
|
||||
const auto top = st::premiumReactionInfoTop
|
||||
+ ((state->bottom.maxWidth() > available)
|
||||
? st::normalFont->height
|
||||
: 0);
|
||||
p.setPen(st::premiumButtonFg);
|
||||
state->bottom.draw(
|
||||
p,
|
||||
padding.left(),
|
||||
top,
|
||||
available,
|
||||
style::al_top);
|
||||
for (const auto &paint : effects) {
|
||||
paint();
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
const auto lookup = [=](QPoint point) {
|
||||
auto index = 0;
|
||||
for (const auto &entry : state->entries) {
|
||||
if (entry->geometry().contains(point) && !entry->disabled()) {
|
||||
return index;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const auto select = [=](int index) {
|
||||
const auto wasInside = (state->selected >= 0);
|
||||
const auto nowInside = (index >= 0);
|
||||
if (state->selected != index) {
|
||||
if (wasInside) {
|
||||
state->entries[state->selected]->setOver(false);
|
||||
}
|
||||
if (nowInside) {
|
||||
state->entries[index]->setOver(true);
|
||||
}
|
||||
state->selected = index;
|
||||
}
|
||||
if (wasInside != nowInside) {
|
||||
result->setCursor(nowInside
|
||||
? style::cur_pointer
|
||||
: style::cur_default);
|
||||
}
|
||||
};
|
||||
result->events(
|
||||
) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
const auto point = static_cast<QMouseEvent*>(event.get())->pos();
|
||||
if (state->selected >= 0) {
|
||||
state->entries[state->selected]->cancelAnimations();
|
||||
}
|
||||
if (const auto index = lookup(point); index >= 0) {
|
||||
state->entries[index]->startAnimations();
|
||||
}
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
const auto point = static_cast<QMouseEvent*>(event.get())->pos();
|
||||
select(lookup(point));
|
||||
} else if (event->type() == QEvent::Leave) {
|
||||
select(-1);
|
||||
}
|
||||
}, lifetime);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> GenerateDefaultPreview(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
Fn<void()> readyCallback) {
|
||||
switch (section) {
|
||||
case PremiumPreview::Reactions:
|
||||
return ReactionsPreview(parent, controller, {}, readyCallback);
|
||||
case PremiumPreview::Stickers:
|
||||
return StickersPreview(parent, controller, readyCallback);
|
||||
default:
|
||||
|
@ -1228,8 +820,6 @@ void PreviewBox(
|
|||
Ui::RpWidget *content = nullptr;
|
||||
Ui::RpWidget *stickersPreload = nullptr;
|
||||
bool stickersPreloadReady = false;
|
||||
Ui::RpWidget *reactionsPreload = nullptr;
|
||||
bool reactionsPreloadReady = false;
|
||||
bool preloadScheduled = false;
|
||||
bool showFinished = false;
|
||||
Ui::Animations::Simple animation;
|
||||
|
@ -1292,21 +882,6 @@ void PreviewBox(
|
|||
ready);
|
||||
state->stickersPreload->hide();
|
||||
}
|
||||
if (now != PremiumPreview::Reactions && !state->reactionsPreload) {
|
||||
const auto ready = [=] {
|
||||
if (state->reactionsPreload) {
|
||||
state->reactionsPreloadReady = true;
|
||||
} else {
|
||||
state->preload();
|
||||
}
|
||||
};
|
||||
state->reactionsPreload = GenerateDefaultPreview(
|
||||
outer,
|
||||
controller,
|
||||
PremiumPreview::Reactions,
|
||||
ready);
|
||||
state->reactionsPreload->hide();
|
||||
}
|
||||
};
|
||||
|
||||
switch (descriptor.section) {
|
||||
|
@ -1315,13 +890,6 @@ void PreviewBox(
|
|||
? StickerPreview(outer, controller, media, state->preload)
|
||||
: StickersPreview(outer, controller, state->preload);
|
||||
break;
|
||||
case PremiumPreview::Reactions:
|
||||
state->content = ReactionsPreview(
|
||||
outer,
|
||||
controller,
|
||||
descriptor.disabled,
|
||||
state->preload);
|
||||
break;
|
||||
default:
|
||||
state->content = GenericPreview(
|
||||
outer,
|
||||
|
@ -1381,13 +949,6 @@ void PreviewBox(
|
|||
if (base::take(state->stickersPreloadReady)) {
|
||||
state->preload();
|
||||
}
|
||||
} else if (now == PremiumPreview::Reactions
|
||||
&& state->reactionsPreload) {
|
||||
state->content = base::take(state->reactionsPreload);
|
||||
state->content->show();
|
||||
if (base::take(state->reactionsPreloadReady)) {
|
||||
state->preload();
|
||||
}
|
||||
} else {
|
||||
state->content = GenerateDefaultPreview(
|
||||
outer,
|
||||
|
@ -1457,12 +1018,14 @@ void PreviewBox(
|
|||
};
|
||||
auto unlock = state->selected.value(
|
||||
) | rpl::map([=](PremiumPreview section) {
|
||||
return (section == PremiumPreview::Reactions)
|
||||
return (section == PremiumPreview::InfiniteReactions)
|
||||
? tr::lng_premium_unlock_reactions()
|
||||
: (section == PremiumPreview::Stickers)
|
||||
? tr::lng_premium_unlock_stickers()
|
||||
: (section == PremiumPreview::AnimatedEmoji)
|
||||
? tr::lng_premium_unlock_emoji()
|
||||
: (section == PremiumPreview::EmojiStatus)
|
||||
? tr::lng_premium_unlock_status()
|
||||
: tr::lng_premium_more_about();
|
||||
}) | rpl::flatten_latest();
|
||||
auto button = descriptor.fromSettings
|
||||
|
@ -1626,17 +1189,8 @@ void ShowPremiumPreviewBox(
|
|||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown) {
|
||||
ShowPremiumPreviewBox(controller, section, {}, std::move(shown));
|
||||
}
|
||||
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
const base::flat_map<ReactionId, ReactionDisableType> &disabled,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown) {
|
||||
Show(controller, Descriptor{
|
||||
.section = section,
|
||||
.disabled = disabled,
|
||||
.shownCallback = std::move(shown),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -42,7 +42,8 @@ enum class PremiumPreview {
|
|||
FasterDownload,
|
||||
VoiceToText,
|
||||
NoAds,
|
||||
Reactions,
|
||||
EmojiStatus,
|
||||
InfiniteReactions,
|
||||
Stickers,
|
||||
AnimatedEmoji,
|
||||
AdvancedChatManagement,
|
||||
|
@ -51,23 +52,12 @@ enum class PremiumPreview {
|
|||
|
||||
kCount,
|
||||
};
|
||||
enum class ReactionDisableType {
|
||||
None,
|
||||
Group,
|
||||
Channel,
|
||||
};
|
||||
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
|
||||
|
||||
void ShowPremiumPreviewBox(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
const base::flat_map<Data::ReactionId, ReactionDisableType> &disabled,
|
||||
Fn<void(not_null<Ui::BoxContent*>)> shown = nullptr);
|
||||
|
||||
void ShowPremiumPreviewToBuy(
|
||||
not_null<Window::SessionController*> controller,
|
||||
PremiumPreview section,
|
||||
|
|
|
@ -521,7 +521,9 @@ void ReactionsSettingsBox(
|
|||
|
||||
button->setClickedCallback([=, id = r.id] {
|
||||
if (premium && !controller->session().premium()) {
|
||||
ShowPremiumPreviewBox(controller, PremiumPreview::Reactions);
|
||||
ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::InfiniteReactions);
|
||||
return;
|
||||
}
|
||||
checkButton(button);
|
||||
|
|
|
@ -687,8 +687,7 @@ void EmojiListWidget::setSingleSize(QSize size) {
|
|||
_innerPosition = QPoint(
|
||||
(area.width() - esize) / 2,
|
||||
(area.height() - esize) / 2);
|
||||
const auto customSize = Ui::Text::AdjustCustomEmojiSize(esize);
|
||||
const auto customSkip = (esize - customSize) / 2;
|
||||
const auto customSkip = (esize - _customSingleSize) / 2;
|
||||
_customPosition = QPoint(customSkip, customSkip);
|
||||
_picker->setSingleSize(_singleSize);
|
||||
}
|
||||
|
@ -964,7 +963,9 @@ void EmojiListWidget::paint(
|
|||
drawCollapsedBadge(p, w - _areaPosition, info.count);
|
||||
continue;
|
||||
}
|
||||
if (selected && st().overBg->c.alpha() > 0) {
|
||||
if (!_grabbingChosen
|
||||
&& selected
|
||||
&& st().overBg->c.alpha() > 0) {
|
||||
auto tl = w;
|
||||
if (rtl()) {
|
||||
tl.setX(width() - tl.x() - st::emojiPanArea.width());
|
||||
|
@ -1128,12 +1129,17 @@ EmojiPtr EmojiListWidget::lookupOverEmoji(const OverEmoji *over) const {
|
|||
EmojiChosen EmojiListWidget::lookupChosen(
|
||||
EmojiPtr emoji,
|
||||
not_null<const OverEmoji*> over) {
|
||||
const auto rect = emojiRect(over->section, over->index);
|
||||
const auto icon = QRect(
|
||||
rect.x() + (_singleSize.width() - st::stickersPremium.width()) / 2,
|
||||
rect.y() + (_singleSize.height() - st::stickersPremium.height()) / 2,
|
||||
rect.width(),
|
||||
rect.height());
|
||||
return {
|
||||
.emoji = emoji,
|
||||
.messageSendingFrom = {
|
||||
.type = Ui::MessageSendingAnimationFrom::Type::Emoji,
|
||||
.globalStartGeometry = mapToGlobal(
|
||||
emojiRect(over->section, over->index)),
|
||||
.globalStartGeometry = mapToGlobal(icon),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1144,14 +1150,19 @@ FileChosen EmojiListWidget::lookupChosen(
|
|||
Api::SendOptions options) {
|
||||
_grabbingChosen = true;
|
||||
const auto guard = gsl::finally([&] { _grabbingChosen = false; });
|
||||
const auto rect = emojiRect(over->section, over->index);
|
||||
const auto rect = over ? emojiRect(over->section, over->index) : QRect();
|
||||
const auto emoji = over ? QRect(
|
||||
rect.topLeft() + _areaPosition + _innerPosition + _customPosition,
|
||||
QSize(_customSingleSize, _customSingleSize)
|
||||
) : QRect();
|
||||
|
||||
return {
|
||||
.document = custom,
|
||||
.options = options,
|
||||
.messageSendingFrom = {
|
||||
.type = Ui::MessageSendingAnimationFrom::Type::Emoji,
|
||||
.globalStartGeometry = over ? mapToGlobal(rect) : QRect(),
|
||||
.frame = over ? Ui::GrabWidgetToImage(this, rect) : QImage(),
|
||||
.globalStartGeometry = over ? mapToGlobal(emoji) : QRect(),
|
||||
.frame = over ? Ui::GrabWidgetToImage(this, emoji) : QImage(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1246,7 +1257,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|||
break;
|
||||
case Mode::FullReactions:
|
||||
case Mode::RecentReactions:
|
||||
Settings::ShowPremium(_controller, u"unique_reactions"_q);
|
||||
Settings::ShowPremium(_controller, u"infinite_reactions"_q);
|
||||
break;
|
||||
case Mode::EmojiStatus:
|
||||
Settings::ShowPremium(_controller, u"emoji_status"_q);
|
||||
|
|
|
@ -25,15 +25,17 @@ struct CustomEmojiId {
|
|||
DocumentId id = 0;
|
||||
};
|
||||
|
||||
enum class CustomEmojiSizeTag : uchar {
|
||||
Normal,
|
||||
Large,
|
||||
Isolated,
|
||||
|
||||
kCount,
|
||||
};
|
||||
|
||||
class CustomEmojiManager final : public base::has_weak_ptr {
|
||||
public:
|
||||
enum class SizeTag : uchar {
|
||||
Normal,
|
||||
Large,
|
||||
Isolated,
|
||||
|
||||
kCount,
|
||||
};
|
||||
using SizeTag = CustomEmojiSizeTag;
|
||||
|
||||
CustomEmojiManager(not_null<Session*> owner);
|
||||
~CustomEmojiManager();
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/view/media/history_view_media.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/media/history_view_web_page.h"
|
||||
#include "history/view/reactions/history_view_reactions_animation.h"
|
||||
#include "history/view/reactions/history_view_reactions_button.h"
|
||||
#include "history/view/reactions/history_view_reactions_selector.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
|
@ -49,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "boxes/delete_messages_box.h"
|
||||
#include "boxes/report_messages_box.h"
|
||||
#include "boxes/sticker_set_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/emoji_interactions.h"
|
||||
#include "history/history_widget.h"
|
||||
|
@ -401,7 +403,9 @@ HistoryInner::HistoryInner(
|
|||
_reactionsManager->premiumPromoChosen(
|
||||
) | rpl::start_with_next([=](FullMsgId context) {
|
||||
_reactionsManager->updateButton({});
|
||||
premiumPromoChosen(context);
|
||||
ShowPremiumPreviewBox(
|
||||
_controller,
|
||||
PremiumPreview::InfiniteReactions);
|
||||
}, lifetime());
|
||||
|
||||
session().data().itemRemoved(
|
||||
|
@ -492,12 +496,6 @@ void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryInner::premiumPromoChosen(FullMsgId context) {
|
||||
if (const auto item = session().data().message(context)) {
|
||||
ShowPremiumPromoBox(_controller, item);
|
||||
}
|
||||
}
|
||||
|
||||
Main::Session &HistoryInner::session() const {
|
||||
return _controller->session();
|
||||
}
|
||||
|
@ -2456,7 +2454,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
desiredPosition,
|
||||
reactItem,
|
||||
[=](ChosenReaction reaction) { reactionChosen(reaction); },
|
||||
[=](FullMsgId context) { premiumPromoChosen(context); },
|
||||
[=](FullMsgId context) { ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::InfiniteReactions); },
|
||||
_controller->cachedReactionIconFactory().createMethod())
|
||||
: AttachSelectorResult::Skipped;
|
||||
if (attached == AttachSelectorResult::Failed) {
|
||||
|
|
|
@ -399,7 +399,6 @@ private:
|
|||
-> HistoryView::Reactions::ButtonParameters;
|
||||
void toggleFavoriteReaction(not_null<Element*> view) const;
|
||||
void reactionChosen(const ChosenReaction &reaction);
|
||||
void premiumPromoChosen(FullMsgId context);
|
||||
|
||||
void setupSharingDisallowed();
|
||||
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
|
||||
|
|
|
@ -29,14 +29,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
namespace HistoryView {
|
||||
|
||||
ReactionAnimationArgs ReactionAnimationArgs::translated(
|
||||
QPoint point) const {
|
||||
return {
|
||||
.id = id,
|
||||
.flyIcon = flyIcon,
|
||||
.flyFrom = flyFrom.translated(point),
|
||||
};
|
||||
}
|
||||
struct BottomInfo::Reaction {
|
||||
mutable std::unique_ptr<Reactions::Animation> animation;
|
||||
mutable QImage image;
|
||||
ReactionId id;
|
||||
QString countText;
|
||||
int count = 0;
|
||||
int countTextWidth = 0;
|
||||
bool chosen = false;
|
||||
};
|
||||
|
||||
BottomInfo::BottomInfo(
|
||||
not_null<::Data::Reactions*> reactionsOwner,
|
||||
|
@ -389,13 +390,19 @@ void BottomInfo::paintReactions(
|
|||
}
|
||||
if (!animations.empty()) {
|
||||
const auto now = context.now;
|
||||
context.reactionInfo->effectPaint = [=](QPainter &p) {
|
||||
context.reactionInfo->effectPaint = [
|
||||
now,
|
||||
origin,
|
||||
list = std::move(animations)
|
||||
](QPainter &p) {
|
||||
auto result = QRect();
|
||||
for (const auto &single : animations) {
|
||||
for (const auto &single : list) {
|
||||
const auto area = single.animation->paintGetArea(
|
||||
p,
|
||||
origin,
|
||||
single.target,
|
||||
QColor(255, 255, 255, 0), // Colored, for emoji status.
|
||||
QRect(), // Clip, for emoji status.
|
||||
now);
|
||||
result = result.isEmpty() ? area : result.united(area);
|
||||
}
|
||||
|
@ -553,7 +560,7 @@ void BottomInfo::setReactionCount(Reaction &reaction, int count) {
|
|||
}
|
||||
|
||||
void BottomInfo::animateReaction(
|
||||
ReactionAnimationArgs &&args,
|
||||
Reactions::AnimationArgs &&args,
|
||||
Fn<void()> repaint) {
|
||||
const auto i = ranges::find(_reactions, args.id, &Reaction::id);
|
||||
if (i == end(_reactions)) {
|
||||
|
|
|
@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "history/view/history_view_object.h"
|
||||
#include "data/data_message_reaction_id.h"
|
||||
#include "ui/text/text.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
|
@ -19,21 +18,16 @@ class AnimatedIcon;
|
|||
|
||||
namespace Data {
|
||||
class Reactions;
|
||||
struct ReactionId;
|
||||
struct MessageReaction;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
namespace Reactions {
|
||||
class Animation;
|
||||
struct AnimationArgs;
|
||||
} // namespace Reactions
|
||||
|
||||
struct ReactionAnimationArgs {
|
||||
::Data::ReactionId id;
|
||||
QImage flyIcon;
|
||||
QRect flyFrom;
|
||||
|
||||
[[nodiscard]] ReactionAnimationArgs translated(QPoint point) const;
|
||||
};
|
||||
|
||||
using PaintContext = Ui::ChatPaintContext;
|
||||
|
||||
class Message;
|
||||
|
@ -86,7 +80,7 @@ public:
|
|||
const PaintContext &context) const;
|
||||
|
||||
void animateReaction(
|
||||
ReactionAnimationArgs &&args,
|
||||
Reactions::AnimationArgs &&args,
|
||||
Fn<void()> repaint);
|
||||
[[nodiscard]] auto takeReactionAnimations()
|
||||
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>>;
|
||||
|
@ -95,15 +89,7 @@ public:
|
|||
std::unique_ptr<Reactions::Animation>> animations);
|
||||
|
||||
private:
|
||||
struct Reaction {
|
||||
mutable std::unique_ptr<Reactions::Animation> animation;
|
||||
mutable QImage image;
|
||||
ReactionId id;
|
||||
QString countText;
|
||||
int count = 0;
|
||||
int countTextWidth = 0;
|
||||
bool chosen = false;
|
||||
};
|
||||
struct Reaction;
|
||||
|
||||
void layout();
|
||||
void layoutDateText();
|
||||
|
|
|
@ -1116,7 +1116,7 @@ void Element::clickHandlerPressedChanged(
|
|||
}
|
||||
}
|
||||
|
||||
void Element::animateReaction(ReactionAnimationArgs &&args) {
|
||||
void Element::animateReaction(Reactions::AnimationArgs &&args) {
|
||||
}
|
||||
|
||||
void Element::animateUnreadReactions() {
|
||||
|
|
|
@ -34,24 +34,23 @@ struct ChatPaintContext;
|
|||
class ChatStyle;
|
||||
} // namespace Ui
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
struct ButtonParameters;
|
||||
struct AnimationArgs;
|
||||
class Animation;
|
||||
class InlineList;
|
||||
} // namespace HistoryView::Reactions
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
using PaintContext = Ui::ChatPaintContext;
|
||||
enum class PointState : char;
|
||||
enum class InfoDisplayType : char;
|
||||
struct ReactionAnimationArgs;
|
||||
struct StateRequest;
|
||||
struct TextState;
|
||||
class Media;
|
||||
//struct ExternalLottieInfo;
|
||||
|
||||
using PaintContext = Ui::ChatPaintContext;
|
||||
|
||||
namespace Reactions {
|
||||
struct ButtonParameters;
|
||||
class Animation;
|
||||
class InlineList;
|
||||
} // namespace Reactions
|
||||
|
||||
enum class Context : char {
|
||||
History,
|
||||
Replies,
|
||||
|
@ -433,7 +432,7 @@ public:
|
|||
|
||||
[[nodiscard]] bool markSponsoredViewed(int shownFromTop) const;
|
||||
|
||||
virtual void animateReaction(ReactionAnimationArgs &&args);
|
||||
virtual void animateReaction(Reactions::AnimationArgs &&args);
|
||||
void animateUnreadReactions();
|
||||
[[nodiscard]] virtual auto takeReactionAnimations()
|
||||
-> base::flat_map<
|
||||
|
|
|
@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history_item_text.h"
|
||||
#include "history/view/media/history_view_media.h"
|
||||
#include "history/view/media/history_view_sticker.h"
|
||||
#include "history/view/reactions/history_view_reactions_animation.h"
|
||||
#include "history/view/reactions/history_view_reactions_button.h"
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_element.h"
|
||||
|
@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/chat/chat_style.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "boxes/delete_messages_box.h"
|
||||
#include "boxes/premium_preview_box.h"
|
||||
#include "boxes/peers/edit_participant_box.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_folder.h"
|
||||
|
@ -390,9 +392,9 @@ ListWidget::ListWidget(
|
|||
_reactionsManager->premiumPromoChosen(
|
||||
) | rpl::start_with_next([=] {
|
||||
_reactionsManager->updateButton({});
|
||||
if (const auto item = _reactionsItem.current()) {
|
||||
ShowPremiumPromoBox(_controller, item);
|
||||
}
|
||||
ShowPremiumPreviewBox(
|
||||
_controller,
|
||||
PremiumPreview::InfiniteReactions);
|
||||
}, lifetime());
|
||||
|
||||
Reactions::SetupManagerList(
|
||||
|
|
|
@ -345,7 +345,7 @@ void Message::applyGroupAdminChanges(
|
|||
}
|
||||
}
|
||||
|
||||
void Message::animateReaction(ReactionAnimationArgs &&args) {
|
||||
void Message::animateReaction(Reactions::AnimationArgs &&args) {
|
||||
const auto item = message();
|
||||
const auto media = this->media();
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ public:
|
|||
void applyGroupAdminChanges(
|
||||
const base::flat_set<UserId> &changes) override;
|
||||
|
||||
void animateReaction(ReactionAnimationArgs &&args) override;
|
||||
void animateReaction(Reactions::AnimationArgs &&args) override;
|
||||
auto takeReactionAnimations()
|
||||
-> base::flat_map<
|
||||
Data::ReactionId,
|
||||
|
|
|
@ -453,13 +453,18 @@ void InlineList::paint(
|
|||
}
|
||||
if (!animations.empty()) {
|
||||
const auto now = context.now;
|
||||
context.reactionInfo->effectPaint = [=](QPainter &p) {
|
||||
context.reactionInfo->effectPaint = [
|
||||
now,
|
||||
list = std::move(animations)
|
||||
](QPainter &p) {
|
||||
auto result = QRect();
|
||||
for (const auto &single : animations) {
|
||||
for (const auto &single : list) {
|
||||
const auto area = single.animation->paintGetArea(
|
||||
p,
|
||||
QPoint(),
|
||||
single.target,
|
||||
QColor(255, 255, 255, 0), // Colored, for emoji status.
|
||||
QRect(), // Clip, for emoji status.
|
||||
now);
|
||||
result = result.isEmpty() ? area : result.united(area);
|
||||
}
|
||||
|
@ -494,7 +499,7 @@ bool InlineList::getState(
|
|||
}
|
||||
|
||||
void InlineList::animate(
|
||||
ReactionAnimationArgs &&args,
|
||||
AnimationArgs &&args,
|
||||
Fn<void()> repaint) {
|
||||
const auto i = ranges::find(_buttons, args.id, &Button::id);
|
||||
if (i == end(_buttons)) {
|
||||
|
|
|
@ -24,13 +24,13 @@ using PaintContext = Ui::ChatPaintContext;
|
|||
class Message;
|
||||
struct TextState;
|
||||
struct UserpicInRow;
|
||||
struct ReactionAnimationArgs;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
||||
using ::Data::ReactionId;
|
||||
using ::Data::MessageReaction;
|
||||
struct AnimationArgs;
|
||||
class Animation;
|
||||
|
||||
struct InlineListData {
|
||||
|
@ -79,7 +79,7 @@ public:
|
|||
not_null<TextState*> outResult) const;
|
||||
|
||||
void animate(
|
||||
ReactionAnimationArgs &&args,
|
||||
AnimationArgs &&args,
|
||||
Fn<void()> repaint);
|
||||
[[nodiscard]] auto takeAnimations()
|
||||
-> base::flat_map<ReactionId, std::unique_ptr<Reactions::Animation>>;
|
||||
|
|
|
@ -32,6 +32,14 @@ constexpr auto kMiniCopiesMaxScaleMax = 0.9;
|
|||
|
||||
} // namespace
|
||||
|
||||
AnimationArgs AnimationArgs::translated(QPoint point) const {
|
||||
return {
|
||||
.id = id,
|
||||
.flyIcon = flyIcon,
|
||||
.flyFrom = flyFrom.translated(point),
|
||||
};
|
||||
}
|
||||
|
||||
auto Animation::flyCallback() {
|
||||
return [=] {
|
||||
if (!_fly.animating()) {
|
||||
|
@ -54,22 +62,28 @@ auto Animation::callback() {
|
|||
|
||||
Animation::Animation(
|
||||
not_null<::Data::Reactions*> owner,
|
||||
ReactionAnimationArgs &&args,
|
||||
AnimationArgs &&args,
|
||||
Fn<void()> repaint,
|
||||
int size)
|
||||
int size,
|
||||
Data::CustomEmojiSizeTag customSizeTag)
|
||||
: _owner(owner)
|
||||
, _repaint(std::move(repaint))
|
||||
, _flyFrom(args.flyFrom) {
|
||||
const auto &list = owner->list(::Data::Reactions::Type::All);
|
||||
auto centerIcon = (DocumentData*)nullptr;
|
||||
auto centerIconSize = size;
|
||||
auto aroundAnimation = (DocumentData*)nullptr;
|
||||
if (const auto customId = args.id.custom()) {
|
||||
centerIconSize = Ui::Text::AdjustCustomEmojiSize(st::emojiSize);
|
||||
const auto esize = Data::FrameSizeFromTag(customSizeTag)
|
||||
/ style::DevicePixelRatio();
|
||||
const auto data = &owner->owner();
|
||||
const auto document = data->document(customId);
|
||||
_custom = data->customEmojiManager().create(document, callback());
|
||||
_customSize = centerIconSize;
|
||||
_custom = data->customEmojiManager().create(
|
||||
document,
|
||||
callback(),
|
||||
customSizeTag);
|
||||
_colored = std::make_unique<Ui::Text::CustomEmojiColored>();
|
||||
_customSize = esize;
|
||||
_centerSizeMultiplier = _customSize / float64(size);
|
||||
aroundAnimation = owner->chooseGenericAnimation(document);
|
||||
} else {
|
||||
const auto i = ranges::find(list, args.id, &::Data::Reaction::id);
|
||||
|
@ -78,6 +92,7 @@ Animation::Animation(
|
|||
}
|
||||
centerIcon = i->centerIcon;
|
||||
aroundAnimation = i->aroundAnimation;
|
||||
_centerSizeMultiplier = 1.;
|
||||
}
|
||||
const auto resolve = [&](
|
||||
std::unique_ptr<Ui::AnimatedIcon> &icon,
|
||||
|
@ -96,7 +111,7 @@ Animation::Animation(
|
|||
});
|
||||
return true;
|
||||
};
|
||||
if (!_custom && !resolve(_center, centerIcon, centerIconSize)) {
|
||||
if (!_custom && !resolve(_center, centerIcon, size)) {
|
||||
return;
|
||||
}
|
||||
resolve(_effect, aroundAnimation, size * 2);
|
||||
|
@ -109,7 +124,6 @@ Animation::Animation(
|
|||
} else {
|
||||
startAnimations();
|
||||
}
|
||||
_centerSizeMultiplier = centerIconSize / float64(size);
|
||||
_valid = true;
|
||||
}
|
||||
|
||||
|
@ -119,21 +133,26 @@ QRect Animation::paintGetArea(
|
|||
QPainter &p,
|
||||
QPoint origin,
|
||||
QRect target,
|
||||
const QColor &colored,
|
||||
QRect clip,
|
||||
crl::time now) const {
|
||||
if (_flyIcon.isNull()) {
|
||||
paintCenterFrame(p, target, now);
|
||||
const auto wide = QRect(
|
||||
target.topLeft() - QPoint(target.width(), target.height()) / 2,
|
||||
target.size() * 2);
|
||||
if (const auto effect = _effect.get()) {
|
||||
p.drawImage(wide, effect->frame());
|
||||
}
|
||||
paintMiniCopies(p, target.center(), now);
|
||||
return _miniCopies.empty()
|
||||
const auto area = _miniCopies.empty()
|
||||
? wide
|
||||
: QRect(
|
||||
target.topLeft() - QPoint(target.width(), target.height()),
|
||||
target.size() * 3);
|
||||
if (clip.isEmpty() || area.intersects(clip)) {
|
||||
paintCenterFrame(p, target, colored, now);
|
||||
if (const auto effect = _effect.get()) {
|
||||
p.drawImage(wide, effect->frame());
|
||||
}
|
||||
paintMiniCopies(p, target.center(), colored, now);
|
||||
}
|
||||
return area;
|
||||
}
|
||||
const auto from = _flyFrom.translated(origin);
|
||||
const auto lshift = target.width() / 4;
|
||||
|
@ -152,21 +171,24 @@ QRect Animation::paintGetArea(
|
|||
anim::interpolate(from.width(), target.width(), progress),
|
||||
anim::interpolate(from.height(), target.height(), progress));
|
||||
const auto wide = rect.marginsAdded(margins);
|
||||
if (progress < 1.) {
|
||||
p.setOpacity(1. - progress);
|
||||
p.drawImage(rect, _flyIcon);
|
||||
if (clip.isEmpty() || wide.intersects(clip)) {
|
||||
if (progress < 1.) {
|
||||
p.setOpacity(1. - progress);
|
||||
p.drawImage(rect, _flyIcon);
|
||||
}
|
||||
if (progress > 0.) {
|
||||
p.setOpacity(progress);
|
||||
paintCenterFrame(p, wide, colored, now);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
if (progress > 0.) {
|
||||
p.setOpacity(progress);
|
||||
paintCenterFrame(p, wide, now);
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
return wide;
|
||||
}
|
||||
|
||||
void Animation::paintCenterFrame(
|
||||
QPainter &p,
|
||||
QRect target,
|
||||
const QColor &colored,
|
||||
crl::time now) const {
|
||||
Expects(_center || _custom);
|
||||
|
||||
|
@ -182,8 +204,10 @@ void Animation::paintCenterFrame(
|
|||
p.drawImage(rect, _center->frame());
|
||||
} else {
|
||||
const auto scaled = (size.width() != _customSize);
|
||||
_colored->color = colored;
|
||||
_custom->paint(p, {
|
||||
.preview = QColor(0, 0, 0, 0),
|
||||
.colored = _colored.get(),
|
||||
.size = { _customSize, _customSize },
|
||||
.now = now,
|
||||
.scale = (scaled ? (size.width() / float64(_customSize)) : 1.),
|
||||
|
@ -198,6 +222,7 @@ void Animation::paintCenterFrame(
|
|||
void Animation::paintMiniCopies(
|
||||
QPainter &p,
|
||||
QPoint center,
|
||||
const QColor &colored,
|
||||
crl::time now) const {
|
||||
Expects(_miniCopies.empty() || _custom != nullptr);
|
||||
|
||||
|
@ -213,8 +238,10 @@ void Animation::paintMiniCopies(
|
|||
/ float64(kMiniCopiesDurationMax);
|
||||
const auto scaleOut = kMiniCopiesScaleOutDuration
|
||||
/ float64(kMiniCopiesDurationMax);
|
||||
_colored->color = colored;
|
||||
auto context = Ui::Text::CustomEmoji::Context{
|
||||
.preview = preview,
|
||||
.colored = _colored.get(),
|
||||
.size = size,
|
||||
.now = now,
|
||||
.scaled = true,
|
||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "ui/effects/animations.h"
|
||||
#include "data/data_message_reaction_id.h"
|
||||
|
||||
namespace Ui {
|
||||
class AnimatedIcon;
|
||||
|
@ -19,21 +20,27 @@ class CustomEmoji;
|
|||
|
||||
namespace Data {
|
||||
class Reactions;
|
||||
enum class CustomEmojiSizeTag : uchar;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
struct ReactionAnimationArgs;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
||||
struct AnimationArgs {
|
||||
::Data::ReactionId id;
|
||||
QImage flyIcon;
|
||||
QRect flyFrom;
|
||||
|
||||
[[nodiscard]] AnimationArgs translated(QPoint point) const;
|
||||
};
|
||||
|
||||
class Animation final {
|
||||
public:
|
||||
Animation(
|
||||
not_null<::Data::Reactions*> owner,
|
||||
ReactionAnimationArgs &&args,
|
||||
AnimationArgs &&args,
|
||||
Fn<void()> repaint,
|
||||
int size);
|
||||
int size,
|
||||
Data::CustomEmojiSizeTag customSizeTag = {});
|
||||
~Animation();
|
||||
|
||||
void setRepaintCallback(Fn<void()> repaint);
|
||||
|
@ -41,6 +48,8 @@ public:
|
|||
QPainter &p,
|
||||
QPoint origin,
|
||||
QRect target,
|
||||
const QColor &colored,
|
||||
QRect clip,
|
||||
crl::time now) const;
|
||||
|
||||
[[nodiscard]] bool flying() const;
|
||||
|
@ -71,14 +80,23 @@ private:
|
|||
int to,
|
||||
int top,
|
||||
float64 progress) const;
|
||||
void paintCenterFrame(QPainter &p, QRect target, crl::time now) const;
|
||||
void paintMiniCopies(QPainter &p, QPoint center, crl::time now) const;
|
||||
void paintCenterFrame(
|
||||
QPainter &p,
|
||||
QRect target,
|
||||
const QColor &colored,
|
||||
crl::time now) const;
|
||||
void paintMiniCopies(
|
||||
QPainter &p,
|
||||
QPoint center,
|
||||
const QColor &colored,
|
||||
crl::time now) const;
|
||||
void generateMiniCopies(int size);
|
||||
|
||||
const not_null<::Data::Reactions*> _owner;
|
||||
Fn<void()> _repaint;
|
||||
QImage _flyIcon;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _custom;
|
||||
std::unique_ptr<Ui::Text::CustomEmojiColored> _colored;
|
||||
std::unique_ptr<Ui::AnimatedIcon> _center;
|
||||
std::unique_ptr<Ui::AnimatedIcon> _effect;
|
||||
std::vector<MiniCopy> _miniCopies;
|
||||
|
|
195
Telegram/SourceFiles/info/profile/info_profile_badge.cpp
Normal file
195
Telegram/SourceFiles/info/profile/info_profile_badge.cpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "main/main_session.h"
|
||||
#include "styles/style_info.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
Badge::Badge(
|
||||
not_null<QWidget*> parent,
|
||||
const style::InfoPeerBadge &st,
|
||||
not_null<PeerData*> peer,
|
||||
EmojiStatusPanel *emojiStatusPanel,
|
||||
Fn<bool()> animationPaused,
|
||||
int customStatusLoopsLimit,
|
||||
base::flags<BadgeType> allowed)
|
||||
: _parent(parent)
|
||||
, _st(st)
|
||||
, _peer(peer)
|
||||
, _emojiStatusPanel(emojiStatusPanel)
|
||||
, _customStatusLoopsLimit(customStatusLoopsLimit)
|
||||
, _allowed(allowed)
|
||||
, _animationPaused(std::move(animationPaused)) {
|
||||
rpl::combine(
|
||||
BadgeValue(peer),
|
||||
EmojiStatusIdValue(peer)
|
||||
) | rpl::start_with_next([=](BadgeType badge, DocumentId emojiStatusId) {
|
||||
setBadge(badge, emojiStatusId);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Ui::RpWidget *Badge::widget() const {
|
||||
return _view.data();
|
||||
}
|
||||
|
||||
void Badge::setBadge(BadgeType badge, DocumentId emojiStatusId) {
|
||||
if (!(_allowed & badge)
|
||||
|| (!_peer->session().premiumBadgesShown()
|
||||
&& badge == BadgeType::Premium)) {
|
||||
badge = BadgeType::None;
|
||||
}
|
||||
if (!(_allowed & badge)) {
|
||||
badge = BadgeType::None;
|
||||
}
|
||||
if (badge != BadgeType::Premium) {
|
||||
emojiStatusId = 0;
|
||||
}
|
||||
if (_badge == badge && _emojiStatusId == emojiStatusId) {
|
||||
return;
|
||||
}
|
||||
_badge = badge;
|
||||
_emojiStatusId = emojiStatusId;
|
||||
_emojiStatus = nullptr;
|
||||
_emojiStatusColored = nullptr;
|
||||
_view.destroy();
|
||||
if (_badge == BadgeType::None) {
|
||||
_updated.fire({});
|
||||
return;
|
||||
}
|
||||
_view.create(_parent);
|
||||
_view->show();
|
||||
switch (_badge) {
|
||||
case BadgeType::Verified:
|
||||
case BadgeType::Premium: {
|
||||
if (_emojiStatusId) {
|
||||
_emojiStatus = _peer->owner().customEmojiManager().create(
|
||||
_emojiStatusId,
|
||||
[raw = _view.data()] { raw->update(); },
|
||||
sizeTag());
|
||||
if (_customStatusLoopsLimit > 0) {
|
||||
_emojiStatus = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
std::move(_emojiStatus),
|
||||
_customStatusLoopsLimit);
|
||||
}
|
||||
_emojiStatusColored = std::make_unique<
|
||||
Ui::Text::CustomEmojiColored
|
||||
>();
|
||||
const auto emoji = Data::FrameSizeFromTag(sizeTag())
|
||||
/ style::DevicePixelRatio();
|
||||
_view->resize(emoji, emoji);
|
||||
_view->paintRequest(
|
||||
) | rpl::start_with_next([=, check = _view.data()]{
|
||||
_emojiStatusColored->color = _st.premiumFg->c;
|
||||
auto args = Ui::Text::CustomEmoji::Context{
|
||||
.preview = st::windowBgOver->c,
|
||||
.colored = _emojiStatusColored.get(),
|
||||
.now = crl::now(),
|
||||
.paused = _animationPaused && _animationPaused(),
|
||||
};
|
||||
if (!_emojiStatusPanel
|
||||
|| !_emojiStatusPanel->paintBadgeFrame(check)) {
|
||||
Painter p(check);
|
||||
_emojiStatus->paint(p, args);
|
||||
}
|
||||
}, _view->lifetime());
|
||||
} else {
|
||||
const auto icon = (_badge == BadgeType::Verified)
|
||||
? &_st.verified
|
||||
: &_st.premium;
|
||||
_view->resize(icon->size());
|
||||
_view->paintRequest(
|
||||
) | rpl::start_with_next([=, check = _view.data()]{
|
||||
Painter p(check);
|
||||
icon->paint(p, 0, 0, check->width());
|
||||
}, _view->lifetime());
|
||||
}
|
||||
} break;
|
||||
case BadgeType::Scam:
|
||||
case BadgeType::Fake: {
|
||||
const auto fake = (_badge == BadgeType::Fake);
|
||||
const auto size = Ui::ScamBadgeSize(fake);
|
||||
const auto skip = st::infoVerifiedCheckPosition.x();
|
||||
_view->resize(
|
||||
size.width() + 2 * skip,
|
||||
size.height() + 2 * skip);
|
||||
_view->paintRequest(
|
||||
) | rpl::start_with_next([=, badge = _view.data()]{
|
||||
Painter p(badge);
|
||||
Ui::DrawScamBadge(
|
||||
fake,
|
||||
p,
|
||||
badge->rect().marginsRemoved({ skip, skip, skip, skip }),
|
||||
badge->width(),
|
||||
st::attentionButtonFg);
|
||||
}, _view->lifetime());
|
||||
} break;
|
||||
}
|
||||
|
||||
if (_badge != BadgeType::Premium || !_premiumClickCallback) {
|
||||
_view->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
} else {
|
||||
_view->setClickedCallback(_premiumClickCallback);
|
||||
}
|
||||
|
||||
_updated.fire({});
|
||||
}
|
||||
|
||||
void Badge::setPremiumClickCallback(Fn<void()> callback) {
|
||||
_premiumClickCallback = std::move(callback);
|
||||
if (_view && _badge == BadgeType::Premium) {
|
||||
if (!_premiumClickCallback) {
|
||||
_view->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
} else {
|
||||
_view->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
_view->setClickedCallback(_premiumClickCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> Badge::updated() const {
|
||||
return _updated.events();
|
||||
}
|
||||
|
||||
void Badge::move(int left, int top, int bottom) {
|
||||
if (!_view) {
|
||||
return;
|
||||
}
|
||||
const auto star = !_emojiStatus
|
||||
&& (_badge == BadgeType::Premium || _badge == BadgeType::Verified);
|
||||
const auto fake = !_emojiStatus && !star;
|
||||
const auto skip = fake ? 0 : _st.position.x();
|
||||
const auto badgeLeft = left + skip;
|
||||
const auto badgeTop = top
|
||||
+ (star
|
||||
? _st.position.y()
|
||||
: (bottom - top - _view->height()) / 2);
|
||||
_view->moveToLeft(badgeLeft, badgeTop);
|
||||
}
|
||||
|
||||
Data::CustomEmojiSizeTag Badge::sizeTag() const {
|
||||
using SizeTag = Data::CustomEmojiSizeTag;
|
||||
return (_st.sizeTag == 2)
|
||||
? SizeTag::Isolated
|
||||
: (_st.sizeTag == 1)
|
||||
? SizeTag::Large
|
||||
: SizeTag::Normal;
|
||||
}
|
||||
|
||||
} // namespace Info::Profile
|
79
Telegram/SourceFiles/info/profile/info_profile_badge.h
Normal file
79
Telegram/SourceFiles/info/profile/info_profile_badge.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/flags.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace style {
|
||||
struct InfoPeerBadge;
|
||||
} // namespace style
|
||||
|
||||
namespace Data {
|
||||
enum class CustomEmojiSizeTag : uchar;
|
||||
} // namespace Data
|
||||
|
||||
namespace Ui {
|
||||
class RpWidget;
|
||||
class AbstractButton;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
class EmojiStatusPanel;
|
||||
|
||||
enum class BadgeType {
|
||||
None = 0x00,
|
||||
Verified = 0x01,
|
||||
Premium = 0x02,
|
||||
Scam = 0x04,
|
||||
Fake = 0x08,
|
||||
};
|
||||
inline constexpr bool is_flag_type(BadgeType) { return true; }
|
||||
|
||||
class Badge final {
|
||||
public:
|
||||
Badge(
|
||||
not_null<QWidget*> parent,
|
||||
const style::InfoPeerBadge &st,
|
||||
not_null<PeerData*> peer,
|
||||
EmojiStatusPanel *emojiStatusPanel,
|
||||
Fn<bool()> animationPaused,
|
||||
int customStatusLoopsLimit = 0,
|
||||
base::flags<BadgeType> allowed = base::flags<BadgeType>::from_raw(-1));
|
||||
|
||||
[[nodiscard]] Ui::RpWidget *widget() const;
|
||||
|
||||
void setPremiumClickCallback(Fn<void()> callback);
|
||||
[[nodiscard]] rpl::producer<> updated() const;
|
||||
void move(int left, int top, int bottom);
|
||||
|
||||
[[nodiscard]] Data::CustomEmojiSizeTag sizeTag() const;
|
||||
|
||||
private:
|
||||
void setBadge(BadgeType badge, DocumentId emojiStatusId);
|
||||
|
||||
const not_null<QWidget*> _parent;
|
||||
const style::InfoPeerBadge &_st;
|
||||
const not_null<PeerData*> _peer;
|
||||
EmojiStatusPanel *_emojiStatusPanel = nullptr;
|
||||
const int _customStatusLoopsLimit = 0;
|
||||
DocumentId _emojiStatusId = 0;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _emojiStatus;
|
||||
std::unique_ptr<Ui::Text::CustomEmojiColored> _emojiStatusColored;
|
||||
base::flags<BadgeType> _allowed;
|
||||
BadgeType _badge = BadgeType();
|
||||
Fn<void()> _premiumClickCallback;
|
||||
Fn<bool()> _animationPaused;
|
||||
object_ptr<Ui::AbstractButton> _view = { nullptr };
|
||||
rpl::event_stream<> _updated;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Profile
|
|
@ -7,48 +7,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "info/profile/info_profile_cover.h"
|
||||
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_chat.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "editor/photo_editor_layer_widget.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "info/info_controller.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "menu/menu_send.h" // SendMenu::Type.
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/time_picker_box.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/unread_badge.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "apiwrap.h"
|
||||
#include "mainwindow.h"
|
||||
#include "api/api_peer_photo.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace Info {
|
||||
namespace Profile {
|
||||
namespace Info::Profile {
|
||||
namespace {
|
||||
|
||||
auto MembersStatusText(int count) {
|
||||
|
@ -83,293 +64,8 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) {
|
|||
: tr::lng_channel_status(tr::now);
|
||||
};
|
||||
|
||||
void PickUntilBox(not_null<Ui::GenericBox*> box, Fn<void(TimeId)> callback) {
|
||||
box->setTitle(tr::lng_emoji_status_for_title());
|
||||
|
||||
const auto seconds = Ui::DefaultTimePickerValues();
|
||||
const auto phrases = ranges::views::all(
|
||||
seconds
|
||||
) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector;
|
||||
|
||||
const auto pickerCallback = Ui::TimePickerBox(box, seconds, phrases, 0);
|
||||
|
||||
Ui::ConfirmBox(box, {
|
||||
.confirmed = [=] {
|
||||
callback(pickerCallback());
|
||||
box->closeBox();
|
||||
},
|
||||
.confirmText = tr::lng_emoji_status_for_submit(),
|
||||
.cancelText = tr::lng_cancel(),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BadgeView::BadgeView(
|
||||
not_null<QWidget*> parent,
|
||||
const style::InfoPeerBadge &st,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<bool()> animationPaused,
|
||||
int customStatusLoopsLimit,
|
||||
base::flags<Badge> allowed)
|
||||
: _parent(parent)
|
||||
, _st(st)
|
||||
, _peer(peer)
|
||||
, _customStatusLoopsLimit(customStatusLoopsLimit)
|
||||
, _allowed(allowed)
|
||||
, _animationPaused(std::move(animationPaused)) {
|
||||
rpl::combine(
|
||||
BadgeValue(peer),
|
||||
EmojiStatusIdValue(peer)
|
||||
) | rpl::start_with_next([=](Badge badge, DocumentId emojiStatusId) {
|
||||
setBadge(badge, emojiStatusId);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
Ui::RpWidget *BadgeView::widget() const {
|
||||
return _view.data();
|
||||
}
|
||||
|
||||
void BadgeView::setBadge(Badge badge, DocumentId emojiStatusId) {
|
||||
if ((!_peer->session().premiumBadgesShown() && badge == Badge::Premium)
|
||||
|| !(_allowed & badge)) {
|
||||
badge = Badge::None;
|
||||
}
|
||||
if (!(_allowed & badge)) {
|
||||
badge = Badge::None;
|
||||
}
|
||||
if (badge != Badge::Premium) {
|
||||
emojiStatusId = 0;
|
||||
}
|
||||
if (_badge == badge && _emojiStatusId == emojiStatusId) {
|
||||
return;
|
||||
}
|
||||
_badge = badge;
|
||||
_emojiStatusId = emojiStatusId;
|
||||
_emojiStatus = nullptr;
|
||||
_emojiStatusColored = nullptr;
|
||||
_view.destroy();
|
||||
if (_badge == Badge::None) {
|
||||
_updated.fire({});
|
||||
return;
|
||||
}
|
||||
_view.create(_parent);
|
||||
_view->show();
|
||||
switch (_badge) {
|
||||
case Badge::Verified:
|
||||
case Badge::Premium: {
|
||||
if (_emojiStatusId) {
|
||||
using SizeTag = Data::CustomEmojiManager::SizeTag;
|
||||
const auto tag = (_st.sizeTag == 2)
|
||||
? SizeTag::Isolated
|
||||
: (_st.sizeTag == 1)
|
||||
? SizeTag::Large
|
||||
: SizeTag::Normal;
|
||||
_emojiStatus = _peer->owner().customEmojiManager().create(
|
||||
_emojiStatusId,
|
||||
[raw = _view.data()] { raw->update(); },
|
||||
tag);
|
||||
if (_customStatusLoopsLimit > 0) {
|
||||
_emojiStatus = std::make_unique<Ui::Text::LimitedLoopsEmoji>(
|
||||
std::move(_emojiStatus),
|
||||
_customStatusLoopsLimit);
|
||||
}
|
||||
_emojiStatusColored = std::make_unique<
|
||||
Ui::Text::CustomEmojiColored
|
||||
>();
|
||||
const auto emoji = Data::FrameSizeFromTag(tag)
|
||||
/ style::DevicePixelRatio();
|
||||
_view->resize(emoji, emoji);
|
||||
_view->paintRequest(
|
||||
) | rpl::start_with_next([=, check = _view.data()]{
|
||||
Painter p(check);
|
||||
_emojiStatusColored->color = _st.premiumFg->c;
|
||||
_emojiStatus->paint(p, {
|
||||
.preview = st::windowBgOver->c,
|
||||
.colored = _emojiStatusColored.get(),
|
||||
.now = crl::now(),
|
||||
.paused = _animationPaused && _animationPaused(),
|
||||
});
|
||||
}, _view->lifetime());
|
||||
} else {
|
||||
const auto icon = (_badge == Badge::Verified)
|
||||
? &_st.verified
|
||||
: &_st.premium;
|
||||
_view->resize(icon->size());
|
||||
_view->paintRequest(
|
||||
) | rpl::start_with_next([=, check = _view.data()]{
|
||||
Painter p(check);
|
||||
icon->paint(p, 0, 0, check->width());
|
||||
}, _view->lifetime());
|
||||
}
|
||||
} break;
|
||||
case Badge::Scam:
|
||||
case Badge::Fake: {
|
||||
const auto fake = (_badge == Badge::Fake);
|
||||
const auto size = Ui::ScamBadgeSize(fake);
|
||||
const auto skip = st::infoVerifiedCheckPosition.x();
|
||||
_view->resize(
|
||||
size.width() + 2 * skip,
|
||||
size.height() + 2 * skip);
|
||||
_view->paintRequest(
|
||||
) | rpl::start_with_next([=, badge = _view.data()]{
|
||||
Painter p(badge);
|
||||
Ui::DrawScamBadge(
|
||||
fake,
|
||||
p,
|
||||
badge->rect().marginsRemoved({ skip, skip, skip, skip }),
|
||||
badge->width(),
|
||||
st::attentionButtonFg);
|
||||
}, _view->lifetime());
|
||||
} break;
|
||||
}
|
||||
|
||||
if (_badge != Badge::Premium || !_premiumClickCallback) {
|
||||
_view->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
} else {
|
||||
_view->setClickedCallback(_premiumClickCallback);
|
||||
}
|
||||
|
||||
_updated.fire({});
|
||||
}
|
||||
|
||||
void BadgeView::setPremiumClickCallback(Fn<void()> callback) {
|
||||
_premiumClickCallback = std::move(callback);
|
||||
if (_view && _badge == Badge::Premium) {
|
||||
if (!_premiumClickCallback) {
|
||||
_view->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
} else {
|
||||
_view->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||
_view->setClickedCallback(_premiumClickCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> BadgeView::updated() const {
|
||||
return _updated.events();
|
||||
}
|
||||
|
||||
void BadgeView::move(int left, int top, int bottom) {
|
||||
if (!_view) {
|
||||
return;
|
||||
}
|
||||
const auto star = !_emojiStatus
|
||||
&& (_badge == Badge::Premium || _badge == Badge::Verified);
|
||||
const auto fake = !_emojiStatus && !star;
|
||||
const auto skip = fake ? 0 : _st.position.x();
|
||||
const auto badgeLeft = left + skip;
|
||||
const auto badgeTop = top
|
||||
+ (star
|
||||
? _st.position.y()
|
||||
: (bottom - top - _view->height()) / 2);
|
||||
_view->moveToLeft(badgeLeft, badgeTop);
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<QWidget*> button) {
|
||||
const auto self = controller->session().user();
|
||||
const auto &statuses = controller->session().data().emojiStatuses();
|
||||
const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent);
|
||||
const auto &other = statuses.list(Data::EmojiStatuses::Type::Default);
|
||||
auto list = statuses.list(Data::EmojiStatuses::Type::Colored);
|
||||
list.insert(begin(list), 0);
|
||||
list.reserve(list.size() + recent.size() + other.size() + 1);
|
||||
for (const auto &id : ranges::views::concat(recent, other)) {
|
||||
if (!ranges::contains(list, id)) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(list, self->emojiStatusId())) {
|
||||
list.push_back(self->emojiStatusId());
|
||||
}
|
||||
if (!_panel) {
|
||||
create(controller);
|
||||
|
||||
const auto weak = Ui::MakeWeak(button.get());
|
||||
_panel->shownValue(
|
||||
) | rpl::filter([=](bool shown) {
|
||||
return !shown && weak;
|
||||
}) | rpl::start_with_next([=] {
|
||||
button->removeEventFilter(_panel.get());
|
||||
}, _panel->lifetime());
|
||||
}
|
||||
_panel->selector()->provideRecentEmoji(list);
|
||||
const auto parent = _panel->parentWidget();
|
||||
const auto global = button->mapToGlobal(QPoint());
|
||||
const auto local = parent->mapFromGlobal(global);
|
||||
_panel->moveTopRight(
|
||||
local.y() + button->height(),
|
||||
local.x() + button->width() * 3);
|
||||
_panel->toggleAnimated();
|
||||
button->installEventFilter(_panel.get());
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::create(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
using Selector = ChatHelpers::TabbedSelector;
|
||||
_panel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
controller->window().widget()->bodyWidget(),
|
||||
controller,
|
||||
object_ptr<Selector>(
|
||||
nullptr,
|
||||
controller,
|
||||
Window::GifPauseReason::Layer,
|
||||
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
|
||||
_panel->setDropDown(true);
|
||||
_panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
st::emojiPanMinHeight);
|
||||
_panel->hide();
|
||||
_panel->selector()->setAllowEmojiWithoutPremium(false);
|
||||
|
||||
struct Chosen {
|
||||
DocumentId id = 0;
|
||||
TimeId until = 0;
|
||||
Ui::MessageSendingAnimationFrom animation;
|
||||
};
|
||||
|
||||
_panel->selector()->contextMenuRequested(
|
||||
) | rpl::start_with_next([=] {
|
||||
_panel->selector()->showMenuWithType(SendMenu::Type::Scheduled);
|
||||
}, _panel->lifetime());
|
||||
|
||||
auto statusChosen = _panel->selector()->customEmojiChosen(
|
||||
) | rpl::map([=](Selector::FileChosen data) {
|
||||
return Chosen{
|
||||
.id = data.document->id,
|
||||
.until = data.options.scheduled,
|
||||
.animation = data.messageSendingFrom,
|
||||
};
|
||||
});
|
||||
|
||||
auto emojiChosen = _panel->selector()->emojiChosen(
|
||||
) | rpl::map([=](Selector::EmojiChosen data) {
|
||||
return Chosen{ .animation = data.messageSendingFrom };
|
||||
});
|
||||
|
||||
rpl::merge(
|
||||
std::move(statusChosen),
|
||||
std::move(emojiChosen)
|
||||
) | rpl::start_with_next([=](const Chosen chosen) {
|
||||
if (chosen.until == ChatHelpers::TabbedSelector::kPickCustomTimeId) {
|
||||
controller->show(Box(PickUntilBox, [=](TimeId seconds) {
|
||||
controller->session().data().emojiStatuses().set(
|
||||
chosen.id,
|
||||
base::unixtime::now() + seconds);
|
||||
}));
|
||||
} else {
|
||||
controller->session().data().emojiStatuses().set(
|
||||
chosen.id,
|
||||
chosen.until);
|
||||
_panel->hideAnimated();
|
||||
}
|
||||
}, _panel->lifetime());
|
||||
|
||||
_panel->selector()->showPromoForPremiumEmoji();
|
||||
}
|
||||
|
||||
Cover::Cover(
|
||||
QWidget *parent,
|
||||
not_null<PeerData*> peer,
|
||||
|
@ -393,14 +89,19 @@ Cover::Cover(
|
|||
+ st::infoProfilePhotoBottom)
|
||||
, _controller(controller)
|
||||
, _peer(peer)
|
||||
, _emojiStatusPanel(peer->isSelf()
|
||||
? std::make_unique<EmojiStatusPanel>()
|
||||
: nullptr)
|
||||
, _badge(
|
||||
this,
|
||||
st::infoPeerBadge,
|
||||
peer,
|
||||
[=] {
|
||||
return controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
})
|
||||
std::make_unique<Badge>(
|
||||
this,
|
||||
st::infoPeerBadge,
|
||||
peer,
|
||||
_emojiStatusPanel.get(),
|
||||
[=] {
|
||||
return controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
}))
|
||||
, _userpic(
|
||||
this,
|
||||
controller,
|
||||
|
@ -423,14 +124,14 @@ Cover::Cover(
|
|||
_status->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
}
|
||||
|
||||
_badge.setPremiumClickCallback([=] {
|
||||
if (_peer->isSelf()) {
|
||||
_emojiStatusPanel.show(_controller, _badge.widget());
|
||||
_badge->setPremiumClickCallback([=] {
|
||||
if (const auto panel = _emojiStatusPanel.get()) {
|
||||
panel->show(_controller, _badge->widget(), _badge->sizeTag());
|
||||
} else {
|
||||
::Settings::ShowEmojiStatusPremium(_controller, _peer);
|
||||
}
|
||||
});
|
||||
_badge.updated() | rpl::start_with_next([=] {
|
||||
_badge->updated() | rpl::start_with_next([=] {
|
||||
refreshNameGeometry(width());
|
||||
}, _name->lifetime());
|
||||
|
||||
|
@ -563,15 +264,15 @@ void Cover::refreshNameGeometry(int newWidth) {
|
|||
auto nameWidth = newWidth
|
||||
- nameLeft
|
||||
- st::infoProfileNameRight;
|
||||
if (const auto width = _badge.widget() ? _badge.widget()->width() : 0) {
|
||||
nameWidth -= st::infoVerifiedCheckPosition.x() + width;
|
||||
if (const auto widget = _badge->widget()) {
|
||||
nameWidth -= st::infoVerifiedCheckPosition.x() + widget->width();
|
||||
}
|
||||
_name->resizeToNaturalWidth(nameWidth);
|
||||
_name->moveToLeft(nameLeft, nameTop, newWidth);
|
||||
const auto badgeLeft = nameLeft + _name->width();
|
||||
const auto badgeTop = nameTop;
|
||||
const auto badgeBottom = nameTop + _name->height();
|
||||
_badge.move(badgeLeft, badgeTop, badgeBottom);
|
||||
_badge->move(badgeLeft, badgeTop, badgeBottom);
|
||||
}
|
||||
|
||||
void Cover::refreshStatusGeometry(int newWidth) {
|
||||
|
@ -585,5 +286,4 @@ void Cover::refreshStatusGeometry(int newWidth) {
|
|||
newWidth);
|
||||
}
|
||||
|
||||
} // namespace Profile
|
||||
} // namespace Info
|
||||
} // namespace Info::Profile
|
||||
|
|
|
@ -8,25 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "base/timer.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace style {
|
||||
struct InfoToggle;
|
||||
struct InfoPeerBadge;
|
||||
} // namespace style
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Ui {
|
||||
class AbstractButton;
|
||||
class UserpicButton;
|
||||
class FlatLabel;
|
||||
template <typename Widget>
|
||||
|
@ -42,66 +30,10 @@ class Controller;
|
|||
class Section;
|
||||
} // namespace Info
|
||||
|
||||
namespace Info {
|
||||
namespace Profile {
|
||||
namespace Info::Profile {
|
||||
|
||||
enum class Badge {
|
||||
None = 0x00,
|
||||
Verified = 0x01,
|
||||
Premium = 0x02,
|
||||
Scam = 0x04,
|
||||
Fake = 0x08,
|
||||
};
|
||||
inline constexpr bool is_flag_type(Badge) { return true; }
|
||||
|
||||
class BadgeView final {
|
||||
public:
|
||||
BadgeView(
|
||||
not_null<QWidget*> parent,
|
||||
const style::InfoPeerBadge &st,
|
||||
not_null<PeerData*> peer,
|
||||
Fn<bool()> animationPaused,
|
||||
int customStatusLoopsLimit = 0,
|
||||
base::flags<Badge> allowed = base::flags<Badge>::from_raw(-1));
|
||||
|
||||
[[nodiscard]] Ui::RpWidget *widget() const;
|
||||
|
||||
void setPremiumClickCallback(Fn<void()> callback);
|
||||
[[nodiscard]] rpl::producer<> updated() const;
|
||||
void move(int left, int top, int bottom);
|
||||
|
||||
private:
|
||||
void setBadge(Badge badge, DocumentId emojiStatusId);
|
||||
|
||||
const not_null<QWidget*> _parent;
|
||||
const style::InfoPeerBadge &_st;
|
||||
const not_null<PeerData*> _peer;
|
||||
const int _customStatusLoopsLimit = 0;
|
||||
DocumentId _emojiStatusId = 0;
|
||||
std::unique_ptr<Ui::Text::CustomEmoji> _emojiStatus;
|
||||
std::unique_ptr<Ui::Text::CustomEmojiColored> _emojiStatusColored;
|
||||
base::flags<Badge> _allowed;
|
||||
Badge _badge = Badge();
|
||||
Fn<void()> _premiumClickCallback;
|
||||
Fn<bool()> _animationPaused;
|
||||
object_ptr<Ui::AbstractButton> _view = { nullptr };
|
||||
rpl::event_stream<> _updated;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class EmojiStatusPanel final {
|
||||
public:
|
||||
void show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<QWidget*> button);
|
||||
|
||||
private:
|
||||
void create(not_null<Window::SessionController*> controller);
|
||||
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
|
||||
|
||||
};
|
||||
class EmojiStatusPanel;
|
||||
class Badge;
|
||||
|
||||
class Cover final : public Ui::FixedHeightWidget {
|
||||
public:
|
||||
|
@ -114,15 +46,14 @@ public:
|
|||
not_null<PeerData*> peer,
|
||||
not_null<Window::SessionController*> controller,
|
||||
rpl::producer<QString> title);
|
||||
~Cover();
|
||||
|
||||
Cover *setOnlineCount(rpl::producer<int> &&count);
|
||||
|
||||
rpl::producer<Section> showSection() const {
|
||||
[[nodiscard]] rpl::producer<Section> showSection() const {
|
||||
return _showSection.events();
|
||||
}
|
||||
|
||||
~Cover();
|
||||
|
||||
private:
|
||||
void setupChildGeometry();
|
||||
void initViewers(rpl::producer<QString> title);
|
||||
|
@ -133,8 +64,8 @@ private:
|
|||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<PeerData*> _peer;
|
||||
BadgeView _badge;
|
||||
EmojiStatusPanel _emojiStatusPanel;
|
||||
const std::unique_ptr<EmojiStatusPanel> _emojiStatusPanel;
|
||||
const std::unique_ptr<Badge> _badge;
|
||||
int _onlineCount = 0;
|
||||
|
||||
object_ptr<Ui::UserpicButton> _userpic;
|
||||
|
@ -147,5 +78,4 @@ private:
|
|||
|
||||
};
|
||||
|
||||
} // namespace Profile
|
||||
} // namespace Info
|
||||
} // namespace Info::Profile
|
||||
|
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_emoji_statuses.h"
|
||||
#include "data/stickers/data_custom_emoji.h"
|
||||
#include "history/view/reactions/history_view_reactions_animation.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "menu/menu_send.h" // SendMenu::Type.
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
#include "ui/boxes/time_picker_box.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "main/main_session.h"
|
||||
#include "mainwindow.h"
|
||||
#include "chat_helpers/tabbed_panel.h"
|
||||
#include "chat_helpers/tabbed_selector.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_info.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
namespace Info::Profile {
|
||||
namespace {
|
||||
|
||||
void PickUntilBox(not_null<Ui::GenericBox*> box, Fn<void(TimeId)> callback) {
|
||||
box->setTitle(tr::lng_emoji_status_for_title());
|
||||
|
||||
const auto seconds = Ui::DefaultTimePickerValues();
|
||||
const auto phrases = ranges::views::all(
|
||||
seconds
|
||||
) | ranges::views::transform(Ui::FormatMuteFor) | ranges::to_vector;
|
||||
|
||||
const auto pickerCallback = Ui::TimePickerBox(box, seconds, phrases, 0);
|
||||
|
||||
Ui::ConfirmBox(box, {
|
||||
.confirmed = [=] {
|
||||
callback(pickerCallback());
|
||||
box->closeBox();
|
||||
},
|
||||
.confirmText = tr::lng_emoji_status_for_submit(),
|
||||
.cancelText = tr::lng_cancel(),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class EmojiStatusPanel::Animation {
|
||||
public:
|
||||
Animation(
|
||||
not_null<Ui::RpWidget*> body,
|
||||
not_null<Data::Reactions*> owner,
|
||||
HistoryView::Reactions::AnimationArgs &&args,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag);
|
||||
|
||||
[[nodiscard]] not_null<Ui::RpWidget*> layer();
|
||||
[[nodiscard]] bool finished() const;
|
||||
|
||||
void repaint();
|
||||
bool paintBadgeFrame(not_null<Ui::RpWidget*> widget);
|
||||
|
||||
private:
|
||||
const int _flySize = 0;
|
||||
HistoryView::Reactions::Animation _fly;
|
||||
Ui::RpWidget _layer;
|
||||
QRect _area;
|
||||
bool _areaUpdated = false;
|
||||
QPointer<Ui::RpWidget> _target;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] int ComputeFlySize(Data::CustomEmojiSizeTag tag) {
|
||||
using Tag = Data::CustomEmojiSizeTag;
|
||||
if (tag == Tag::Normal) {
|
||||
return st::reactionInlineImage;
|
||||
}
|
||||
return int(base::SafeRound(
|
||||
(st::reactionInlineImage * Data::FrameSizeFromTag(tag)
|
||||
/ float64(Data::FrameSizeFromTag(Tag::Normal)))));
|
||||
}
|
||||
|
||||
EmojiStatusPanel::Animation::Animation(
|
||||
not_null<Ui::RpWidget*> body,
|
||||
not_null<Data::Reactions*> owner,
|
||||
HistoryView::Reactions::AnimationArgs &&args,
|
||||
Fn<void()> repaint,
|
||||
Data::CustomEmojiSizeTag tag)
|
||||
: _flySize(ComputeFlySize(tag))
|
||||
, _fly(
|
||||
owner,
|
||||
std::move(args),
|
||||
std::move(repaint),
|
||||
_flySize,
|
||||
tag)
|
||||
, _layer(body) {
|
||||
body->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||
_layer.setGeometry(QRect(QPoint(), size));
|
||||
}, _layer.lifetime());
|
||||
|
||||
_layer.paintRequest(
|
||||
) | rpl::start_with_next([=](QRect clip) {
|
||||
const auto target = _target.data();
|
||||
if (!target || !target->isVisible()) {
|
||||
return;
|
||||
}
|
||||
auto p = QPainter(&_layer);
|
||||
|
||||
const auto rect = Ui::MapFrom(&_layer, target, target->rect());
|
||||
const auto skipx = (rect.width() - _flySize) / 2;
|
||||
const auto skipy = (rect.height() - _flySize) / 2;
|
||||
const auto area = _fly.paintGetArea(
|
||||
p,
|
||||
QPoint(),
|
||||
QRect(
|
||||
rect.topLeft() + QPoint(skipx, skipy),
|
||||
QSize(_flySize, _flySize)),
|
||||
st::infoPeerBadge.premiumFg->c,
|
||||
clip,
|
||||
crl::now());
|
||||
if (_areaUpdated || _area.isEmpty()) {
|
||||
_area = area;
|
||||
} else {
|
||||
_area = _area.united(area);
|
||||
}
|
||||
}, _layer.lifetime());
|
||||
|
||||
_layer.setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
_layer.show();
|
||||
}
|
||||
|
||||
not_null<Ui::RpWidget*> EmojiStatusPanel::Animation::layer() {
|
||||
return &_layer;
|
||||
}
|
||||
|
||||
bool EmojiStatusPanel::Animation::finished() const {
|
||||
if (const auto target = _target.data()) {
|
||||
return _fly.finished() || !target->isVisible();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::Animation::repaint() {
|
||||
if (_area.isEmpty()) {
|
||||
_layer.update();
|
||||
} else {
|
||||
_layer.update(_area);
|
||||
_areaUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool EmojiStatusPanel::Animation::paintBadgeFrame(
|
||||
not_null<Ui::RpWidget*> widget) {
|
||||
_target = widget;
|
||||
return !_fly.finished();
|
||||
}
|
||||
|
||||
EmojiStatusPanel::EmojiStatusPanel() = default;
|
||||
|
||||
EmojiStatusPanel::~EmojiStatusPanel() = default;
|
||||
|
||||
void EmojiStatusPanel::show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<QWidget*> button,
|
||||
Data::CustomEmojiSizeTag animationSizeTag) {
|
||||
const auto self = controller->session().user();
|
||||
const auto &statuses = controller->session().data().emojiStatuses();
|
||||
const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent);
|
||||
const auto &other = statuses.list(Data::EmojiStatuses::Type::Default);
|
||||
auto list = statuses.list(Data::EmojiStatuses::Type::Colored);
|
||||
list.insert(begin(list), 0);
|
||||
list.reserve(list.size() + recent.size() + other.size() + 1);
|
||||
for (const auto &id : ranges::views::concat(recent, other)) {
|
||||
if (!ranges::contains(list, id)) {
|
||||
list.push_back(id);
|
||||
}
|
||||
}
|
||||
if (!ranges::contains(list, self->emojiStatusId())) {
|
||||
list.push_back(self->emojiStatusId());
|
||||
}
|
||||
if (!_panel) {
|
||||
create(controller);
|
||||
|
||||
_panel->shownValue(
|
||||
) | rpl::filter([=] {
|
||||
return (_panelButton != nullptr);
|
||||
}) | rpl::start_with_next([=](bool shown) {
|
||||
if (shown) {
|
||||
_panelButton->installEventFilter(_panel.get());
|
||||
} else {
|
||||
_panelButton->removeEventFilter(_panel.get());
|
||||
}
|
||||
}, _panel->lifetime());
|
||||
}
|
||||
if (const auto previous = _panelButton.data()) {
|
||||
if (previous != button) {
|
||||
previous->removeEventFilter(_panel.get());
|
||||
}
|
||||
}
|
||||
_panelButton = button;
|
||||
_animationSizeTag = animationSizeTag;
|
||||
_panel->selector()->provideRecentEmoji(list);
|
||||
const auto parent = _panel->parentWidget();
|
||||
const auto global = button->mapToGlobal(QPoint());
|
||||
const auto local = parent->mapFromGlobal(global);
|
||||
_panel->moveTopRight(
|
||||
local.y() + button->height(),
|
||||
local.x() + button->width() * 3);
|
||||
_panel->toggleAnimated();
|
||||
}
|
||||
|
||||
bool EmojiStatusPanel::paintBadgeFrame(not_null<Ui::RpWidget*> widget) {
|
||||
if (!_animation) {
|
||||
return false;
|
||||
} else if (_animation->paintBadgeFrame(widget)) {
|
||||
return true;
|
||||
}
|
||||
InvokeQueued(_animation->layer(), [=] { _animation = nullptr; });
|
||||
return false;
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::create(
|
||||
not_null<Window::SessionController*> controller) {
|
||||
using Selector = ChatHelpers::TabbedSelector;
|
||||
const auto body = controller->window().widget()->bodyWidget();
|
||||
_panel = base::make_unique_q<ChatHelpers::TabbedPanel>(
|
||||
body,
|
||||
controller,
|
||||
object_ptr<Selector>(
|
||||
nullptr,
|
||||
controller,
|
||||
Window::GifPauseReason::Layer,
|
||||
ChatHelpers::TabbedSelector::Mode::EmojiStatus));
|
||||
_panel->setDropDown(true);
|
||||
_panel->setDesiredHeightValues(
|
||||
1.,
|
||||
st::emojiPanMinHeight / 2,
|
||||
st::emojiPanMinHeight);
|
||||
_panel->hide();
|
||||
_panel->selector()->setAllowEmojiWithoutPremium(false);
|
||||
|
||||
struct Chosen {
|
||||
DocumentId id = 0;
|
||||
TimeId until = 0;
|
||||
Ui::MessageSendingAnimationFrom animation;
|
||||
};
|
||||
|
||||
_panel->selector()->contextMenuRequested(
|
||||
) | rpl::start_with_next([=] {
|
||||
_panel->selector()->showMenuWithType(SendMenu::Type::Scheduled);
|
||||
}, _panel->lifetime());
|
||||
|
||||
auto statusChosen = _panel->selector()->customEmojiChosen(
|
||||
) | rpl::map([=](Selector::FileChosen data) {
|
||||
return Chosen{
|
||||
.id = data.document->id,
|
||||
.until = data.options.scheduled,
|
||||
.animation = data.messageSendingFrom,
|
||||
};
|
||||
});
|
||||
|
||||
auto emojiChosen = _panel->selector()->emojiChosen(
|
||||
) | rpl::map([=](Selector::EmojiChosen data) {
|
||||
return Chosen{ .animation = data.messageSendingFrom };
|
||||
});
|
||||
|
||||
const auto set = [=](Chosen chosen) {
|
||||
Expects(chosen.until != Selector::kPickCustomTimeId);
|
||||
|
||||
const auto owner = &controller->session().data();
|
||||
startAnimation(owner, body, chosen.id, chosen.animation);
|
||||
owner->emojiStatuses().set(chosen.id, chosen.until);
|
||||
};
|
||||
|
||||
rpl::merge(
|
||||
std::move(statusChosen),
|
||||
std::move(emojiChosen)
|
||||
) | rpl::start_with_next([=](const Chosen chosen) {
|
||||
if (chosen.until == Selector::kPickCustomTimeId) {
|
||||
controller->show(Box(PickUntilBox, [=](TimeId seconds) {
|
||||
set({ chosen.id, base::unixtime::now() + seconds });
|
||||
}));
|
||||
} else {
|
||||
set(chosen);
|
||||
_panel->hideAnimated();
|
||||
}
|
||||
}, _panel->lifetime());
|
||||
|
||||
_panel->selector()->showPromoForPremiumEmoji();
|
||||
}
|
||||
|
||||
void EmojiStatusPanel::startAnimation(
|
||||
not_null<Data::Session*> owner,
|
||||
not_null<Ui::RpWidget*> body,
|
||||
DocumentId statusId,
|
||||
Ui::MessageSendingAnimationFrom from) {
|
||||
if (!_panelButton) {
|
||||
return;
|
||||
}
|
||||
auto args = HistoryView::Reactions::AnimationArgs{
|
||||
.id = { { statusId } },
|
||||
.flyIcon = from.frame,
|
||||
.flyFrom = body->mapFromGlobal(from.globalStartGeometry),
|
||||
};
|
||||
_animation = std::make_unique<Animation>(
|
||||
body,
|
||||
&owner->reactions(),
|
||||
std::move(args),
|
||||
[=] { _animation->repaint(); },
|
||||
_animationSizeTag);
|
||||
}
|
||||
|
||||
} // namespace Info::Profile
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
enum class CustomEmojiSizeTag : uchar;
|
||||
} // namespace Data
|
||||
|
||||
namespace Window {
|
||||
class SessionController;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
struct MessageSendingAnimationFrom;
|
||||
class RpWidget;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
struct CustomEmojiPaintContext;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace ChatHelpers {
|
||||
class TabbedPanel;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Info::Profile {
|
||||
|
||||
class EmojiStatusPanel final {
|
||||
public:
|
||||
EmojiStatusPanel();
|
||||
~EmojiStatusPanel();
|
||||
|
||||
void show(
|
||||
not_null<Window::SessionController*> controller,
|
||||
not_null<QWidget*> button,
|
||||
Data::CustomEmojiSizeTag animationSizeTag = {});
|
||||
|
||||
bool paintBadgeFrame(not_null<Ui::RpWidget*> widget);
|
||||
|
||||
private:
|
||||
class Animation;
|
||||
|
||||
void create(not_null<Window::SessionController*> controller);
|
||||
|
||||
void startAnimation(
|
||||
not_null<Data::Session*> owner,
|
||||
not_null<Ui::RpWidget*> body,
|
||||
DocumentId statusId,
|
||||
Ui::MessageSendingAnimationFrom from);
|
||||
|
||||
base::unique_qptr<ChatHelpers::TabbedPanel> _panel;
|
||||
QPointer<QWidget> _panelButton;
|
||||
std::unique_ptr<Animation> _animation;
|
||||
Data::CustomEmojiSizeTag _animationSizeTag = {};
|
||||
|
||||
};
|
||||
|
||||
} // namespace Info::Profile
|
|
@ -93,12 +93,12 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
|
|||
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||
auto min = (request.ymin < 0)
|
||||
? request.ymin
|
||||
: mapFromGlobal(_members->mapToGlobal({ 0, request.ymin })).y();
|
||||
: MapFrom(this, _members, QPoint(0, request.ymin)).y();
|
||||
auto max = (request.ymin < 0)
|
||||
? mapFromGlobal(_members->mapToGlobal({ 0, 0 })).y()
|
||||
? MapFrom(this, _members, QPoint()).y()
|
||||
: (request.ymax < 0)
|
||||
? request.ymax
|
||||
: mapFromGlobal(_members->mapToGlobal({ 0, request.ymax })).y();
|
||||
: MapFrom(this, _members, QPoint(0, request.ymax)).y();
|
||||
_scrollToRequests.fire({ min, max });
|
||||
}, _members->lifetime());
|
||||
_cover->setOnlineCount(_members->onlineCountValue());
|
||||
|
|
|
@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "info/profile/info_profile_values.h"
|
||||
|
||||
#include "info/profile/info_profile_cover.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "core/application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "countries/countries_instance.h"
|
||||
|
@ -456,7 +456,7 @@ rpl::producer<int> FullReactionsCountValue(
|
|||
}
|
||||
|
||||
template <typename Flag, typename Peer>
|
||||
rpl::producer<Badge> BadgeValueFromFlags(Peer peer) {
|
||||
rpl::producer<BadgeType> BadgeValueFromFlags(Peer peer) {
|
||||
return rpl::combine(
|
||||
Data::PeerFlagsValue(
|
||||
peer,
|
||||
|
@ -464,24 +464,24 @@ rpl::producer<Badge> BadgeValueFromFlags(Peer peer) {
|
|||
Data::PeerPremiumValue(peer)
|
||||
) | rpl::map([=](base::flags<Flag> value, bool premium) {
|
||||
return (value & Flag::Scam)
|
||||
? Badge::Scam
|
||||
? BadgeType::Scam
|
||||
: (value & Flag::Fake)
|
||||
? Badge::Fake
|
||||
? BadgeType::Fake
|
||||
: (value & Flag::Verified)
|
||||
? Badge::Verified
|
||||
? BadgeType::Verified
|
||||
: premium
|
||||
? Badge::Premium
|
||||
: Badge::None;
|
||||
? BadgeType::Premium
|
||||
: BadgeType::None;
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<Badge> BadgeValue(not_null<PeerData*> peer) {
|
||||
rpl::producer<BadgeType> BadgeValue(not_null<PeerData*> peer) {
|
||||
if (const auto user = peer->asUser()) {
|
||||
return BadgeValueFromFlags<UserDataFlag>(user);
|
||||
} else if (const auto channel = peer->asChannel()) {
|
||||
return BadgeValueFromFlags<ChannelDataFlag>(channel);
|
||||
}
|
||||
return rpl::single(Badge::None);
|
||||
return rpl::single(BadgeType::None);
|
||||
}
|
||||
|
||||
rpl::producer<DocumentId> EmojiStatusIdValue(not_null<PeerData*> peer) {
|
||||
|
|
|
@ -90,8 +90,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
|
|||
[[nodiscard]] rpl::producer<int> FullReactionsCountValue(
|
||||
not_null<Main::Session*> peer);
|
||||
|
||||
enum class Badge;
|
||||
[[nodiscard]] rpl::producer<Badge> BadgeValue(not_null<PeerData*> peer);
|
||||
enum class BadgeType;
|
||||
[[nodiscard]] rpl::producer<BadgeType> BadgeValue(not_null<PeerData*> peer);
|
||||
[[nodiscard]] rpl::producer<DocumentId> EmojiStatusIdValue(
|
||||
not_null<PeerData*> peer);
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_premium_limits.h"
|
||||
#include "dialogs/ui/dialogs_layout.h"
|
||||
#include "info/profile/info_profile_values.h"
|
||||
#include "info/profile/info_profile_cover.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_account.h"
|
||||
#include "main/main_session.h"
|
||||
|
@ -82,7 +82,7 @@ private:
|
|||
rpl::event_stream<int> _premiumWidth;
|
||||
|
||||
QPointer<Ui::RpWidget> _unread;
|
||||
Info::Profile::BadgeView _badge;
|
||||
Info::Profile::Badge _badge;
|
||||
|
||||
};
|
||||
|
||||
|
@ -99,9 +99,10 @@ ComposedBadge::ComposedBadge(
|
|||
this,
|
||||
st::settingsInfoPeerBadge,
|
||||
session->user(),
|
||||
nullptr,
|
||||
std::move(animationPaused),
|
||||
kPlayStatusLimit,
|
||||
Info::Profile::Badge::Premium) {
|
||||
Info::Profile::BadgeType::Premium) {
|
||||
if (hasUnread) {
|
||||
_unread = CreateUnread(this, rpl::single(
|
||||
rpl::empty
|
||||
|
|
|
@ -30,7 +30,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "info/profile/info_profile_cover.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "data/data_user.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
|
@ -85,8 +86,8 @@ private:
|
|||
|
||||
const not_null<Window::SessionController*> _controller;
|
||||
const not_null<UserData*> _user;
|
||||
Info::Profile::BadgeView _badge;
|
||||
Info::Profile::EmojiStatusPanel _emojiStatusPanel;
|
||||
Info::Profile::Badge _badge;
|
||||
|
||||
object_ptr<Ui::UserpicButton> _userpic;
|
||||
object_ptr<Ui::FlatLabel> _name = { nullptr };
|
||||
|
@ -110,12 +111,13 @@ Cover::Cover(
|
|||
this,
|
||||
st::infoPeerBadge,
|
||||
user,
|
||||
&_emojiStatusPanel,
|
||||
[=] {
|
||||
return controller->isGifPausedAtLeastFor(
|
||||
Window::GifPauseReason::Layer);
|
||||
},
|
||||
0, // customStatusLoopsLimit
|
||||
Info::Profile::Badge::Premium)
|
||||
Info::Profile::BadgeType::Premium)
|
||||
, _userpic(
|
||||
this,
|
||||
controller,
|
||||
|
@ -145,7 +147,10 @@ Cover::Cover(
|
|||
}, _userpic->lifetime());
|
||||
|
||||
_badge.setPremiumClickCallback([=] {
|
||||
_emojiStatusPanel.show(_controller, _badge.widget());
|
||||
_emojiStatusPanel.show(
|
||||
_controller,
|
||||
_badge.widget(),
|
||||
_badge.sizeTag());
|
||||
});
|
||||
_badge.updated() | rpl::start_with_next([=] {
|
||||
refreshNameGeometry(width());
|
||||
|
|
|
@ -208,7 +208,8 @@ using Order = std::vector<QString>;
|
|||
u"faster_download"_q,
|
||||
u"voice_to_text"_q,
|
||||
u"no_ads"_q,
|
||||
u"unique_reactions"_q,
|
||||
u"emoji_status"_q,
|
||||
u"infinite_reactions"_q,
|
||||
u"premium_stickers"_q,
|
||||
u"animated_emoji"_q,
|
||||
u"advanced_chat_management"_q,
|
||||
|
@ -264,12 +265,21 @@ using Order = std::vector<QString>;
|
|||
},
|
||||
},
|
||||
{
|
||||
u"unique_reactions"_q,
|
||||
u"emoji_status"_q,
|
||||
Entry{
|
||||
&st::settingsPremiumIconLike,
|
||||
tr::lng_premium_summary_subtitle_unique_reactions(),
|
||||
tr::lng_premium_summary_about_unique_reactions(),
|
||||
PremiumPreview::Reactions,
|
||||
tr::lng_premium_summary_subtitle_emoji_status(),
|
||||
tr::lng_premium_summary_about_emoji_status(),
|
||||
PremiumPreview::EmojiStatus,
|
||||
},
|
||||
},
|
||||
{
|
||||
u"infinite_reactions"_q,
|
||||
Entry{
|
||||
&st::settingsPremiumIconLike,
|
||||
tr::lng_premium_summary_subtitle_infinite_reactions(),
|
||||
tr::lng_premium_summary_about_infinite_reactions(),
|
||||
PremiumPreview::InfiniteReactions,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -343,26 +343,6 @@ bool ShowSendPremiumError(
|
|||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ExtractDisabledReactions(
|
||||
not_null<PeerData*> peer,
|
||||
const std::vector<Data::Reaction> &list)
|
||||
-> base::flat_map<Data::ReactionId, ReactionDisableType> {
|
||||
auto result = base::flat_map<Data::ReactionId, ReactionDisableType>();
|
||||
const auto type = peer->isBroadcast()
|
||||
? ReactionDisableType::Channel
|
||||
: ReactionDisableType::Group;
|
||||
const auto &allowed = Data::PeerAllowedReactions(peer);
|
||||
if (!allowed.some.empty()) {
|
||||
for (const auto &reaction : list) {
|
||||
if (reaction.premium
|
||||
&& !ranges::contains(allowed.some, reaction.id)) {
|
||||
result.emplace(reaction.id, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ShowReactPremiumError(
|
||||
not_null<SessionController*> controller,
|
||||
not_null<HistoryItem*> item,
|
||||
|
@ -379,22 +359,8 @@ bool ShowReactPremiumError(
|
|||
return false;
|
||||
}
|
||||
}
|
||||
ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::Reactions,
|
||||
ExtractDisabledReactions(item->history()->peer, list));
|
||||
ShowPremiumPreviewBox(controller, PremiumPreview::InfiniteReactions);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShowPremiumPromoBox(
|
||||
not_null<SessionController*> controller,
|
||||
not_null<HistoryItem*> item) {
|
||||
const auto &list = controller->session().data().reactions().list(
|
||||
Data::Reactions::Type::Active);
|
||||
ShowPremiumPreviewBox(
|
||||
controller,
|
||||
PremiumPreview::Reactions,
|
||||
ExtractDisabledReactions(item->history()->peer, list));
|
||||
}
|
||||
|
||||
} // namespace Window
|
||||
|
|
|
@ -219,8 +219,4 @@ private:
|
|||
not_null<HistoryItem*> item,
|
||||
const Data::ReactionId &id);
|
||||
|
||||
void ShowPremiumPromoBox(
|
||||
not_null<SessionController*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
|
||||
} // namespace Window
|
||||
|
|
|
@ -34,7 +34,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "settings/settings_common.h"
|
||||
#include "settings/settings_calls.h"
|
||||
#include "settings/settings_information.h"
|
||||
#include "info/profile/info_profile_cover.h"
|
||||
#include "info/profile/info_profile_badge.h"
|
||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "boxes/about_box.h"
|
||||
#include "ui/boxes/confirm_box.h"
|
||||
|
@ -338,14 +339,15 @@ MainMenu::MainMenu(
|
|||
Ui::UserpicButton::Role::Custom,
|
||||
st::mainMenuUserpic)
|
||||
, _toggleAccounts(this)
|
||||
, _badge(std::make_unique<Info::Profile::BadgeView>(
|
||||
, _emojiStatusPanel(std::make_unique<Info::Profile::EmojiStatusPanel>())
|
||||
, _badge(std::make_unique<Info::Profile::Badge>(
|
||||
this,
|
||||
st::settingsInfoPeerBadge,
|
||||
controller->session().user(),
|
||||
_emojiStatusPanel.get(),
|
||||
[=] { return controller->isGifPausedAtLeastFor(GifPauseReason::Layer); },
|
||||
kPlayStatusLimit,
|
||||
Info::Profile::Badge::Premium))
|
||||
, _emojiStatusPanel(std::make_unique<Info::Profile::EmojiStatusPanel>())
|
||||
Info::Profile::BadgeType::Premium))
|
||||
, _scroll(this, st::defaultSolidScroll)
|
||||
, _inner(_scroll->setOwnedWidget(
|
||||
object_ptr<Ui::VerticalLayout>(_scroll.data())))
|
||||
|
@ -438,7 +440,10 @@ MainMenu::MainMenu(
|
|||
moveBadge();
|
||||
}, lifetime());
|
||||
_badge->setPremiumClickCallback([=] {
|
||||
_emojiStatusPanel->show(_controller, _badge->widget());
|
||||
_emojiStatusPanel->show(
|
||||
_controller,
|
||||
_badge->widget(),
|
||||
_badge->sizeTag());
|
||||
});
|
||||
|
||||
_controller->session().downloaderTaskFinished(
|
||||
|
|
|
@ -29,7 +29,7 @@ class SlideWrap;
|
|||
} // namespace Ui
|
||||
|
||||
namespace Info::Profile {
|
||||
class BadgeView;
|
||||
class Badge;
|
||||
class EmojiStatusPanel;
|
||||
} // namespace Info::Profile
|
||||
|
||||
|
@ -77,8 +77,8 @@ private:
|
|||
Ui::Text::String _name;
|
||||
int _nameVersion = 0;
|
||||
object_ptr<ToggleAccountsButton> _toggleAccounts;
|
||||
std::unique_ptr<Info::Profile::BadgeView> _badge;
|
||||
std::unique_ptr<Info::Profile::EmojiStatusPanel> _emojiStatusPanel;
|
||||
std::unique_ptr<Info::Profile::Badge> _badge;
|
||||
object_ptr<ResetScaleButton> _resetScaleButton = { nullptr };
|
||||
object_ptr<Ui::ScrollArea> _scroll;
|
||||
not_null<Ui::VerticalLayout*> _inner;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 4ec399f169e9308cd5da194b6fa2104578c39e45
|
||||
Subproject commit 12b8b3804c1b458c124ca150de4320e3fda5035e
|
Loading…
Add table
Reference in a new issue