diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp index 25a7065873..c70ef4491f 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.cpp +++ b/Telegram/SourceFiles/api/api_peer_photo.cpp @@ -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()); } diff --git a/Telegram/SourceFiles/api/api_peer_photo.h b/Telegram/SourceFiles/api/api_peer_photo.h index 1d4bd1071e..71d340a031 100644 --- a/Telegram/SourceFiles/api/api_peer_photo.h +++ b/Telegram/SourceFiles/api/api_peer_photo.h @@ -32,6 +32,7 @@ public: Profile, Group, Background, + NoChannelStatus, }; struct UserPhoto { @@ -112,6 +113,7 @@ private: EmojiListData _profileEmojiList; EmojiListData _groupEmojiList; EmojiListData _backgroundEmojiList; + EmojiListData _noChannelStatusEmojiList; }; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index f686c06cfd..4ebee1962b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -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 show, not_null 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 show, not_null peer, - uint8 colorIndex, - DocumentId backgroundEmojiId, + SetValues values, Fn close, Fn 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( "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( + "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(raw); right->show(); + using namespace Info::Profile; struct State { - Info::Profile::EmojiStatusPanel panel; + EmojiStatusPanel panel; std::unique_ptr emoji; DocumentId emojiId = 0; uint8 index = 0; }; const auto state = right->lifetime().make_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 CreateEmojiStatusButton( + not_null parent, + std::shared_ptr show, + rpl::producer statusIdValue, + Fn 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( + basicSt); + st->padding.setRight(rightPadding); + auto result = object_ptr( + parent, + tr::lng_edit_channel_status(), + *st); + const auto raw = result.data(); + + const auto right = Ui::CreateChild(raw); + right->show(); + + using namespace Info::Profile; + struct State { + EmojiStatusPanel panel; + std::unique_ptr emoji; + DocumentId statusId = 0; + }; + const auto state = right->lifetime().make_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 index; rpl::variable emojiId; + rpl::variable statusId; + TimeId statusUntil = 0; + bool statusChanged = false; bool changing = false; bool applying = false; }; const auto state = box->lifetime().make_state(); state->index = peer->colorIndex(); state->emojiId = peer->backgroundEmojiId(); + state->statusId = peer->emojiStatusId(); box->addRow(object_ptr( 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; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 9ae411d24b..2d27f1db47 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -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 &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 &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 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(&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(); 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, diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 5735d7a55d..c2a8ef3ed0 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -71,6 +71,7 @@ enum class EmojiListMode { Full, TopicIcon, EmojiStatus, + ChannelStatus, FullReactions, RecentReactions, UserpicBuilder, @@ -384,6 +385,7 @@ private: bool _grabbingChosen = false; QVector _emoji[kEmojiSectionCount]; std::vector _custom; + base::flat_set _restrictedCustomList; base::flat_map _customEmoji; base::flat_map< DocumentId, diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 9d6f669cdd..6fc2c555ad 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -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 diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index c70118df2e..3b2f45d5dc 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -81,6 +81,7 @@ enum class TabbedSelectorMode { EmojiOnly, MediaEditor, EmojiStatus, + ChannelStatus, BackgroundEmoji, FullReactions, RecentReactions, diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.cpp b/Telegram/SourceFiles/data/data_emoji_statuses.cpp index 961fcde83a..dd1ad583bc 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.cpp +++ b/Telegram/SourceFiles/data/data_emoji_statuses.cpp @@ -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 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 &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 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 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) { diff --git a/Telegram/SourceFiles/data/data_emoji_statuses.h b/Telegram/SourceFiles/data/data_emoji_statuses.h index cf0e54d767..5501eb8bc0 100644 --- a/Telegram/SourceFiles/data/data_emoji_statuses.h +++ b/Telegram/SourceFiles/data/data_emoji_statuses.h @@ -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 &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 peer, DocumentId id, TimeId until = 0); void registerAutomaticClear(not_null 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 _recent; std::vector _default; std::vector _colored; + std::vector _channelDefault; + std::vector _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, mtpRequestId> _sentRequests; base::flat_map, TimeId> _clearing; base::Timer _clearingTimer; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp index 347a3f13ca..3639e436a3 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.cpp @@ -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, diff --git a/Telegram/SourceFiles/data/stickers/data_stickers_set.h b/Telegram/SourceFiles/data/stickers/data_stickers_set.h index bf9a6e4177..e9124471c2 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers_set.h +++ b/Telegram/SourceFiles/data/stickers/data_stickers_set.h @@ -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; @@ -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); diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp index db8714c705..8881dbc99c 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.cpp @@ -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(); + const auto feed = [=, now = descriptor.ensureAddedEmojiId]( + std::vector 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 &&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 { diff --git a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h index 246b171fe0..a373c904c9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h +++ b/Telegram/SourceFiles/info/profile/info_profile_emoji_status_panel.h @@ -51,15 +51,20 @@ public: not_null controller; not_null button; Data::CustomEmojiSizeTag animationSizeTag = {}; - DocumentId currentBackgroundEmojiId = 0; + DocumentId ensureAddedEmojiId = 0; Fn customTextColor; bool backgroundEmojiMode = false; + bool channelStatusMode = false; }; void show(Descriptor &&descriptor); void repaint(); - [[nodiscard]] rpl::producer backgroundEmojiChosen() const { - return _backgroundEmojiChosen.events(); + struct CustomChosen { + DocumentId id = 0; + TimeId until = 0; + }; + [[nodiscard]] rpl::producer someCustomChosen() const { + return _someCustomChosen.events(); } bool paintBadgeFrame(not_null widget); @@ -81,9 +86,10 @@ private: Fn _chooseFilter; QPointer _panelButton; std::unique_ptr _animation; - rpl::event_stream _backgroundEmojiChosen; + rpl::event_stream _someCustomChosen; Data::CustomEmojiSizeTag _animationSizeTag = {}; bool _backgroundEmojiMode = false; + bool _channelStatusMode = false; }; diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 4f69791b09..47e1196eaa 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -526,7 +526,8 @@ bool ShowReactPremiumError( not_null 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(