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_chat_filters.h"
#include "data/data_cloud_themes.h"
#include "data/data_emoji_statuses.h"
#include "data/data_group_call.h"
#include "data/data_drafts.h"
#include "data/data_histories.h"
@ -2388,7 +2389,7 @@ void Updates::feedUpdate(const MTPUpdate &update) {
} break;
case mtpc_updateRecentEmojiStatuses: {
// #TODO emoji_status
session().data().emojiStatuses().refreshRecentDelayed();
} break;
case mtpc_updateRecentReactions: {

View file

@ -419,7 +419,7 @@ void AddReactionCustomIcon(
Fn<void()> repaint;
};
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(
customId,
[=] { state->repaint(); },
@ -445,6 +445,7 @@ void AddReactionCustomIcon(
paintCallback,
std::move(destroys),
stateLifetime);
state->repaint = crl::guard(widget, [=] { widget->update(); });
}
void ReactionsSettingsBox(

View file

@ -454,6 +454,13 @@ EmojiListWidget::~EmojiListWidget() {
base::take(_customEmoji);
}
void EmojiListWidget::provideRecent(
const std::vector<DocumentId> &customRecentList) {
clearSelection();
fillRecentFrom(customRecentList);
resizeToWidth(width());
}
void EmojiListWidget::repaintCustom(uint64 setId) {
if (!_repaintsScheduled.emplace(setId).second) {
return;
@ -730,24 +737,18 @@ void EmojiListWidget::ensureLoaded(int section) {
}
void EmojiListWidget::fillRecent() {
if (_mode != Mode::Full && _mode != Mode::EmojiStatus) {
return; // #TODO emoji_status
if (_mode != Mode::Full) {
return;
}
_recent.clear();
_recentCustomIds.clear();
const auto &list = Core::App().settings().recentEmoji();
_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();
for (const auto &one : list) {
const auto document = std::get_if<RecentEmojiDocument>(&one.id.data);
if (_mode == Mode::EmojiStatus && !document) {
continue;
} else if (document && document->test != test) {
if (document && document->test != test) {
continue;
}
_recent.push_back({
@ -770,11 +771,16 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
_recent.reserve(list.size());
for (const auto &id : list) {
_recent.push_back({
.custom = resolveCustomRecent(id),
.id = { RecentEmojiDocument{.id = id, .test = test } },
});
_recentCustomIds.emplace(id);
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({
.custom = resolveCustomRecent(id),
.id = { RecentEmojiDocument{.id = id, .test = test } },
});
_recentCustomIds.emplace(id);
}
}
}
@ -1445,8 +1451,8 @@ void EmojiListWidget::processPanelHideFinished() {
}
void EmojiListWidget::refreshRecent() {
if (_mode != Mode::Full && _mode != Mode::EmojiStatus) {
return; // #TODO emoji_status
if (_mode != Mode::Full) {
return;
}
clearSelection();
fillRecent();

View file

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

View file

@ -872,6 +872,16 @@ void TabbedSelector::showPromoForPremiumEmoji() {
}, 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() {
if (_currentPeer) {
const auto error = (_currentTabType == SelectorTab::Stickers)

View file

@ -109,6 +109,7 @@ public:
void refreshStickers();
void setCurrentPeer(PeerData *peer);
void showPromoForPremiumEmoji();
void provideRecentEmoji(const std::vector<DocumentId> &customRecentList);
void hideFinished();
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 kRecentReactionsLimit = 40;
constexpr auto kTopRequestDelay = 60 * crl::time(1000);
constexpr auto kTopReactionsLimit = 10;
constexpr auto kTopReactionsLimit = 14;
[[nodiscard]] QString ReactionIdToLog(const ReactionId &id) {
if (const auto custom = id.custom()) {
@ -107,7 +107,7 @@ PossibleItemReactionsRef LookupPossibleReactions(
}();
auto added = base::flat_set<ReactionId>();
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) {
if (predicate(reaction)) {
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_sponsored_messages.h"
#include "data/data_message_reactions.h"
#include "data/data_emoji_statuses.h"
#include "data/data_cloud_themes.h"
#include "data/data_streaming.h"
#include "data/data_media_rotation.h"
@ -252,6 +253,7 @@ Session::Session(not_null<Main::Session*> session)
, _stickers(std::make_unique<Stickers>(this))
, _sponsoredMessages(std::make_unique<SponsoredMessages>(this))
, _reactions(std::make_unique<Reactions>(this))
, _emojiStatuses(std::make_unique<EmojiStatuses>(this))
, _notifySettings(std::make_unique<NotifySettings>(this))
, _customEmojiManager(std::make_unique<CustomEmojiManager>(this)) {
_cache->open(_session->local().cacheKey());

View file

@ -52,6 +52,7 @@ class ScheduledMessages;
class SendActionManager;
class SponsoredMessages;
class Reactions;
class EmojiStatuses;
class ChatFilters;
class CloudThemes;
class Streaming;
@ -118,6 +119,9 @@ public:
[[nodiscard]] Reactions &reactions() const {
return *_reactions;
}
[[nodiscard]] EmojiStatuses &emojiStatuses() const {
return *_emojiStatuses;
}
[[nodiscard]] NotifySettings &notifySettings() const {
return *_notifySettings;
}
@ -981,6 +985,7 @@ private:
const std::unique_ptr<Stickers> _stickers;
std::unique_ptr<SponsoredMessages> _sponsoredMessages;
const std::unique_ptr<Reactions> _reactions;
const std::unique_ptr<EmojiStatuses> _emojiStatuses;
const std::unique_ptr<NotifySettings> _notifySettings;
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 kFullDuration = kExpandDuration + kScaleDuration;
constexpr auto kExpandDelay = crl::time(40);
constexpr auto kDefaultColumns = 8;
class StripEmoji final : public Ui::Text::CustomEmoji {
public:
@ -105,19 +106,59 @@ Selector::Selector(
const Data::PossibleItemReactionsRef &reactions,
IconFactory iconFactory,
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)
, _parentController(parentController.get())
, _reactions(reactions)
, _recent(std::move(recent))
, _listMode(mode)
, _jumpedToPremium([=] { close(false); })
, _cachedRound(
QSize(2 * st::reactStripSkip + st::reactStripSize, st::reactStripHeight),
st::reactionCornerShadow,
st::reactStripHeight)
, _strip(
QRect(0, 0, st::reactStripSize, st::reactStripSize),
st::reactStripImage,
crl::guard(this, [=] { update(_inner); }),
std::move(iconFactory))
, _strip(iconFactory
? std::make_unique<Strip>(
QRect(0, 0, st::reactStripSize, st::reactStripSize),
st::reactStripImage,
crl::guard(this, [=] { update(_inner); }),
std::move(iconFactory))
: nullptr)
, _size(st::reactStripSize)
, _skipx(countSkipLeft())
, _skipy((st::reactStripHeight - st::reactStripSize) / 2) {
@ -129,10 +170,14 @@ Selector::Selector(
}, lifetime());
}
int Selector::recentCount() const {
return int(_strip ? _reactions.recent.size() : _recent.size());
}
int Selector::countSkipLeft() const {
const auto addedToMax = _reactions.customAllowed
|| _reactions.morePremiumAvailable;
const auto max = int(_reactions.recent.size()) + (addedToMax ? 1 : 0);
const auto max = recentCount() + (addedToMax ? 1 : 0);
const auto width = max * _size;
return std::max(
(st::reactStripMinWidth - (max * _size)) / 2,
@ -142,13 +187,13 @@ int Selector::countSkipLeft() const {
int Selector::countWidth(int desiredWidth, int maxWidth) {
const auto addedToMax = _reactions.customAllowed
|| _reactions.morePremiumAvailable;
const auto max = int(_reactions.recent.size()) + (addedToMax ? 1 : 0);
const auto max = recentCount() + (addedToMax ? 1 : 0);
const auto possibleColumns = std::min(
(desiredWidth - 2 * _skipx + _size - 1) / _size,
(maxWidth - 2 * _skipx) / _size);
_columns = std::min(possibleColumns, max);
_columns = _strip ? std::min(possibleColumns, max) : kDefaultColumns;
_small = (possibleColumns - _columns > 1);
_recentRows = (_reactions.recent.size()
_recentRows = (recentCount()
+ (_reactions.morePremiumAvailable ? 1 : 0)
+ _columns - 1) / _columns;
const auto added = (_columns < max || _reactions.customAllowed)
@ -156,22 +201,24 @@ int Selector::countWidth(int desiredWidth, int maxWidth) {
: _reactions.morePremiumAvailable
? Strip::AddedButton::Premium
: Strip::AddedButton::None;
const auto &real = _reactions.recent;
auto list = std::vector<not_null<const Data::Reaction*>>();
list.reserve(_columns);
if (const auto cut = max - _columns) {
const auto from = begin(real);
const auto till = end(real) - (cut + (addedToMax ? 0 : 1));
for (auto i = from; i != till; ++i) {
list.push_back(&*i);
}
} else {
for (const auto &reaction : real) {
list.push_back(&reaction);
if (_strip) {
const auto &real = _reactions.recent;
auto list = std::vector<not_null<const Data::Reaction*>>();
list.reserve(_columns);
if (const auto cut = max - _columns) {
const auto from = begin(real);
const auto till = end(real) - (cut + (addedToMax ? 0 : 1));
for (auto i = from; i != till; ++i) {
list.push_back(&*i);
}
} else {
for (const auto &reaction : real) {
list.push_back(&reaction);
}
}
_strip->applyList(list, added);
_strip->clearAppearAnimations(false);
}
_strip.applyList(list, added);
_strip.clearAppearAnimations(false);
return std::max(2 * _skipx + _columns * _size, desiredWidth);
}
@ -211,6 +258,10 @@ void Selector::initGeometry(int innerTop) {
{ 0, _collapsedTopSkip, 0, 0 }
).translated(left, top));
_inner = _outer.marginsRemoved(extents);
if (!_strip) {
expand();
}
}
void Selector::updateShowState(
@ -239,6 +290,8 @@ void Selector::updateShowState(
}
void Selector::paintAppearing(QPainter &p) {
Expects(_strip != nullptr);
p.setOpacity(_appearOpacity);
const auto factor = style::DevicePixelRatio();
@ -256,7 +309,7 @@ void Selector::paintAppearing(QPainter &p) {
const auto size = QSize(fullWidth, _outer.height());
q.translate(_inner.topLeft() - QPoint(0, _collapsedTopSkip));
_strip.paint(
_strip->paint(
q,
{ _skipx, _skipy },
{ _size, 0 },
@ -305,11 +358,13 @@ void Selector::paintBackgroundToBuffer() {
}
void Selector::paintCollapsed(QPainter &p) {
Expects(_strip != nullptr);
if (_paintBuffer.isNull()) {
paintBackgroundToBuffer();
}
p.drawImage(_outer.topLeft(), _paintBuffer);
_strip.paint(
_strip->paint(
p,
_inner.topLeft() + QPoint(_skipx, _skipy),
{ _size, 0 },
@ -441,9 +496,9 @@ void Selector::paintBubble(QPainter &p, int innerWidth) {
void Selector::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
if (_appearing) {
if (_strip && _appearing) {
paintAppearing(p);
} else if (!_expanded) {
} else if (_strip && !_expanded) {
paintCollapsed(p);
} else if (const auto progress = _expanding.value(kFullDuration)
; progress < kFullDuration) {
@ -454,12 +509,15 @@ void Selector::paintEvent(QPaintEvent *e) {
}
void Selector::mouseMoveEvent(QMouseEvent *e) {
if (!_strip) {
return;
}
setSelected(lookupSelectedIndex(e->pos()));
}
int Selector::lookupSelectedIndex(QPoint position) const {
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;
if (p.x() >= 0 && p.y() >= 0 && p.y() < _inner.height() && index < max) {
return index;
@ -468,10 +526,12 @@ int Selector::lookupSelectedIndex(QPoint position) const {
}
void Selector::setSelected(int index) {
Expects(_strip != nullptr);
if (index >= 0 && _expandScheduled) {
return;
}
_strip.setSelected(index);
_strip->setSelected(index);
const auto over = (index >= 0);
if (_over != over) {
_over = over;
@ -485,19 +545,25 @@ void Selector::setSelected(int index) {
}
void Selector::leaveEventHook(QEvent *e) {
if (!_strip) {
return;
}
setSelected(-1);
}
void Selector::mousePressEvent(QMouseEvent *e) {
if (!_strip) {
return;
}
_pressed = lookupSelectedIndex(e->pos());
}
void Selector::mouseReleaseEvent(QMouseEvent *e) {
if (_pressed != lookupSelectedIndex(e->pos())) {
if (!_strip || _pressed != lookupSelectedIndex(e->pos())) {
return;
}
_pressed = -1;
const auto selected = _strip.selected();
const auto selected = _strip->selected();
if (selected == Strip::AddedButton::Premium) {
_premiumPromoChosen.fire({});
} else if (selected == Strip::AddedButton::Expand) {
@ -547,30 +613,35 @@ void Selector::expand() {
}
void Selector::cacheExpandIcon() {
if (!_strip) {
return;
}
_expandIconCache = _cachedRound.PrepareImage({ _size, _size });
_expandIconCache.fill(Qt::transparent);
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) {
using namespace ChatHelpers;
auto recent = std::vector<DocumentId>();
auto recent = _recent;
auto defaultReactionIds = base::flat_map<DocumentId, QString>();
recent.reserve(_reactions.recent.size());
auto index = 0;
const auto inStrip = _strip.count();
for (const auto &reaction : _reactions.recent) {
if (const auto id = reaction.id.custom()) {
recent.push_back(id);
} else {
recent.push_back(reaction.selectAnimation->id);
defaultReactionIds.emplace(recent.back(), reaction.id.emoji());
}
if (index + 1 < inStrip) {
_defaultReactionInStripMap.emplace(recent.back(), index++);
}
};
if (_strip) {
recent.reserve(recentCount());
auto index = 0;
const auto inStrip = _strip->count();
for (const auto &reaction : _reactions.recent) {
if (const auto id = reaction.id.custom()) {
recent.push_back(id);
} else {
recent.push_back(reaction.selectAnimation->id);
defaultReactionIds.emplace(recent.back(), reaction.id.emoji());
}
if (index + 1 < inStrip) {
_defaultReactionInStripMap.emplace(recent.back(), index++);
}
};
}
const auto manager = &controller->session().data().customEmojiManager();
_stripPaintOneShift = [&] {
// 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);
const auto i = _defaultReactionInStripMap.find(id);
if (i != end(_defaultReactionInStripMap)) {
Assert(_strip != nullptr);
return std::make_unique<StripEmoji>(
std::move(result),
&_strip,
_strip.get(),
-_stripPaintOneShift,
i->second);
}
@ -623,9 +695,7 @@ void Selector::createList(not_null<Window::SessionController*> controller) {
_list = _scroll->setOwnedWidget(
object_ptr<EmojiListWidget>(_scroll, EmojiListDescriptor{
.session = &controller->session(),
.mode = (_reactions.customAllowed
? EmojiListMode::FullReactions
: EmojiListMode::RecentReactions),
.mode = _listMode,
.controller = controller,
.paused = [] { return false; },
.customRecentList = std::move(recent),
@ -770,6 +840,65 @@ bool AdjustMenuGeometryForSelector(
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(
not_null<Ui::PopupMenu*> menu,
not_null<Window::SessionController*> controller,

View file

@ -22,6 +22,7 @@ namespace ChatHelpers {
class TabbedPanel;
class EmojiListWidget;
class StickersListFooter;
enum class EmojiListMode;
} // namespace ChatHelpers
namespace Window {
@ -43,6 +44,12 @@ public:
const Data::PossibleItemReactionsRef &reactions,
IconFactory iconFactory,
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);
[[nodiscard]] QMargins extentsForShadow() const;
@ -74,6 +81,15 @@ private:
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 mouseMoveEvent(QMouseEvent *e) override;
void leaveEventHook(QEvent *e) override;
@ -89,6 +105,7 @@ private:
void paintBubble(QPainter &p, int innerWidth);
void paintBackgroundToBuffer();
[[nodiscard]] int recentCount() const;
[[nodiscard]] int countSkipLeft() const;
[[nodiscard]] int lookupSelectedIndex(QPoint position) const;
void setSelected(int index);
@ -100,12 +117,14 @@ private:
const base::weak_ptr<Window::SessionController> _parentController;
const Data::PossibleItemReactions _reactions;
const std::vector<DocumentId> _recent;
const ChatHelpers::EmojiListMode _listMode;
Fn<void()> _jumpedToPremium;
base::flat_map<DocumentId, int> _defaultReactionInStripMap;
Ui::RoundAreaWithShadow _cachedRound;
QPoint _defaultReactionShift;
QPoint _stripPaintOneShift;
Strip _strip;
std::unique_ptr<Strip> _strip;
rpl::event_stream<ChosenReaction> _chosen;
rpl::event_stream<> _premiumPromoChosen;
@ -149,6 +168,15 @@ enum class AttachSelectorResult {
Failed,
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(
not_null<Ui::PopupMenu*> menu,
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_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"
@ -231,6 +232,20 @@ void BadgeView::move(int left, int top, int bottom) {
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 &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) {
create(controller);
@ -242,6 +257,7 @@ void EmojiStatusPanel::show(
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);
@ -280,10 +296,7 @@ void EmojiStatusPanel::create(
std::move(statusChosen),
_panel->selector()->emojiChosen() | rpl::map_to(DocumentId())
) | rpl::start_with_next([=](DocumentId id) {
controller->session().user()->setEmojiStatus(id);
controller->session().api().request(MTPaccount_UpdateEmojiStatus(
id ? MTP_emojiStatus(MTP_long(id)) : MTP_emojiStatusEmpty()
)).send();
controller->session().data().emojiStatuses().set(id);
_panel->hideAnimated();
}, _panel->lifetime());

View file

@ -919,14 +919,13 @@ void SetupMessages(
selected
) | rpl::start_with_next([=, idValue = std::move(idValue)](
const Data::ReactionId &id) {
const auto index = state->icons.flag ? 1 : 0;
const auto iconSize = st::settingsReactionRightIcon;
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;
state->icons.lifetimes[index] = rpl::lifetime();
const auto iconSize = st::settingsReactionRightIcon;
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(
inner,
buttonRight->geometryValue(
@ -936,17 +935,30 @@ void SetupMessages(
r.top() + (r.height() - iconSize) / 2);
}),
iconSize,
r,
*i,
buttonRight->events(
) | rpl::filter([=](not_null<QEvent*> event) {
return event->type() == QEvent::Enter;
}) | rpl::to_empty,
rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
&state->icons.lifetimes[index]);
state->icons.flag = !state->icons.flag;
toggleButtonRight(true);
break;
} 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;
toggleButtonRight(true);
}, buttonRight->lifetime());
react->geometryValue(