Allow setting channel emoji status.

This commit is contained in:
John Preston 2023-12-21 22:06:10 -04:00
parent 0e8058adb1
commit 941126ad69
14 changed files with 421 additions and 78 deletions

View File

@ -515,6 +515,7 @@ auto PeerPhoto::emojiList(EmojiListType type) -> EmojiListData & {
case EmojiListType::Profile: return _profileEmojiList;
case EmojiListType::Group: return _groupEmojiList;
case EmojiListType::Background: return _backgroundEmojiList;
case EmojiListType::NoChannelStatus: return _noChannelStatusEmojiList;
}
Unexpected("Type in PeerPhoto::emojiList.");
}
@ -551,6 +552,8 @@ void PeerPhoto::requestEmojiList(EmojiListType type) {
? send(MTPaccount_GetDefaultProfilePhotoEmojis())
: (type == EmojiListType::Group)
? send(MTPaccount_GetDefaultGroupPhotoEmojis())
: (type == EmojiListType::NoChannelStatus)
? send(MTPaccount_GetChannelRestrictedStatusEmojis())
: send(MTPaccount_GetDefaultBackgroundEmojis());
}

View File

@ -32,6 +32,7 @@ public:
Profile,
Group,
Background,
NoChannelStatus,
};
struct UserPhoto {
@ -112,6 +113,7 @@ private:
EmojiListData _profileEmojiList;
EmojiListData _groupEmojiList;
EmojiListData _backgroundEmojiList;
EmojiListData _noChannelStatusEmojiList;
};

View File

