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

View File

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

View File

@ -9,12 +9,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "api/api_peer_colors.h" #include "api/api_peer_colors.h"
#include "api/api_peer_photo.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/peers/replace_boost_box.h" #include "boxes/peers/replace_boost_box.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_emoji_statuses.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
@ -428,11 +430,17 @@ HistoryView::Context PreviewDelegate::elementContext() {
return HistoryView::Context::AdminLog; return HistoryView::Context::AdminLog;
} }
struct SetValues {
uint8 colorIndex = 0;
DocumentId backgroundEmojiId = 0;
DocumentId statusId = 0;
TimeId statusUntil = 0;
bool statusChanged = false;
};
void Set( void Set(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
uint8 colorIndex, SetValues values) {
DocumentId backgroundEmojiId) {
const auto wasIndex = peer->colorIndex(); const auto wasIndex = peer->colorIndex();
const auto wasEmojiId = peer->backgroundEmojiId(); const auto wasEmojiId = peer->backgroundEmojiId();
@ -444,7 +452,7 @@ void Set(
peer, peer,
UpdateFlag::Color | UpdateFlag::BackgroundEmoji); UpdateFlag::Color | UpdateFlag::BackgroundEmoji);
}; };
setLocal(colorIndex, backgroundEmojiId); setLocal(values.colorIndex, values.backgroundEmojiId);
const auto done = [=] { const auto done = [=] {
show->showToast(peer->isSelf() show->showToast(peer->isSelf()
@ -467,15 +475,23 @@ void Set(
using Flag = MTPaccount_UpdateColor::Flag; using Flag = MTPaccount_UpdateColor::Flag;
send(MTPaccount_UpdateColor( send(MTPaccount_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id), MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
MTP_int(colorIndex), MTP_int(values.colorIndex),
MTP_long(backgroundEmojiId))); MTP_long(values.backgroundEmojiId)));
} else if (const auto channel = peer->asChannel()) { } else if (const auto channel = peer->asChannel()) {
using Flag = MTPchannels_UpdateColor::Flag; using Flag = MTPchannels_UpdateColor::Flag;
send(MTPchannels_UpdateColor( send(MTPchannels_UpdateColor(
MTP_flags(Flag::f_color | Flag::f_background_emoji_id), MTP_flags(Flag::f_color | Flag::f_background_emoji_id),
channel->inputChannel, channel->inputChannel,
MTP_int(colorIndex), MTP_int(values.colorIndex),
MTP_long(backgroundEmojiId))); MTP_long(values.backgroundEmojiId)));
if (values.statusChanged
&& (values.statusId || peer->emojiStatusId())) {
peer->owner().emojiStatuses().set(
channel,
values.statusId,
values.statusUntil);
}
} else { } else {
Unexpected("Invalid peer type in Set(colorIndex)."); Unexpected("Invalid peer type in Set(colorIndex).");
} }
@ -484,13 +500,13 @@ void Set(
void Apply( void Apply(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
uint8 colorIndex, SetValues values,
DocumentId backgroundEmojiId,
Fn<void()> close, Fn<void()> close,
Fn<void()> cancel) { Fn<void()> cancel) {
const auto session = &peer->session(); const auto session = &peer->session();
if (peer->colorIndex() == colorIndex if (peer->colorIndex() == values.colorIndex
&& peer->backgroundEmojiId() == backgroundEmojiId) { && peer->backgroundEmojiId() == values.backgroundEmojiId
&& !values.statusChanged) {
close(); close();
} else if (peer->isSelf() && !session->premium()) { } else if (peer->isSelf() && !session->premium()) {
Settings::ShowPremiumPromoToast( Settings::ShowPremiumPromoToast(
@ -505,7 +521,7 @@ void Apply(
u"name_color"_q); u"name_color"_q);
cancel(); cancel();
} else if (peer->isSelf()) { } else if (peer->isSelf()) {
Set(show, peer, colorIndex, backgroundEmojiId); Set(show, peer, values);
close(); close();
} else { } else {
session->api().request(MTPpremium_GetBoostsStatus( session->api().request(MTPpremium_GetBoostsStatus(
@ -518,15 +534,26 @@ void Apply(
const auto peerColors = &peer->session().api().peerColors(); const auto peerColors = &peer->session().api().peerColors();
const auto colorRequired = peerColors->requiredLevelFor( const auto colorRequired = peerColors->requiredLevelFor(
peer->id, peer->id,
colorIndex); values.colorIndex);
const auto iconRequired = backgroundEmojiId const auto iconRequired = values.backgroundEmojiId
? session->account().appConfig().get<int>( ? session->account().appConfig().get<int>(
"channel_bg_icon_level_min", "channel_bg_icon_level_min",
5) 5)
: 0; : 0;
const auto required = std::max(colorRequired, iconRequired); const auto statusRequired = (values.statusChanged
if (data.vlevel().v >= required) { && values.statusId)
Set(show, peer, colorIndex, backgroundEmojiId); ? 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(); close();
return; return;
} }
@ -538,10 +565,18 @@ void Apply(
}; };
auto counters = ParseBoostCounters(result); auto counters = ParseBoostCounters(result);
counters.mine = 0; // Don't show current level as just-reached. 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{ show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()), .link = qs(data.vboost_url()),
.boost = counters, .boost = counters,
.reason = { Ui::AskBoostChannelColor{ required } }, .reason = reason,
}, openStatistics, nullptr)); }, openStatistics, nullptr));
cancel(); cancel();
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
@ -685,15 +720,18 @@ int ColorSelector::resizeGetHeight(int newWidth) {
const auto right = Ui::CreateChild<Ui::RpWidget>(raw); const auto right = Ui::CreateChild<Ui::RpWidget>(raw);
right->show(); right->show();
using namespace Info::Profile;
struct State { struct State {
Info::Profile::EmojiStatusPanel panel; EmojiStatusPanel panel;
std::unique_ptr<Ui::Text::CustomEmoji> emoji; std::unique_ptr<Ui::Text::CustomEmoji> emoji;
DocumentId emojiId = 0; DocumentId emojiId = 0;
uint8 index = 0; uint8 index = 0;
}; };
const auto state = right->lifetime().make_state<State>(); const auto state = right->lifetime().make_state<State>();
state->panel.backgroundEmojiChosen( state->panel.someCustomChosen(
) | rpl::start_with_next(emojiIdChosen, raw->lifetime()); ) | rpl::start_with_next([=](EmojiStatusPanel::CustomChosen chosen) {
emojiIdChosen(chosen.id);
}, raw->lifetime());
std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) { std::move(colorIndexValue) | rpl::start_with_next([=](uint8 index) {
state->index = index; state->index = index;
@ -761,7 +799,7 @@ int ColorSelector::resizeGetHeight(int newWidth) {
state->panel.show({ state->panel.show({
.controller = controller, .controller = controller,
.button = right, .button = right,
.currentBackgroundEmojiId = state->emojiId, .ensureAddedEmojiId = state->emojiId,
.customTextColor = customTextColor, .customTextColor = customTextColor,
.backgroundEmojiMode = true, .backgroundEmojiMode = true,
}); });
@ -771,6 +809,108 @@ int ColorSelector::resizeGetHeight(int newWidth) {
return result; 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 } // namespace
void EditPeerColorBox( void EditPeerColorBox(
@ -785,12 +925,16 @@ void EditPeerColorBox(
struct State { struct State {
rpl::variable<uint8> index; rpl::variable<uint8> index;
rpl::variable<DocumentId> emojiId; rpl::variable<DocumentId> emojiId;
rpl::variable<DocumentId> statusId;
TimeId statusUntil = 0;
bool statusChanged = false;
bool changing = false; bool changing = false;
bool applying = false; bool applying = false;
}; };
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->index = peer->colorIndex(); state->index = peer->colorIndex();
state->emojiId = peer->backgroundEmojiId(); state->emojiId = peer->backgroundEmojiId();
state->statusId = peer->emojiStatusId();
box->addRow(object_ptr<PreviewWrap>( box->addRow(object_ptr<PreviewWrap>(
box, box,
@ -833,6 +977,32 @@ void EditPeerColorBox(
? tr::lng_settings_color_emoji_about() ? tr::lng_settings_color_emoji_about()
: tr::lng_settings_color_emoji_about_channel()); : 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(), [=] { box->addButton(tr::lng_settings_apply(), [=] {
if (state->applying) { if (state->applying) {
return; return;
@ -840,7 +1010,13 @@ void EditPeerColorBox(
state->applying = true; state->applying = true;
const auto index = state->index.current(); const auto index = state->index.current();
const auto emojiId = state->emojiId.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(); box->closeBox();
}), crl::guard(box, [=] { }), crl::guard(box, [=] {
state->applying = false; 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 "chat_helpers/emoji_list_widget.h"
#include "api/api_peer_photo.h"
#include "apiwrap.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "ui/controls/tabbed_search.h" #include "ui/controls/tabbed_search.h"
@ -477,10 +479,23 @@ EmojiListWidget::EmojiListWidget(
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
} }
if (_mode != Mode::RecentReactions && _mode != Mode::BackgroundEmoji) { if (_mode != Mode::RecentReactions
&& _mode != Mode::BackgroundEmoji
&& _mode != Mode::ChannelStatus) {
setupSearch(); 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( _customSingleSize = Data::FrameSizeFromTag(
Data::CustomEmojiManager::SizeTag::Large Data::CustomEmojiManager::SizeTag::Large
) / style::DevicePixelRatio(); ) / style::DevicePixelRatio();
@ -1034,7 +1049,9 @@ void EmojiListWidget::fillRecentFrom(const std::vector<DocumentId> &list) {
if (!id && _mode == Mode::EmojiStatus) { if (!id && _mode == Mode::EmojiStatus) {
const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f"); const auto star = QString::fromUtf8("\xe2\xad\x90\xef\xb8\x8f");
_recent.push_back({ .id = { Ui::Emoji::Find(star) } }); _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 fakeId = DocumentId(5246772116543512028ULL);
const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f"); const auto no = QString::fromUtf8("\xe2\x9b\x94\xef\xb8\x8f");
_recent.push_back({ _recent.push_back({
@ -1070,7 +1087,7 @@ base::unique_qptr<Ui::PopupMenu> EmojiListWidget::fillContextMenu(
: st::defaultPopupMenu)); : st::defaultPopupMenu));
if (_mode == Mode::Full) { if (_mode == Mode::Full) {
fillRecentMenu(menu, section, index); fillRecentMenu(menu, section, index);
} else if (_mode == Mode::EmojiStatus) { } else if (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus) {
fillEmojiStatusMenu(menu, section, index); fillEmojiStatusMenu(menu, section, index);
} }
if (menu->empty()) { if (menu->empty()) {
@ -1205,7 +1222,7 @@ void EmojiListWidget::validateEmojiPaintContext(
auto value = Ui::Text::CustomEmojiPaintContext{ auto value = Ui::Text::CustomEmojiPaintContext{
.textColor = (_customTextColor .textColor = (_customTextColor
? _customTextColor() ? _customTextColor()
: (_mode == Mode::EmojiStatus) : (_mode == Mode::EmojiStatus || _mode == Mode::ChannelStatus)
? anim::color( ? anim::color(
st::stickerPanPremium1, st::stickerPanPremium1,
st::stickerPanPremium2, st::stickerPanPremium2,
@ -1402,6 +1419,10 @@ void EmojiListWidget::drawRecent(
_emojiPaintContext->position = position _emojiPaintContext->position = position
+ _innerPosition + _innerPosition
+ _customPosition; + _customPosition;
if (_mode == Mode::ChannelStatus) {
_emojiPaintContext->internal.forceFirstFrame
= (recent.id == _recent.front().id);
}
custom->paint(p, *_emojiPaintContext); custom->paint(p, *_emojiPaintContext);
} else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) { } else if (const auto emoji = std::get_if<EmojiPtr>(&recent.id.data)) {
if (_mode == Mode::EmojiStatus) { if (_mode == Mode::EmojiStatus) {
@ -1642,6 +1663,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
Settings::ShowPremium(resolved, u"infinite_reactions"_q); Settings::ShowPremium(resolved, u"infinite_reactions"_q);
break; break;
case Mode::EmojiStatus: case Mode::EmojiStatus:
case Mode::ChannelStatus:
Settings::ShowPremium(resolved, u"emoji_status"_q); Settings::ShowPremium(resolved, u"emoji_status"_q);
break; break;
case Mode::TopicIcon: case Mode::TopicIcon:
@ -2018,8 +2040,9 @@ void EmojiListWidget::refreshCustom() {
auto it = sets.find(setId); auto it = sets.find(setId);
if (it == sets.cend() if (it == sets.cend()
|| it->second->stickers.isEmpty() || it->second->stickers.isEmpty()
|| (_mode == Mode::BackgroundEmoji || (_mode == Mode::BackgroundEmoji && !it->second->textColor())
&& !it->second->textColor())) { || (_mode == Mode::ChannelStatus
&& !it->second->channelStatus())) {
return; return;
} }
const auto canRemove = !!(it->second->flags const auto canRemove = !!(it->second->flags
@ -2070,7 +2093,9 @@ void EmojiListWidget::refreshCustom() {
auto set = std::vector<CustomOne>(); auto set = std::vector<CustomOne>();
set.reserve(list.size()); set.reserve(list.size());
for (const auto document : list) { 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({ set.push_back({
.custom = resolveCustomEmoji(document, setId), .custom = resolveCustomEmoji(document, setId),
.document = document, .document = document,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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