mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Request default and recent emoji statuses.
This commit is contained in:
parent
b2d72e2541
commit
923e725e18
15 changed files with 549 additions and 87 deletions
|
@ -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: {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -109,6 +109,7 @@ public:
|
|||
void refreshStickers();
|
||||
void setCurrentPeer(PeerData *peer);
|
||||
void showPromoForPremiumEmoji();
|
||||
void provideRecentEmoji(const std::vector<DocumentId> &customRecentList);
|
||||
|
||||
void hideFinished();
|
||||
void showStarted();
|
||||
|
|
173
Telegram/SourceFiles/data/data_emoji_statuses.cpp
Normal file
173
Telegram/SourceFiles/data/data_emoji_statuses.cpp
Normal 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
|
79
Telegram/SourceFiles/data/data_emoji_statuses.h
Normal file
79
Telegram/SourceFiles/data/data_emoji_statuses.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/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
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 ¬ifySettings() 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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Reference in a new issue