@ -9,12 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "api/api_peer_colors.h"
#include "api/api_peer_photo.h"
#include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h"
#include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_emoji_statuses.h"
#include "data/data_peer.h"
#include "data/data_session.h"
#include "data/data_web_page.h"
@ -428,11 +430,17 @@ HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog;
}
struct SetValues {
uint8 colorIndex = 0;
DocumentId backgroundEmojiId = 0;
DocumentId statusId = 0;
TimeId statusUntil = 0;
bool statusChanged = false;
};
void Set(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId) {
SetValues values) {
const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId();
@ -444,7 +452,7 @@ void Set(
peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
};
setLocal(colorIndex, backgroundEmojiId);
setLocal(values.colorIndex, values.backgroundEmojiId);
const auto done = [=] {
show->showToast(peer->isSelf()
@ -467,15 +475,23 @@ void Set(
using Flag = MTPaccount_UpdateColor::Flag;
send(MTPaccount_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
MTP_int(values.colorIndex),
MTP_long(values.backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
channel->inputChannel,
MTP_int(colorIndex),
MTP_long(backgroundEmojiId)));
MTP_int(values.colorIndex),
MTP_long(values.backgroundEmojiId)));
if (values.statusChanged
&& (values.statusId || peer->emojiStatusId())) {
peer->owner().emojiStatuses().set(
channel,
values.statusId,
values.statusUntil);
}
} else {
Unexpected("Invalid peer type in Set(colorIndex).");
}
@ -484,13 +500,13 @@ void Set(
void Apply(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer,
uint8 colorIndex,
DocumentId backgroundEmojiId,
SetValues values,
Fn<void()> close,
Fn<void()> cancel) {
const auto session = &peer->session();
if (peer->colorIndex() == colorIndex
&& peer->backgroundEmojiId() == backgroundEmojiId) {
if (peer->colorIndex() == values.colorIndex
&& peer->backgroundEmojiId() == values.backgroundEmojiId
&& !values.statusChanged) {
close();
} else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast(
@ -505,7 +521,7 @@ void Apply(
u"name_color"_q);
cancel();
} else if (peer->isSelf()) {
Set(show, peer, colorIndex, backgroundEmojiId);
Set(show, peer, values);
close();
} else {
session->api().request(MTPpremium_GetBoostsStatus(
@ -518,15 +534,26 @@ void Apply(
const auto peerColors = &peer->session().api().peerColors();
const auto colorRequired = peerColors->requiredLevelFor(
peer->id,
colorIndex);
const auto iconRequired = backgroundEmojiId
values.colorIndex);
const auto iconRequired = values.backgroundEmojiId
? session->account().appConfig().get<int>(
"channel_bg_icon_level_min",
5)
: 0;
const auto required = std::max(colorRequired, iconRequired);
if (data.vlevel().v >= required) {
Set(show, peer, colorIndex, backgroundEmojiId);
const auto statusRequired = (values.statusChanged
&& values.statusId)
? session->account().appConfig().get<int>(
"channel_emoji_status_level_min",
8)
: 0;
const auto required = std::max({
colorRequired,
iconRequired,
statusRequired,
});
const auto current = data.vlevel().v;
if (current >= required) {
Set(show, peer, values);
close();
return;
}
@ -538,10 +565,18 @@ void Apply(
};
auto counters = ParseBoostCounters(result);
counters.mine = 0; // Don't show current level as just-reached.
const auto reason = [&]() -> Ui::AskBoostReason {
if (current < statusRequired) {
return { Ui::AskBoostEmojiStatus{ statusRequired } };
} else if (current < iconRequired) {
return { Ui::AskBoostChannelColor{ iconRequired } };
}
return { Ui::AskBoostChannelColor{ colorRequired } };
}();
show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()),
.boost = counters,
.reason = { Ui::AskBoostChannelColor{ required } },
.reason = reason,
}, openStatistics, nullptr));
cancel();
}).fail([=](const MTP::Error &error) {
@ -685,15 +720,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
using namespace Info::Profile;
struct State {
Info::Profile::EmojiStatusPanel panel;
EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId emojiId = 0;
uint8 index = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.backgroundEmojiChosen(
) | rpl::start_with_next(emojiIdChosen, raw->lifetime());
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
emojiIdChosen(chosen.id);
}, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index;
@ -761,7 +799,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
state->panel.show({
.controller = controller,
.button = right,
.currentBackgroundEmojiId = state->emojiId,
.ensureAddedEmojiId = state->emojiId,
.customTextColor = customTextColor,
.backgroundEmojiMode = true,
});
@ -771,6 +809,108 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result;
}
[[nodiscard]] object_ptr<Ui::SettingsButton> CreateEmojiStatusButton(
not_null<Ui::RpWidget*> parent,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<DocumentId> statusIdValue,
Fn<void(DocumentId,TimeId)> statusIdChosen) {
const auto &basicSt = st::settingsButtonNoIcon;
const auto ratio = style::DevicePixelRatio();
const auto added = st::normalFont->spacew;
const auto emojiSize = Data::FrameSizeFromTag({}) / ratio;
const auto noneWidth = added
+ st::normalFont->width(tr::lng_settings_color_emoji_off(tr::now));
const auto emojiWidth = added + emojiSize;
const auto rightPadding = std::max(noneWidth, emojiWidth)
+ basicSt.padding.right();
const auto st = parent->lifetime().make_state<style::SettingsButton>(
basicSt);
st->padding.setRight(rightPadding);
auto result = object_ptr<Ui::SettingsButton>(
parent,
tr::lng_edit_channel_status(),
*st);
const auto raw = result.data();
const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show();
using namespace Info::Profile;
struct State {
EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId statusId = 0;
};
const auto state = right->lifetime().make_state<State>();
state->panel.someCustomChosen(
) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
statusIdChosen(chosen.id, chosen.until);
}, raw->lifetime());
const auto session = &show->session();
std::move(statusIdValue) | rpl::start_with_next([=](DocumentId id) {
state->statusId = id;
state->emoji = id
? session->data().customEmojiManager().create(
id,
[=] { right->update(); })
: nullptr;
right->resize(
(id ? emojiWidth : noneWidth) + added,
right->height());
right->update();
}, right->lifetime());
rpl::combine(
raw->sizeValue(),
right->widthValue()
) | rpl::start_with_next([=](QSize outer, int width) {
right->resize(width, outer.height());
const auto skip = st::settingsButton.padding.right();
right->moveToRight(skip - added, 0, outer.width());
}, right->lifetime());
right->paintRequest(
) | rpl::start_with_next([=] {
if (state->panel.paintBadgeFrame(right)) {
return;
}
auto p = QPainter(right);
const auto height = right->height();
if (state->emoji) {
state->emoji->paint(p, {
.textColor = anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
0.5),
.position = QPoint(added, (height - emojiSize) / 2),
});
} else {
const auto &font = st::normalFont;
p.setFont(font);
p.setPen(st::windowActiveTextFg);
p.drawText(
QPoint(added, (height - font->height) / 2 + font->ascent),
tr::lng_settings_color_emoji_off(tr::now));
}
}, right->lifetime());
raw->setClickedCallback([=] {
const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo);
if (controller) {
state->panel.show({
.controller = controller,
.button = right,
.ensureAddedEmojiId = state->statusId,
.channelStatusMode = true,
});
}
});
return result;
}
} // namespace
void EditPeerColorBox(
@ -785,12 +925,16 @@ void EditPeerColorBox(
struct State {
rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId;
rpl::variable<DocumentId> statusId;
TimeId statusUntil = 0;
bool statusChanged = false;
bool changing = false;
bool applying = false;
};
const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId();
box->addRow(object_ptr<PreviewWrap>(
box,
@ -833,6 +977,32 @@ void EditPeerColorBox(
? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel());
if (const auto channel = peer->asChannel()) {
// Preload exceptions list.
const auto peerPhoto = &channel->session().api().peerPhoto();
[[maybe_unused]] auto list = peerPhoto->emojiListValue(
Api::PeerPhoto::EmojiListType::NoChannelStatus
);
const auto statuses = &channel->owner().emojiStatuses();
statuses->refreshChannelDefault();
statuses->refreshChannelColored();
Ui::AddSkip(container, st::settingsColorSampleSkip);
container->add(CreateEmojiStatusButton(
container,
show,
state->statusId.value(),
[=](DocumentId id, TimeId until) {
state->statusId = id;
state->statusUntil = until;
state->statusChanged = true;
}));
Ui::AddSkip(container, st::settingsColorSampleSkip);
Ui::AddDividerText(container, tr::lng_edit_channel_status_about());
}
box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) {
return;
@ -840,7 +1010,13 @@ void EditPeerColorBox(
state->applying = true;
const auto index = state->index.current();
const auto emojiId = state->emojiId.current();
Apply(show, peer, index, emojiId, crl::guard(box, [=] {
Apply(show, peer, {
state->index.current(),
state->emojiId.current(),
state->statusId.current(),
state->statusUntil,
state->statusChanged,
}, crl::guard(box, [=] {
box->closeBox();
}), crl::guard(box, [=] {
state->applying = false;

View File

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "chat_helpers/emoji_list_widget.h"
#include "api/api_peer_photo.h"
#include "apiwrap.h"
#include "base/unixtime.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/tabbed_search.h"
@ -477,10 +479,23 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent);
}
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) {
if (_mode != Mode::RecentReactions
&& _mode != Mode::BackgroundEmoji
&& _mode != Mode::ChannelStatus) {
setupSearch();
}
if (_mode == Mode::ChannelStatus) {
session().api().peerPhoto().emojiListValue(
Api::PeerPhoto::EmojiListType::NoChannelStatus
) | rpl::start_with_next([=](const std::vector<DocumentId> &list) {
_restrictedCustomList = { begin(list), end(list) };
if (!_custom.empty()) {
refreshCustom();
}
}, lifetime());
}
_customSingleSize = Data::FrameSizeFromTag(
Data::CustomEmojiManager::SizeTag::Large
) / style::DevicePixelRatio();
@ -1034,7 +1049,9 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &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 if (!id && _mode == Mode::BackgroundEmoji) {
} else if (!id
&& (_mode == Mode::BackgroundEmoji
|| _mode == Mode::ChannelStatus)) {
const auto fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({
@ -1070,7 +1087,7 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
: st::defaultPopupMenu));
if (_mode == Mode::Full) {
fillRecentMenu(menu, section, index);
} else if (_mode == Mode::EmojiStatus) {
} else if (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus) {
fillEmojiStatusMenu(menu, section, index);
}
if (menu->empty()) {
@ -1205,7 +1222,7 @@ void EmojiListWidget::validateEmojiPaintContext(
auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_customTextColor
? _customTextColor()
: (_mode == Mode::EmojiStatus)
: (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus)
? anim::color(
st::stickerPanPremium1,
st::stickerPanPremium2,
@ -1402,6 +1419,10 @@ void EmojiListWidget::drawRecent(
_emojiPaintContext->position = position
+ _innerPosition
+ _customPosition;
if (_mode == Mode::ChannelStatus) {
_emojiPaintContext->internal.forceFirstFrame
= (recent.id == _recent.front().id);
}
custom->paint(p, *_emojiPaintContext);
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) {
@ -1642,6 +1663,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
Settings::ShowPremium(resolved, u"infinite_reactions"_q);
break;
case Mode::EmojiStatus:
case Mode::ChannelStatus:
Settings::ShowPremium(resolved, u"emoji_status"_q);
break;
case Mode::TopicIcon:
@ -2018,8 +2040,9 @@ void EmojiListWidget::refreshCustom() {
auto it = sets.find(setId);
if (it == sets.cend()
|| it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji
&& !it->second->textColor())) {
|| (_mode == Mode::BackgroundEmoji && !it->second->textColor())
|| (_mode == Mode::ChannelStatus
&& !it->second->channelStatus())) {
return;
}
const auto canRemove = !!(it->second->flags
@ -2070,7 +2093,9 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector<CustomOne>();
set.reserve(list.size());
for (const auto document : list) {
if (const auto sticker = document->sticker()) {
if (_restrictedCustomList.contains(document->id)) {
continue;
} else if (const auto sticker = document->sticker()) {
set.push_back({
.custom = resolveCustomEmoji(document, setId),
.document = document,

View File

@ -71,6 +71,7 @@ enum class EmojiListMode {
Full,
TopicIcon,
EmojiStatus,
ChannelStatus,
FullReactions,
RecentReactions,
UserpicBuilder,
@ -384,6 +385,7 @@ private:
bool _grabbingChosen = false;
QVector<EmojiPtr> _emoji[kEmojiSectionCount];
std::vector<CustomSet> _custom;
base::flat_set<DocumentId> _restrictedCustomList;
base::flat_map<DocumentId, CustomEmojiInstance> _customEmoji;
base::flat_map<
DocumentId,

View File

@ -332,6 +332,7 @@ TabbedSelector::TabbedSelector(
: TabbedSelector(parent, {
.show = std::move(show),
.st = ((mode == Mode::EmojiStatus
|| mode == Mode::ChannelStatus
|| mode == Mode::BackgroundEmoji
|| mode == Mode::FullReactions)
? st::statusEmojiPan
@ -521,6 +522,8 @@ TabbedSelector::Tab TabbedSelector::createTab(SelectorTab type, int index) {
.show = _show,
.mode = (_mode == Mode::EmojiStatus
? EmojiMode::EmojiStatus
: _mode == Mode::ChannelStatus
? EmojiMode::ChannelStatus
: _mode == Mode::BackgroundEmoji
? EmojiMode::BackgroundEmoji
: _mode == Mode::FullReactions

View File

@ -81,6 +81,7 @@ enum class TabbedSelectorMode {
EmojiOnly,
MediaEditor,
EmojiStatus,
ChannelStatus,
BackgroundEmoji,
FullReactions,
RecentReactions,

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_emoji_statuses.h"
#include "main/main_session.h"
#include "data/data_channel.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_document.h"
@ -53,6 +54,7 @@ EmojiStatuses::EmojiStatuses(not_null<Session*> owner)
kRefreshDefaultListEach
) | rpl::start_with_next([=] {
refreshDefault();
refreshChannelDefault();
}, _lifetime);
}
@ -74,6 +76,14 @@ void EmojiStatuses::refreshColored() {
requestColored();
}
void EmojiStatuses::refreshChannelDefault() {
requestChannelDefault();
}
void EmojiStatuses::refreshChannelColored() {
requestChannelColored();
}
void EmojiStatuses::refreshRecentDelayed() {
if (_recentRequestId || _recentRequestScheduled) {
return;
@ -91,6 +101,8 @@ const std::vector<DocumentId> &EmojiStatuses::list(Type type) const {
case Type::Recent: return _recent;
case Type::Default: return _default;
case Type::Colored: return _colored;
case Type::ChannelDefault: return _channelDefault;
case Type::ChannelColored: return _channelColored;
}
Unexpected("Type in EmojiStatuses::list.");
}
@ -103,6 +115,10 @@ rpl::producer<> EmojiStatuses::defaultUpdates() const {
return _defaultUpdated.events();
}
rpl::producer<> EmojiStatuses::channelDefaultUpdates() const {
return _channelDefaultUpdated.events();
}
void EmojiStatuses::registerAutomaticClear(
not_null<PeerData*> peer,
TimeId until) {
@ -266,7 +282,7 @@ void EmojiStatuses::requestDefault() {
}
auto &api = _owner->session().api();
_defaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_recentHash)
MTP_long(_defaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_defaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
@ -299,6 +315,45 @@ void EmojiStatuses::requestColored() {
}).send();
}
void EmojiStatuses::requestChannelDefault() {
if (_channelDefaultRequestId) {
return;
}
auto &api = _owner->session().api();
_channelDefaultRequestId = api.request(MTPaccount_GetDefaultEmojiStatuses(
MTP_long(_channelDefaultHash)
)).done([=](const MTPaccount_EmojiStatuses &result) {
_channelDefaultRequestId = 0;
result.match([&](const MTPDaccount_emojiStatuses &data) {
updateChannelDefault(data);
}, [&](const MTPDaccount_emojiStatusesNotModified &) {
});
}).fail([=] {
_channelDefaultRequestId = 0;
_channelDefaultHash = 0;
}).send();
}
void EmojiStatuses::requestChannelColored() {
if (_channelColoredRequestId) {
return;
}
auto &api = _owner->session().api();
_channelColoredRequestId = api.request(MTPmessages_GetStickerSet(
MTP_inputStickerSetEmojiChannelDefaultStatuses(),
MTP_int(0) // hash
)).done([=](const MTPmessages_StickerSet &result) {
_channelColoredRequestId = 0;
result.match([&](const MTPDmessages_stickerSet &data) {
updateChannelColored(data);
}, [](const MTPDmessages_stickerSetNotModified &) {
LOG(("API Error: Unexpected messages.stickerSetNotModified."));
});
}).fail([=] {
_channelColoredRequestId = 0;
}).send();
}
void EmojiStatuses::updateRecent(const MTPDaccount_emojiStatuses &data) {
_recentHash = data.vhash().v;
_recent = ListFromMTP(data);
@ -321,27 +376,57 @@ void EmojiStatuses::updateColored(const MTPDmessages_stickerSet &data) {
_coloredUpdated.fire({});
}
void EmojiStatuses::set(DocumentId id, TimeId until) {
auto &api = _owner->session().api();
if (_sentRequestId) {
api.request(base::take(_sentRequestId)).cancel();
void EmojiStatuses::updateChannelDefault(
const MTPDaccount_emojiStatuses &data) {
_channelDefaultHash = data.vhash().v;
_channelDefault = ListFromMTP(data);
_channelDefaultUpdated.fire({});
}
void EmojiStatuses::updateChannelColored(
const MTPDmessages_stickerSet &data) {
const auto &list = data.vdocuments().v;
_channelColored.clear();
_channelColored.reserve(list.size());
for (const auto &sticker : data.vdocuments().v) {
_channelColored.push_back(_owner->processDocument(sticker)->id);
}
_owner->session().user()->setEmojiStatus(id, until);
_sentRequestId = api.request(MTPaccount_UpdateEmojiStatus(
!id
_channelColoredUpdated.fire({});
}
void EmojiStatuses::set(DocumentId id, TimeId until) {
set(_owner->session().user(), id, until);
}
void EmojiStatuses::set(
not_null<PeerData*> peer,
DocumentId id,
TimeId until) {
auto &api = _owner->session().api();
auto &requestId = _sentRequests[peer];
if (requestId) {
api.request(base::take(requestId)).cancel();
}
peer->setEmojiStatus(id, until);
const auto send = [&](auto &&request) {
requestId = api.request(
std::move(request)
).done([=] {
_sentRequests.remove(peer);
}).fail([=] {
_sentRequests.remove(peer);
}).send();
};
const auto status = !id
? MTP_emojiStatusEmpty()
: !until
? MTP_emojiStatus(MTP_long(id))
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until))
)).done([=] {
_sentRequestId = 0;
}).fail([=] {
_sentRequestId = 0;
}).send();
}
bool EmojiStatuses::setting() const {
return _sentRequestId != 0;;
: MTP_emojiStatusUntil(MTP_long(id), MTP_int(until));
if (peer->isSelf()) {
send(MTPaccount_UpdateEmojiStatus(status));
} else if (const auto channel = peer->asChannel()) {
send(MTPchannels_UpdateEmojiStatus(channel->inputChannel, status));
}
}
EmojiStatusData ParseEmojiStatus(const MTPEmojiStatus &status) {

View File

@ -36,20 +36,24 @@ public:
void refreshRecentDelayed();
void refreshDefault();
void refreshColored();
void refreshChannelDefault();
void refreshChannelColored();
enum class Type {
Recent,
Default,
Colored,
ChannelDefault,
ChannelColored,
};
[[nodiscard]] const std::vector<DocumentId> &list(Type type) const;
[[nodiscard]] rpl::producer<> recentUpdates() const;
[[nodiscard]] rpl::producer<> defaultUpdates() const;
[[nodiscard]] rpl::producer<> coloredUpdates() const;
[[nodiscard]] rpl::producer<> channelDefaultUpdates() const;
void set(DocumentId id, TimeId until = 0);
[[nodiscard]] bool setting() const;
void set(not_null<PeerData*> peer, DocumentId id, TimeId until = 0);
void registerAutomaticClear(not_null<PeerData*> peer, TimeId until);
@ -71,10 +75,14 @@ private:
void requestRecent();
void requestDefault();
void requestColored();
void requestChannelDefault();
void requestChannelColored();
void updateRecent(const MTPDaccount_emojiStatuses &data);
void updateDefault(const MTPDaccount_emojiStatuses &data);
void updateColored(const MTPDmessages_stickerSet &data);
void updateChannelDefault(const MTPDaccount_emojiStatuses &data);
void updateChannelColored(const MTPDmessages_stickerSet &data);
void processClearingIn(TimeId wait);
void processClearing();
@ -87,9 +95,13 @@ private:
std::vector<DocumentId> _recent;
std::vector<DocumentId> _default;
std::vector<DocumentId> _colored;
std::vector<DocumentId> _channelDefault;
std::vector<DocumentId> _channelColored;
rpl::event_stream<> _recentUpdated;
rpl::event_stream<> _defaultUpdated;
rpl::event_stream<> _coloredUpdated;
rpl::event_stream<> _channelDefaultUpdated;
rpl::event_stream<> _channelColoredUpdated;
mtpRequestId _recentRequestId = 0;
bool _recentRequestScheduled = false;
@ -100,7 +112,12 @@ private:
mtpRequestId _coloredRequestId = 0;
mtpRequestId _sentRequestId = 0;
mtpRequestId _channelDefaultRequestId = 0;
uint64 _channelDefaultHash = 0;
mtpRequestId _channelColoredRequestId = 0;
base::flat_map<not_null<PeerData*>, mtpRequestId> _sentRequests;
base::flat_map<not_null<PeerData*>, TimeId> _clearing;
base::Timer _clearingTimer;

View File

@ -54,7 +54,8 @@ StickersSetFlags ParseStickersSetFlags(const MTPDstickerSet &data) {
| (data.is_emojis() ? Flag::Emoji : Flag())
| (data.vinstalled_date() ? Flag::Installed : Flag())
| (data.is_videos() ? Flag::Webm : Flag())
| (data.is_text_color() ? Flag::TextColor : Flag());
| (data.is_text_color() ? Flag::TextColor : Flag())
| (data.is_channel_emoji_status() ? Flag::ChannelStatus : Flag());
}
StickersSet::StickersSet(
@ -113,6 +114,10 @@ bool StickersSet::textColor() const {
return flags & StickersSetFlag::TextColor;
}
bool StickersSet::channelStatus() const {
return flags & StickersSetFlag::ChannelStatus;
}
void StickersSet::setThumbnail(const ImageWithLocation &data) {
Data::UpdateCloudFile(
_thumbnail,

View File

@ -58,6 +58,7 @@ enum class StickersSetFlag {
Webm = (1 << 8),
Emoji = (1 << 9),
TextColor = (1 << 10),
ChannelStatus = (1 << 11),
};
inline constexpr bool is_flag_type(StickersSetFlag) { return true; };
using StickersSetFlags = base::flags<StickersSetFlag>;
@ -86,6 +87,7 @@ public:
[[nodiscard]] StickerSetIdentifier identifier() const;
[[nodiscard]] StickersType type() const;
[[nodiscard]] bool textColor() const;
[[nodiscard]] bool channelStatus() const;
void setThumbnail(const ImageWithLocation &data);

View File

@ -72,6 +72,7 @@ void EmojiStatusPanel::show(
.controller = controller,
.button = button,
.animationSizeTag = animationSizeTag,
.ensureAddedEmojiId = controller->session().user()->emojiStatusId(),
});
}
@ -99,28 +100,41 @@ void EmojiStatusPanel::show(Descriptor &&descriptor) {
}
_panelButton = button;
_animationSizeTag = descriptor.animationSizeTag;
auto list = std::vector<DocumentId>();
const auto feed = [=, now = descriptor.ensureAddedEmojiId](
std::vector<DocumentId> list) {
list.insert(begin(list), 0);
if (now && !ranges::contains(list, now)) {
list.push_back(now);
}
_panel->selector()->provideRecentEmoji(list);
};
if (descriptor.backgroundEmojiMode) {
controller->session().api().peerPhoto().emojiListValue(
Api::PeerPhoto::EmojiListType::Background
) | rpl::start_with_next([=](std::vector<DocumentId> &&list) {
list.insert(begin(list), 0);
if (const auto now = descriptor.currentBackgroundEmojiId) {
if (!ranges::contains(list, now)) {
list.push_back(now);
}
}
_panel->selector()->provideRecentEmoji(list);
feed(std::move(list));
}, _panel->lifetime());
} else if (descriptor.channelStatusMode) {
const auto &statuses = controller->session().data().emojiStatuses();
const auto &other = statuses.list(Data::EmojiStatuses::Type::ChannelDefault);
auto list = statuses.list(Data::EmojiStatuses::Type::ChannelColored);
if (list.size() > kLimitFirstRow - 1) {
list.erase(begin(list) + kLimitFirstRow - 1, end(list));
}
list.reserve(list.size() + other.size() + 1);
for (const auto &id : other) {
if (!ranges::contains(list, id)) {
list.push_back(id);
}
}
feed(std::move(list));
} else {
const auto self = controller->session().user();
const auto &statuses = controller->session().data().emojiStatuses();
const auto &recent = statuses.list(Data::EmojiStatuses::Type::Recent);
const auto &other = statuses.list(Data::EmojiStatuses::Type::Default);
auto list = statuses.list(Data::EmojiStatuses::Type::Colored);
list.insert(begin(list), 0);
if (list.size() > kLimitFirstRow) {
list.erase(begin(list) + kLimitFirstRow, end(list));
if (list.size() > kLimitFirstRow - 1) {
list.erase(begin(list) + kLimitFirstRow - 1, end(list));
}
list.reserve(list.size() + recent.size() + other.size() + 1);
for (const auto &id : ranges::views::concat(recent, other)) {
@ -128,15 +142,12 @@ void EmojiStatusPanel::show(Descriptor &&descriptor) {
list.push_back(id);
}
}
if (!ranges::contains(list, self->emojiStatusId())) {
list.push_back(self->emojiStatusId());
}
_panel->selector()->provideRecentEmoji(list);
feed(std::move(list));
}
const auto parent = _panel->parentWidget();
const auto global = button->mapToGlobal(QPoint());
const auto local = parent->mapFromGlobal(global);
if (descriptor.backgroundEmojiMode) {
if (descriptor.backgroundEmojiMode || descriptor.channelStatusMode) {
_panel->moveBottomRight(
local.y() + (st::normalFont->height / 2),
local.x() + button->width() * 3);
@ -175,18 +186,22 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) {
nullptr,
Descriptor{
.show = controller->uiShow(),
.st = (descriptor.backgroundEmojiMode
.st = ((descriptor.backgroundEmojiMode
|| descriptor.channelStatusMode)
? st::backgroundEmojiPan
: st::statusEmojiPan),
.level = Window::GifPauseReason::Layer,
.mode = (descriptor.backgroundEmojiMode
? Mode::BackgroundEmoji
: descriptor.channelStatusMode
? Mode::ChannelStatus
: Mode::EmojiStatus),
.customTextColor = descriptor.customTextColor,
}));
_customTextColor = descriptor.customTextColor;
_backgroundEmojiMode = descriptor.backgroundEmojiMode;
_panel->setDropDown(!_backgroundEmojiMode);
_channelStatusMode = descriptor.channelStatusMode;
_panel->setDropDown(!_backgroundEmojiMode && !_channelStatusMode);
_panel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
@ -218,14 +233,14 @@ void EmojiStatusPanel::create(const Descriptor &descriptor) {
return Chosen{ .animation = data.messageSendingFrom };
});
if (descriptor.backgroundEmojiMode) {
if (descriptor.backgroundEmojiMode || descriptor.channelStatusMode) {
rpl::merge(
std::move(statusChosen),
std::move(emojiChosen)
) | rpl::start_with_next([=](const Chosen &chosen) {
const auto owner = &controller->session().data();
startAnimation(owner, body, chosen.id, chosen.animation);
_backgroundEmojiChosen.fire_copy(chosen.id);
_someCustomChosen.fire({ chosen.id, chosen.until });
_panel->hideAnimated();
}, _panel->lifetime());
} else {

View File

@ -51,15 +51,20 @@ public:
not_null<Window::SessionController*> controller;
not_null<QWidget*> button;
Data::CustomEmojiSizeTag animationSizeTag = {};
DocumentId currentBackgroundEmojiId = 0;
DocumentId ensureAddedEmojiId = 0;
Fn<QColor()> customTextColor;
bool backgroundEmojiMode = false;
bool channelStatusMode = false;
};
void show(Descriptor &&descriptor);
void repaint();
[[nodiscard]] rpl::producer<DocumentId> backgroundEmojiChosen() const {
return _backgroundEmojiChosen.events();
struct CustomChosen {
DocumentId id = 0;
TimeId until = 0;
};
[[nodiscard]] rpl::producer<CustomChosen> someCustomChosen() const {
return _someCustomChosen.events();
}
bool paintBadgeFrame(not_null<Ui::RpWidget*> widget);
@ -81,9 +86,10 @@ private:
Fn<bool(DocumentId)> _chooseFilter;
QPointer<QWidget> _panelButton;
std::unique_ptr<Ui::EmojiFlyAnimation> _animation;
rpl::event_stream<DocumentId> _backgroundEmojiChosen;
rpl::event_stream<CustomChosen> _someCustomChosen;
Data::CustomEmojiSizeTag _animationSizeTag = {};
bool _backgroundEmojiMode = false;
bool _channelStatusMode = false;
};

View File

@ -526,7 +526,8 @@ bool ShowReactPremiumError(
not_null<HistoryItem*> item,
const Data::ReactionId &id) {
if (controller->session().premium()
|| ranges::contains(item->chosenReactions(), id)) {
|| ranges::contains(item->chosenReactions(), id)
|| item->history()->peer->isBroadcast()) {
return false;
}
const auto &list = controller->session().data().reactions().list(