Request default and recent emoji statuses.

This commit is contained in:
John Preston 2022-08-30 19:21:14 +04:00
parent b2d72e2541
commit 923e725e18
15 changed files with 549 additions and 87 deletions

View file

@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
#include "data/data_emoji_statuses.h"
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_drafts.h" #include "data/data_drafts.h"
#include "data/data_histories.h" #include "data/data_histories.h"
@ -2388,7 +2389,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} break; } break;
case mtpc_updateRecentEmojiStatuses: { case mtpc_updateRecentEmojiStatuses: {
// #TODO emoji_status session().data().emojiStatuses().refreshRecentDelayed();
} break; } break;
case mtpc_updateRecentReactions: { case mtpc_updateRecentReactions: {

View file

@ -419,7 +419,7 @@ void AddReactionCustomIcon(
Fn<void()> repaint; Fn<void()> repaint;
}; };
const auto state = stateLifetime->make_state<State>(); const auto state = stateLifetime->make_state<State>();
static constexpr auto tag = Data::CustomEmojiManager::SizeTag::Large; static constexpr auto tag = Data::CustomEmojiManager::SizeTag::Normal;
state->custom = controller->session().data().customEmojiManager().create( state->custom = controller->session().data().customEmojiManager().create(
customId, customId,
[=] { state->repaint(); }, [=] { state->repaint(); },
@ -445,6 +445,7 @@ void AddReactionCustomIcon(
paintCallback, paintCallback,
std::move(destroys), std::move(destroys),
stateLifetime); stateLifetime);
state->repaint = crl::guard(widget, [=] { widget->update(); });
} }
void ReactionsSettingsBox( void ReactionsSettingsBox(

View file

@ -454,6 +454,13 @@ EmojiListWidget::~EmojiListWidget() {
base::take(_customEmoji); base::take(_customEmoji);
} }
void EmojiListWidget::provideRecent(
const std::vector<DocumentId> &customRecentList) {
clearSelection();
fillRecentFrom(customRecentList);
resizeToWidth(width());
}
void EmojiListWidget::repaintCustom(uint64 setId) { void EmojiListWidget::repaintCustom(uint64 setId) {
if (!_repaintsScheduled.emplace(setId).second) { if (!_repaintsScheduled.emplace(setId).second) {
return; return;
@ -730,24 +737,18 @@ void EmojiListWidget::ensureLoaded(int section) {
} }
void EmojiListWidget::fillRecent() { void EmojiListWidget::fillRecent() {
if (_mode != Mode::Full && _mode != Mode::EmojiStatus) { if (_mode != Mode::Full) {
return; // #TODO emoji_status return;
} }
_recent.clear(); _recent.clear();
_recentCustomIds.clear(); _recentCustomIds.clear();
const auto &list = Core::App().settings().recentEmoji(); const auto &list = Core::App().settings().recentEmoji();
_recent.reserve(std::min(int(list.size()), Core::kRecentEmojiLimit) + 1); _recent.reserve(std::min(int(list.size()), Core::kRecentEmojiLimit) + 1);
if (_mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
}
const auto test = session().isTestMode(); const auto test = session().isTestMode();
for (const auto &one : list) { for (const auto &one : list) {
const auto document = std::get_if<RecentEmojiDocument>(&one.id.data); const auto document = std::get_if<RecentEmojiDocument>(&one.id.data);
if (_mode == Mode::EmojiStatus && !document) { if (document && document->test != test) {
continue;
} else if (document && document->test != test) {
continue; continue;
} }
_recent.push_back({ _recent.push_back({
@ -770,6 +771,10 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
_recent.reserve(list.size()); _recent.reserve(list.size());
for (const auto &id : list) { for (const auto &id : list) {
if (!id && _mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } });
} else {
_recent.push_back({ _recent.push_back({
.custom = resolveCustomRecent(id), .custom = resolveCustomRecent(id),
.id = { RecentEmojiDocument{.id = id, .test = test } }, .id = { RecentEmojiDocument{.id = id, .test = test } },
@ -777,6 +782,7 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
_recentCustomIds.emplace(id); _recentCustomIds.emplace(id);
} }
} }
}
void EmojiListWidget::paintEvent(QPaintEvent *e) { void EmojiListWidget::paintEvent(QPaintEvent *e) {
Painter p(this); Painter p(this);
@ -1445,8 +1451,8 @@ void EmojiListWidget::processPanelHideFinished() {
} }
void EmojiListWidget::refreshRecent() { void EmojiListWidget::refreshRecent() {
if (_mode != Mode::Full && _mode != Mode::EmojiStatus) { if (_mode != Mode::Full) {
return; // #TODO emoji_status return;
} }
clearSelection(); clearSelection();
fillRecent(); fillRecent();

View file

@ -113,6 +113,8 @@ public:
-> rpl::producer<not_null<DocumentData*>>; -> rpl::producer<not_null<DocumentData*>>;
[[nodiscard]] rpl::producer<> jumpedToPremium() const; [[nodiscard]] rpl::producer<> jumpedToPremium() const;
void provideRecent(const std::vector<DocumentId> &customRecentList);
void paintExpanding( void paintExpanding(
QPainter &p, QPainter &p,
QRect clip, QRect clip,

View file

@ -872,6 +872,16 @@ void TabbedSelector::showPromoForPremiumEmoji() {
}, lifetime()); }, lifetime());
} }
void TabbedSelector::provideRecentEmoji(
const std::vector<DocumentId> &customRecentList) {
for (const auto &tab : _tabs) {
if (tab.type() == SelectorTab::Emoji) {
const auto emoji = static_cast<EmojiListWidget*>(tab.widget());
emoji->provideRecent(customRecentList);
}
}
}
void TabbedSelector::checkRestrictedPeer() { void TabbedSelector::checkRestrictedPeer() {
if (_currentPeer) { if (_currentPeer) {
const auto error = (_currentTabType == SelectorTab::Stickers) const auto error = (_currentTabType == SelectorTab::Stickers)

View file

@ -109,6 +109,7 @@ public:
void refreshStickers(); void refreshStickers();
void setCurrentPeer(PeerData *peer); void setCurrentPeer(PeerData *peer);
void showPromoForPremiumEmoji(); void showPromoForPremiumEmoji();
void provideRecentEmoji(const std::vector<DocumentId> &customRecentList);
void hideFinished(); void hideFinished();
void showStarted(); void showStarted();

View file

@ -0,0 +1,173 @@
/*
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 "data/data_emoji_statuses.h"
//
#include "main/main_session.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "base/timer_rpl.h"
#include "base/call_delayed.h"
#include "apiwrap.h"
namespace Data {
namespace {
constexpr auto kRefreshDefaultListEach = 60 * 60 * crl::time(1000);
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
[[nodiscard]] DocumentId Parse(const MTPEmojiStatus &status) {
return status.match([&](const MTPDemojiStatus &data) {
return DocumentId(data.vdocument_id().v);
}, [](const MTPDemojiStatusEmpty &) {
return DocumentId();
});
}
[[nodiscard]] std::vector<DocumentId> ListFromMTP(
const MTPDaccount_emojiStatuses &data) {
const auto &list = data.vstatuses().v;
auto result = std::vector<DocumentId>();
result.reserve(list.size());
for (const auto &status : list) {
const auto id = Parse(status);
if (!id) {
LOG(("API Error: emojiStatusEmpty in account.emojiStatuses."));
} else {
result.push_back(id);
}
}
return result;
}
} // namespace
EmojiStatuses::EmojiStatuses(not_null<Session*> owner)
: _owner(owner)
, _defaultRefreshTimer([=] { refreshDefault(); }) {
refreshDefault();
base::timer_each(
kRefreshDefaultListEach
) | rpl::start_with_next([=] {
refreshDefault();
}, _lifetime);
}
EmojiStatuses::~EmojiStatuses() = default;
Main::Session &EmojiStatuses::session() const {
return _owner->session();
}
void EmojiStatuses::refreshRecent() {
requestRecent();
}
void EmojiStatuses::refreshDefault() {
requestDefault();
}
void EmojiStatuses::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) {
return;
}
_recentRequestScheduled = true;
base::call_delayed(kRecentRequestTimeout, &_owner->session(), [=] {
if (_recentRequestScheduled) {
requestRecent();
}
});
}
const std::vector<DocumentId> &EmojiStatuses::list(Type type) const {
switch (type) {
case Type::Recent: return _recent;
case Type::Default: return _default;
}
Unexpected("Type in EmojiStatuses::list.");
}
rpl::producer<> EmojiStatuses::recentUpdates() const {
return _recentUpdated.events();
}
rpl::producer<> EmojiStatuses::defaultUpdates() const {
return _defaultUpdated.events();
}
void EmojiStatuses::requestRecent() {
if (_recentRequestId) {
return;
}
auto &api = _owner->session().api();
_recentRequestScheduled = false;
_recentRequestId = api.request(MTPaccount_GetRecentEmojiStatuses(
MTP_long(_recentHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_recentRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
updateRecent(data);
}, [](const MTPDaccount_emojiStatusesNotModified&) {
});
}).fail([=] {
_recentRequestId = 0;
_recentHash = 0;
}).send();
}
void EmojiStatuses::requestDefault() {
if (_defaultRequestId) {
return;
}
auto &api = _owner->session().api();
_defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_recentHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_defaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
updateDefault(data);
}, [&](const MTPDaccount_emojiStatusesNotModified &) {
});
}).fail([=] {
_defaultRequestId = 0;
_defaultHash = 0;
}).send();
}
void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
_recentHash = data.vhash().v;
_recent = ListFromMTP(data);
_recentUpdated.fire({});
}
void EmojiStatuses::updateDefault(const MTPDaccount_emojiStatuses &data) {
_defaultHash = data.vhash().v;
_default = ListFromMTP(data);
_defaultUpdated.fire({});
}
void EmojiStatuses::set(DocumentId id) {
auto &api = _owner->session().api();
if (_sentRequestId) {
api.request(base::take(_sentRequestId)).cancel();
}
_owner->session().user()->setEmojiStatus(id);
_sentRequestId = api.request(MTPaccount_UpdateEmojiStatus(
id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty()
)).done([=] {
_sentRequestId = 0;
}).fail([=] {
_sentRequestId = 0;
}).send();
}
bool EmojiStatuses::setting() const {
return _sentRequestId != 0;;
}
} // namespace Data

View 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/timer.h"
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Main {
class Session;
} // namespace Main
namespace Data {
class DocumentMedia;
class Session;
class EmojiStatuses final {
public:
explicit EmojiStatuses(not_null<Session*> owner);
~EmojiStatuses();
[[nodiscard]] Session &owner() const {
return *_owner;
}
[[nodiscard]] Main::Session &session() const;
void refreshRecent();
void refreshRecentDelayed();
void refreshDefault();
enum class Type {
Recent,
Default,
};
[[nodiscard]] const std::vector<DocumentId> &list(Type type) const;
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
void set(DocumentId id);
[[nodiscard]] bool setting() const;
private:
void requestRecent();
void requestDefault();
void updateRecent(const MTPDaccount_emojiStatuses &data);
void updateDefault(const MTPDaccount_emojiStatuses &data);
const not_null<Session*> _owner;
std::vector<DocumentId> _recent;
std::vector<DocumentId> _default;
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false;
uint64 _recentHash = 0;
base::Timer _defaultRefreshTimer;
mtpRequestId _defaultRequestId = 0;
uint64 _defaultHash = 0;
mtpRequestId _sentRequestId = 0;
rpl::lifetime _lifetime;
};
} // namespace Data

View file

@ -38,7 +38,7 @@ constexpr auto kSizeForDownscale = 64;
constexpr auto kRecentRequestTimeout = 10 * crl::time(1000); constexpr auto kRecentRequestTimeout = 10 * crl::time(1000);
constexpr auto kRecentReactionsLimit = 40; constexpr auto kRecentReactionsLimit = 40;
constexpr auto kTopRequestDelay = 60 * crl::time(1000); constexpr auto kTopRequestDelay = 60 * crl::time(1000);
constexpr auto kTopReactionsLimit = 10; constexpr auto kTopReactionsLimit = 14;
[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) { [[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
if (const auto custom = id.custom()) { if (const auto custom = id.custom()) {
@ -107,7 +107,7 @@ PossibleItemReactionsRef LookupPossibleReactions(
}(); }();
auto added = base::flat_set<ReactionId>(); auto added = base::flat_set<ReactionId>();
const auto add = [&](auto predicate) { const auto add = [&](auto predicate) {
auto &&all = ranges::views::concat(recent, top, full); auto &&all = ranges::views::concat(top, recent, full);
for (const auto &reaction : all) { for (const auto &reaction : all) {
if (predicate(reaction)) { if (predicate(reaction)) {
if (added.emplace(reaction.id).second) { if (added.emplace(reaction.id).second) {

View file

@ -60,6 +60,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_send_action.h" #include "data/data_send_action.h"
#include "data/data_sponsored_messages.h" #include "data/data_sponsored_messages.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/data_emoji_statuses.h"
#include "data/data_cloud_themes.h" #include "data/data_cloud_themes.h"
#include "data/data_streaming.h" #include "data/data_streaming.h"
#include "data/data_media_rotation.h" #include "data/data_media_rotation.h"
@ -252,6 +253,7 @@ Session::Session(not_null<Main::Session*> session)
, _stickers(std::make_unique<Stickers>(this)) , _stickers(std::make_unique<Stickers>(this))
, _sponsoredMessages(std::make_unique<SponsoredMessages>(this)) , _sponsoredMessages(std::make_unique<SponsoredMessages>(this))
, _reactions(std::make_unique<Reactions>(this)) , _reactions(std::make_unique<Reactions>(this))
, _emojiStatuses(std::make_unique<EmojiStatuses>(this))
, _notifySettings(std::make_unique<NotifySettings>(this)) , _notifySettings(std::make_unique<NotifySettings>(this))
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this)) { , _customEmojiManager(std::make_unique<CustomEmojiManager>(this)) {
_cache->open(_session->local().cacheKey()); _cache->open(_session->local().cacheKey());

View file

@ -52,6 +52,7 @@ class ScheduledMessages;
class SendActionManager; class SendActionManager;
class SponsoredMessages; class SponsoredMessages;
class Reactions; class Reactions;
class EmojiStatuses;
class ChatFilters; class ChatFilters;
class CloudThemes; class CloudThemes;
class Streaming; class Streaming;
@ -118,6 +119,9 @@ public:
[[nodiscard]] Reactions &reactions() const { [[nodiscard]] Reactions &reactions() const {
return *_reactions; return *_reactions;
} }
[[nodiscard]] EmojiStatuses &emojiStatuses() const {
return *_emojiStatuses;
}
[[nodiscard]] NotifySettings &notifySettings() const { [[nodiscard]] NotifySettings &notifySettings() const {
return *_notifySettings; return *_notifySettings;
} }
@ -981,6 +985,7 @@ private:
const std::unique_ptr<Stickers> _stickers; const std::unique_ptr<Stickers> _stickers;
std::unique_ptr<SponsoredMessages> _sponsoredMessages; std::unique_ptr<SponsoredMessages> _sponsoredMessages;
const std::unique_ptr<Reactions> _reactions; const std::unique_ptr<Reactions> _reactions;
const std::unique_ptr<EmojiStatuses> _emojiStatuses;
const std::unique_ptr<NotifySettings> _notifySettings; const std::unique_ptr<NotifySettings> _notifySettings;
const std::unique_ptr<CustomEmojiManager> _customEmojiManager; const std::unique_ptr<CustomEmojiManager> _customEmojiManager;

View file

@ -33,6 +33,7 @@ constexpr auto kExpandDuration = crl::time(300);
constexpr auto kScaleDuration = crl::time(120); constexpr auto kScaleDuration = crl::time(120);
constexpr auto kFullDuration = kExpandDuration + kScaleDuration; constexpr auto kFullDuration = kExpandDuration + kScaleDuration;
constexpr auto kExpandDelay = crl::time(40); constexpr auto kExpandDelay = crl::time(40);
constexpr auto kDefaultColumns = 8;
class StripEmoji final : public Ui::Text::CustomEmoji { class StripEmoji final : public Ui::Text::CustomEmoji {
public: public:
@ -105,19 +106,59 @@ Selector::Selector(
const Data::PossibleItemReactionsRef &reactions, const Data::PossibleItemReactionsRef &reactions,
IconFactory iconFactory, IconFactory iconFactory,
Fn<void(bool fast)> close) Fn<void(bool fast)> close)
: Selector(
parent,
parentController,
reactions,
(reactions.customAllowed
? ChatHelpers::EmojiListMode::FullReactions
: ChatHelpers::EmojiListMode::RecentReactions),
{},
iconFactory,
close) {
}
Selector::Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
Fn<void(bool fast)> close)
: Selector(
parent,
parentController,
{ .customAllowed = true },
mode,
std::move(recent),
nullptr,
close) {
}
Selector::Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
const Data::PossibleItemReactionsRef &reactions,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
IconFactory iconFactory,
Fn<void(bool fast)> close)
: RpWidget(parent) : RpWidget(parent)
, _parentController(parentController.get()) , _parentController(parentController.get())
, _reactions(reactions) , _reactions(reactions)
, _recent(std::move(recent))
, _listMode(mode)
, _jumpedToPremium([=] { close(false); }) , _jumpedToPremium([=] { close(false); })
, _cachedRound( , _cachedRound(
QSize(2 * st::reactStripSkip + st::reactStripSize, st::reactStripHeight), QSize(2 * st::reactStripSkip + st::reactStripSize, st::reactStripHeight),
st::reactionCornerShadow, st::reactionCornerShadow,
st::reactStripHeight) st::reactStripHeight)
, _strip( , _strip(iconFactory
? std::make_unique<Strip>(
QRect(0, 0, st::reactStripSize, st::reactStripSize), QRect(0, 0, st::reactStripSize, st::reactStripSize),
st::reactStripImage, st::reactStripImage,
crl::guard(this, [=] { update(_inner); }), crl::guard(this, [=] { update(_inner); }),
std::move(iconFactory)) std::move(iconFactory))
: nullptr)
, _size(st::reactStripSize) , _size(st::reactStripSize)
, _skipx(countSkipLeft()) , _skipx(countSkipLeft())
, _skipy((st::reactStripHeight - st::reactStripSize) / 2) { , _skipy((st::reactStripHeight - st::reactStripSize) / 2) {
@ -129,10 +170,14 @@ Selector::Selector(
}, lifetime()); }, lifetime());
} }
int Selector::recentCount() const {
return int(_strip ? _reactions.recent.size() : _recent.size());
}
int Selector::countSkipLeft() const { int Selector::countSkipLeft() const {
const auto addedToMax = _reactions.customAllowed const auto addedToMax = _reactions.customAllowed
|| _reactions.morePremiumAvailable; || _reactions.morePremiumAvailable;
const auto max = int(_reactions.recent.size()) + (addedToMax ? 1 : 0); const auto max = recentCount() + (addedToMax ? 1 : 0);
const auto width = max * _size; const auto width = max * _size;
return std::max( return std::max(
(st::reactStripMinWidth - (max * _size)) / 2, (st::reactStripMinWidth - (max * _size)) / 2,
@ -142,13 +187,13 @@ int Selector::countSkipLeft() const {
int Selector::countWidth(int desiredWidth, int maxWidth) { int Selector::countWidth(int desiredWidth, int maxWidth) {
const auto addedToMax = _reactions.customAllowed const auto addedToMax = _reactions.customAllowed
|| _reactions.morePremiumAvailable; || _reactions.morePremiumAvailable;
const auto max = int(_reactions.recent.size()) + (addedToMax ? 1 : 0); const auto max = recentCount() + (addedToMax ? 1 : 0);
const auto possibleColumns = std::min( const auto possibleColumns = std::min(
(desiredWidth - 2 * _skipx + _size - 1) / _size, (desiredWidth - 2 * _skipx + _size - 1) / _size,
(maxWidth - 2 * _skipx) / _size); (maxWidth - 2 * _skipx) / _size);
_columns = std::min(possibleColumns, max); _columns = _strip ? std::min(possibleColumns, max) : kDefaultColumns;
_small = (possibleColumns - _columns > 1); _small = (possibleColumns - _columns > 1);
_recentRows = (_reactions.recent.size() _recentRows = (recentCount()
+ (_reactions.morePremiumAvailable ? 1 : 0) + (_reactions.morePremiumAvailable ? 1 : 0)
+ _columns - 1) / _columns; + _columns - 1) / _columns;
const auto added = (_columns < max || _reactions.customAllowed) const auto added = (_columns < max || _reactions.customAllowed)
@ -156,6 +201,7 @@ int Selector::countWidth(int desiredWidth, int maxWidth) {
: _reactions.morePremiumAvailable : _reactions.morePremiumAvailable
? Strip::AddedButton::Premium ? Strip::AddedButton::Premium
: Strip::AddedButton::None; : Strip::AddedButton::None;
if (_strip) {
const auto &real = _reactions.recent; const auto &real = _reactions.recent;
auto list = std::vector<not_null<const Data::Reaction*>>(); auto list = std::vector<not_null<const Data::Reaction*>>();
list.reserve(_columns); list.reserve(_columns);
@ -170,8 +216,9 @@ int Selector::countWidth(int desiredWidth, int maxWidth) {
list.push_back(&reaction); list.push_back(&reaction);
} }
} }
_strip.applyList(list, added); _strip->applyList(list, added);
_strip.clearAppearAnimations(false); _strip->clearAppearAnimations(false);
}
return std::max(2 * _skipx + _columns * _size, desiredWidth); return std::max(2 * _skipx + _columns * _size, desiredWidth);
} }
@ -211,6 +258,10 @@ void Selector::initGeometry(int innerTop) {
{ 0, _collapsedTopSkip, 0, 0 } { 0, _collapsedTopSkip, 0, 0 }
).translated(left, top)); ).translated(left, top));
_inner = _outer.marginsRemoved(extents); _inner = _outer.marginsRemoved(extents);
if (!_strip) {
expand();
}
} }
void Selector::updateShowState( void Selector::updateShowState(
@ -239,6 +290,8 @@ void Selector::updateShowState(
} }
void Selector::paintAppearing(QPainter &p) { void Selector::paintAppearing(QPainter &p) {
Expects(_strip != nullptr);
p.setOpacity(_appearOpacity); p.setOpacity(_appearOpacity);
const auto factor = style::DevicePixelRatio(); const auto factor = style::DevicePixelRatio();
@ -256,7 +309,7 @@ void Selector::paintAppearing(QPainter &p) {
const auto size = QSize(fullWidth, _outer.height()); const auto size = QSize(fullWidth, _outer.height());
q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip)); q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip));
_strip.paint( _strip->paint(
q, q,
{ _skipx, _skipy }, { _skipx, _skipy },
{ _size, 0 }, { _size, 0 },
@ -305,11 +358,13 @@ void Selector::paintBackgroundToBuffer() {
} }
void Selector::paintCollapsed(QPainter &p) { void Selector::paintCollapsed(QPainter &p) {
Expects(_strip != nullptr);
if (_paintBuffer.isNull()) { if (_paintBuffer.isNull()) {
paintBackgroundToBuffer(); paintBackgroundToBuffer();
} }
p.drawImage(_outer.topLeft(), _paintBuffer); p.drawImage(_outer.topLeft(), _paintBuffer);
_strip.paint( _strip->paint(
p, p,
_inner.topLeft() + QPoint(_skipx, _skipy), _inner.topLeft() + QPoint(_skipx, _skipy),
{ _size, 0 }, { _size, 0 },
@ -441,9 +496,9 @@ void Selector::paintBubble(QPainter &p, int innerWidth) {
void Selector::paintEvent(QPaintEvent *e) { void Selector::paintEvent(QPaintEvent *e) {
auto p = Painter(this); auto p = Painter(this);
if (_appearing) { if (_strip && _appearing) {
paintAppearing(p); paintAppearing(p);
} else if (!_expanded) { } else if (_strip && !_expanded) {
paintCollapsed(p); paintCollapsed(p);
} else if (const auto progress = _expanding.value(kFullDuration) } else if (const auto progress = _expanding.value(kFullDuration)
; progress < kFullDuration) { ; progress < kFullDuration) {
@ -454,12 +509,15 @@ void Selector::paintEvent(QPaintEvent *e) {
} }
void Selector::mouseMoveEvent(QMouseEvent *e) { void Selector::mouseMoveEvent(QMouseEvent *e) {
if (!_strip) {
return;
}
setSelected(lookupSelectedIndex(e->pos())); setSelected(lookupSelectedIndex(e->pos()));
} }
int Selector::lookupSelectedIndex(QPoint position) const { int Selector::lookupSelectedIndex(QPoint position) const {
const auto p = position - _inner.topLeft() - QPoint(_skipx, _skipy); const auto p = position - _inner.topLeft() - QPoint(_skipx, _skipy);
const auto max = _strip.count(); const auto max = _strip->count();
const auto index = p.x() / _size; const auto index = p.x() / _size;
if (p.x() >= 0 && p.y() >= 0 && p.y() < _inner.height() && index < max) { if (p.x() >= 0 && p.y() >= 0 && p.y() < _inner.height() && index < max) {
return index; return index;
@ -468,10 +526,12 @@ int Selector::lookupSelectedIndex(QPoint position) const {
} }
void Selector::setSelected(int index) { void Selector::setSelected(int index) {
Expects(_strip != nullptr);
if (index >= 0 && _expandScheduled) { if (index >= 0 && _expandScheduled) {
return; return;
} }
_strip.setSelected(index); _strip->setSelected(index);
const auto over = (index >= 0); const auto over = (index >= 0);
if (_over != over) { if (_over != over) {
_over = over; _over = over;
@ -485,19 +545,25 @@ void Selector::setSelected(int index) {
} }
void Selector::leaveEventHook(QEvent *e) { void Selector::leaveEventHook(QEvent *e) {
if (!_strip) {
return;
}
setSelected(-1); setSelected(-1);
} }
void Selector::mousePressEvent(QMouseEvent *e) { void Selector::mousePressEvent(QMouseEvent *e) {
if (!_strip) {
return;
}
_pressed = lookupSelectedIndex(e->pos()); _pressed = lookupSelectedIndex(e->pos());
} }
void Selector::mouseReleaseEvent(QMouseEvent *e) { void Selector::mouseReleaseEvent(QMouseEvent *e) {
if (_pressed != lookupSelectedIndex(e->pos())) { if (!_strip || _pressed != lookupSelectedIndex(e->pos())) {
return; return;
} }
_pressed = -1; _pressed = -1;
const auto selected = _strip.selected(); const auto selected = _strip->selected();
if (selected == Strip::AddedButton::Premium) { if (selected == Strip::AddedButton::Premium) {
_premiumPromoChosen.fire({}); _premiumPromoChosen.fire({});
} else if (selected == Strip::AddedButton::Expand) { } else if (selected == Strip::AddedButton::Expand) {
@ -547,19 +613,23 @@ void Selector::expand() {
} }
void Selector::cacheExpandIcon() { void Selector::cacheExpandIcon() {
if (!_strip) {
return;
}
_expandIconCache = _cachedRound.PrepareImage({ _size, _size }); _expandIconCache = _cachedRound.PrepareImage({ _size, _size });
_expandIconCache.fill(Qt::transparent); _expandIconCache.fill(Qt::transparent);
auto q = QPainter(&_expandIconCache); auto q = QPainter(&_expandIconCache);
_strip.paintOne(q, _strip.count() - 1, { 0, 0 }, 1.); _strip->paintOne(q, _strip->count() - 1, { 0, 0 }, 1.);
} }
void Selector::createList(not_null<Window::SessionController*> controller) { void Selector::createList(not_null<Window::SessionController*> controller) {
using namespace ChatHelpers; using namespace ChatHelpers;
auto recent = std::vector<DocumentId>(); auto recent = _recent;
auto defaultReactionIds = base::flat_map<DocumentId, QString>(); auto defaultReactionIds = base::flat_map<DocumentId, QString>();
recent.reserve(_reactions.recent.size()); if (_strip) {
recent.reserve(recentCount());
auto index = 0; auto index = 0;
const auto inStrip = _strip.count(); const auto inStrip = _strip->count();
for (const auto &reaction : _reactions.recent) { for (const auto &reaction : _reactions.recent) {
if (const auto id = reaction.id.custom()) { if (const auto id = reaction.id.custom()) {
recent.push_back(id); recent.push_back(id);
@ -571,6 +641,7 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
_defaultReactionInStripMap.emplace(recent.back(), index++); _defaultReactionInStripMap.emplace(recent.back(), index++);
} }
}; };
}
const auto manager = &controller->session().data().customEmojiManager(); const auto manager = &controller->session().data().customEmojiManager();
_stripPaintOneShift = [&] { _stripPaintOneShift = [&] {
// See EmojiListWidget custom emoji position resolving. // See EmojiListWidget custom emoji position resolving.
@ -603,9 +674,10 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
: manager->create(id, std::move(repaint), tag); : manager->create(id, std::move(repaint), tag);
const auto i = _defaultReactionInStripMap.find(id); const auto i = _defaultReactionInStripMap.find(id);
if (i != end(_defaultReactionInStripMap)) { if (i != end(_defaultReactionInStripMap)) {
Assert(_strip != nullptr);
return std::make_unique<StripEmoji>( return std::make_unique<StripEmoji>(
std::move(result), std::move(result),
&_strip, _strip.get(),
-_stripPaintOneShift, -_stripPaintOneShift,
i->second); i->second);
} }
@ -623,9 +695,7 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
_list = _scroll->setOwnedWidget( _list = _scroll->setOwnedWidget(
object_ptr<EmojiListWidget>(_scroll, EmojiListDescriptor{ object_ptr<EmojiListWidget>(_scroll, EmojiListDescriptor{
.session = &controller->session(), .session = &controller->session(),
.mode = (_reactions.customAllowed .mode = _listMode,
? EmojiListMode::FullReactions
: EmojiListMode::RecentReactions),
.controller = controller, .controller = controller,
.paused = [] { return false; }, .paused = [] { return false; },
.customRecentList = std::move(recent), .customRecentList = std::move(recent),
@ -770,6 +840,65 @@ bool AdjustMenuGeometryForSelector(
return menu->prepareGeometryFor(desiredPosition); return menu->prepareGeometryFor(desiredPosition);
} }
AttachSelectorResult MakeJustSelectorMenu(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> controller,
QPoint desiredPosition,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
Fn<void(ChosenReaction)> chosen) {
const auto selector = Ui::CreateChild<Selector>(
menu.get(),
controller,
mode,
std::move(recent),
[=](bool fast) { menu->hideMenu(fast); });
if (!AdjustMenuGeometryForSelector(menu, desiredPosition, selector)) {
return AttachSelectorResult::Failed;
}
const auto selectorInnerTop = menu->preparedPadding().top()
- st::reactStripExtend.top();
selector->initGeometry(selectorInnerTop);
selector->show();
selector->chosen() | rpl::start_with_next([=](ChosenReaction reaction) {
menu->hideMenu();
chosen(std::move(reaction));
}, selector->lifetime());
const auto correctTop = selector->y();
menu->showStateValue(
) | rpl::start_with_next([=](Ui::PopupMenu::ShowState state) {
const auto origin = menu->preparedOrigin();
using Origin = Ui::PanelAnimation::Origin;
if (origin == Origin::BottomLeft || origin == Origin::BottomRight) {
const auto add = state.appearing
? (menu->rect().marginsRemoved(
menu->preparedPadding()
).height() - state.appearingHeight)
: 0;
selector->move(selector->x(), correctTop + add);
}
selector->updateShowState(
state.widthProgress * state.heightProgress,
state.opacity,
state.appearing,
state.toggling);
}, selector->lifetime());
const auto weak = base::make_weak(controller.get());
controller->enableGifPauseReason(
Window::GifPauseReason::MediaPreview);
QObject::connect(menu.get(), &QObject::destroyed, [weak] {
if (const auto strong = weak.get()) {
strong->disableGifPauseReason(
Window::GifPauseReason::MediaPreview);
}
});
return AttachSelectorResult::Attached;
}
AttachSelectorResult AttachSelectorToMenu( AttachSelectorResult AttachSelectorToMenu(
not_null<Ui::PopupMenu*> menu, not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,

View file

@ -22,6 +22,7 @@ namespace ChatHelpers {
class TabbedPanel; class TabbedPanel;
class EmojiListWidget; class EmojiListWidget;
class StickersListFooter; class StickersListFooter;
enum class EmojiListMode;
} // namespace ChatHelpers } // namespace ChatHelpers
namespace Window { namespace Window {
@ -43,6 +44,12 @@ public:
const Data::PossibleItemReactionsRef &reactions, const Data::PossibleItemReactionsRef &reactions,
IconFactory iconFactory, IconFactory iconFactory,
Fn<void(bool fast)> close); Fn<void(bool fast)> close);
Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
Fn<void(bool fast)> close);
int countWidth(int desiredWidth, int maxWidth); int countWidth(int desiredWidth, int maxWidth);
[[nodiscard]] QMargins extentsForShadow() const; [[nodiscard]] QMargins extentsForShadow() const;
@ -74,6 +81,15 @@ private:
int finalBottom = 0; int finalBottom = 0;
}; };
Selector(
not_null<QWidget*> parent,
not_null<Window::SessionController*> parentController,
const Data::PossibleItemReactionsRef &reactions,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
IconFactory iconFactory,
Fn<void(bool fast)> close);
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override;
@ -89,6 +105,7 @@ private:
void paintBubble(QPainter &p, int innerWidth); void paintBubble(QPainter &p, int innerWidth);
void paintBackgroundToBuffer(); void paintBackgroundToBuffer();
[[nodiscard]] int recentCount() const;
[[nodiscard]] int countSkipLeft() const; [[nodiscard]] int countSkipLeft() const;
[[nodiscard]] int lookupSelectedIndex(QPoint position) const; [[nodiscard]] int lookupSelectedIndex(QPoint position) const;
void setSelected(int index); void setSelected(int index);
@ -100,12 +117,14 @@ private:
const base::weak_ptr<Window::SessionController> _parentController; const base::weak_ptr<Window::SessionController> _parentController;
const Data::PossibleItemReactions _reactions; const Data::PossibleItemReactions _reactions;
const std::vector<DocumentId> _recent;
const ChatHelpers::EmojiListMode _listMode;
Fn<void()> _jumpedToPremium; Fn<void()> _jumpedToPremium;
base::flat_map<DocumentId, int> _defaultReactionInStripMap; base::flat_map<DocumentId, int> _defaultReactionInStripMap;
Ui::RoundAreaWithShadow _cachedRound; Ui::RoundAreaWithShadow _cachedRound;
QPoint _defaultReactionShift; QPoint _defaultReactionShift;
QPoint _stripPaintOneShift; QPoint _stripPaintOneShift;
Strip _strip; std::unique_ptr<Strip> _strip;
rpl::event_stream<ChosenReaction> _chosen; rpl::event_stream<ChosenReaction> _chosen;
rpl::event_stream<> _premiumPromoChosen; rpl::event_stream<> _premiumPromoChosen;
@ -149,6 +168,15 @@ enum class AttachSelectorResult {
Failed, Failed,
Attached, Attached,
}; };
AttachSelectorResult MakeJustSelectorMenu(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> controller,
QPoint desiredPosition,
ChatHelpers::EmojiListMode mode,
std::vector<DocumentId> recent,
Fn<void(ChosenReaction)> chosen);
AttachSelectorResult AttachSelectorToMenu( AttachSelectorResult AttachSelectorToMenu(
not_null<Ui::PopupMenu*> menu, not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_document.h" #include "data/data_document.h"
#include "data/data_emoji_statuses.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "editor/photo_editor_layer_widget.h" #include "editor/photo_editor_layer_widget.h"
#include "info/profile/info_profile_values.h" #include "info/profile/info_profile_values.h"
@ -231,6 +232,20 @@ void BadgeView::move(int left, int top, int bottom) {
void EmojiStatusPanel::show( void EmojiStatusPanel::show(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<QWidget*> button) { not_null<QWidget*> button) {
const auto self = controller->session().user();
const auto &statuses = controller->session().data().emojiStatuses();
const auto &other = statuses.list(Data::EmojiStatuses::Type::Default);
auto list = statuses.list(Data::EmojiStatuses::Type::Recent);
list.insert(begin(list), 0);
list.reserve(list.size() + other.size() + 1);
for (const auto &otherId : other) {
if (!ranges::contains(list, otherId)) {
list.push_back(otherId);
}
}
if (!ranges::contains(list, self->emojiStatusId())) {
list.push_back(self->emojiStatusId());
}
if (!_panel) { if (!_panel) {
create(controller); create(controller);
@ -242,6 +257,7 @@ void EmojiStatusPanel::show(
button->removeEventFilter(_panel.get()); button->removeEventFilter(_panel.get());
}, _panel->lifetime()); }, _panel->lifetime());
} }
_panel->selector()->provideRecentEmoji(list);
const auto parent = _panel->parentWidget(); const auto parent = _panel->parentWidget();
const auto global = button->mapToGlobal(QPoint()); const auto global = button->mapToGlobal(QPoint());
const auto local = parent->mapFromGlobal(global); const auto local = parent->mapFromGlobal(global);
@ -280,10 +296,7 @@ void EmojiStatusPanel::create(
std::move(statusChosen), std::move(statusChosen),
_panel->selector()->emojiChosen() | rpl::map_to(DocumentId()) _panel->selector()->emojiChosen() | rpl::map_to(DocumentId())
) | rpl::start_with_next([=](DocumentId id) { ) | rpl::start_with_next([=](DocumentId id) {
controller->session().user()->setEmojiStatus(id); controller->session().data().emojiStatuses().set(id);
controller->session().api().request(MTPaccount_UpdateEmojiStatus(
id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty()
)).send();
_panel->hideAnimated(); _panel->hideAnimated();
}, _panel->lifetime()); }, _panel->lifetime());

View file

@ -919,14 +919,13 @@ void SetupMessages(
selected selected
) | rpl::start_with_next([=, idValue = std::move(idValue)]( ) | rpl::start_with_next([=, idValue = std::move(idValue)](
const Data::ReactionId &id) { const Data::ReactionId &id) {
const auto &reactions = controller->session().data().reactions();
for (const auto &r : reactions.list(Data::Reactions::Type::All)) {
if (id != r.id) {
continue;
}
const auto index = state->icons.flag ? 1 : 0; const auto index = state->icons.flag ? 1 : 0;
state->icons.lifetimes[index] = rpl::lifetime();
const auto iconSize = st::settingsReactionRightIcon; const auto iconSize = st::settingsReactionRightIcon;
const auto &reactions = controller->session().data().reactions();
const auto &list = reactions.list(Data::Reactions::Type::All);
const auto i = ranges::find(list, id, &Data::Reaction::id);
state->icons.lifetimes[index] = rpl::lifetime();
if (i != end(list)) {
AddReactionAnimatedIcon( AddReactionAnimatedIcon(
inner, inner,
buttonRight->geometryValue( buttonRight->geometryValue(
@ -936,17 +935,30 @@ void SetupMessages(
r.top() + (r.height() - iconSize) / 2); r.top() + (r.height() - iconSize) / 2);
}), }),
iconSize, iconSize,
r, *i,
buttonRight->events( buttonRight->events(
) | rpl::filter([=](not_null<QEvent*> event) { ) | rpl::filter([=](not_null<QEvent*> event) {
return event->type() == QEvent::Enter; return event->type() == QEvent::Enter;
}) | rpl::to_empty, }) | rpl::to_empty,
rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty, rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
&state->icons.lifetimes[index]); &state->icons.lifetimes[index]);
} else if (const auto customId = id.custom()) {
AddReactionCustomIcon(
inner,
buttonRight->geometryValue(
) | rpl::map([=](const QRect &r) {
return QPoint(
r.left() + (r.width() - iconSize) / 2,
r.top() + (r.height() - iconSize) / 2);
}),
iconSize,
controller,
customId,
rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
&state->icons.lifetimes[index]);
}
state->icons.flag = !state->icons.flag; state->icons.flag = !state->icons.flag;
toggleButtonRight(true); toggleButtonRight(true);
break;
}
}, buttonRight->lifetime()); }, buttonRight->lifetime());
react->geometryValue( react->geometryValue(