diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 56ee957078..ed8caa6b9b 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -1061,11 +1061,11 @@ bool StickersBox::Inner::Row::isRecentSet() const { } bool StickersBox::Inner::Row::isMasksSet() const { - return (set->flags & SetFlag::Masks); + return (set->type() == Data::StickersType::Masks); } bool StickersBox::Inner::Row::isEmojiSet() const { - return (set->flags & SetFlag::Emoji); + return (set->type() == Data::StickersType::Emoji); } bool StickersBox::Inner::Row::isWebm() const { diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index 5ad31af19b..ec618ccca7 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -31,6 +31,14 @@ stickersTrendingSubheaderFont: normalFont; stickersTrendingSubheaderFg: windowSubTextFg; stickersTrendingSubheaderTop: 31px; +emojiPanButtonRight: 7px; +emojiPanButtonTop: 10px; +emojiPanButton: RoundButton(defaultActiveButton) { + width: -24px; + height: 23px; + textTop: 2px; +} + stickersTrendingAddTop: 14px; stickersTrendingAdd: RoundButton(defaultActiveButton) { width: -16px; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index e331425e43..b3c479be57 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/shadow.h" #include "ui/text/custom_emoji_instance.h" #include "ui/effects/ripple_animation.h" +#include "ui/effects/premium_graphics.h" #include "ui/emoji_config.h" #include "ui/ui_utility.h" #include "ui/cached_round_corners.h" @@ -31,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "core/core_settings.h" #include "core/application.h" +#include "settings/settings_premium.h" #include "window/window_session_controller.h" #include "facades.h" #include "styles/style_chat_helpers.h" @@ -363,6 +365,8 @@ EmojiListWidget::EmojiListWidget( QWidget *parent, not_null controller) : Inner(parent, controller) +, _localSetsManager( + std::make_unique(&controller->session())) , _picker(this) , _showPickerTimer([=] { showPicker(); }) , _repaintTimer([=] { invokeRepaints(); }) { @@ -399,6 +403,16 @@ EmojiListWidget::EmojiListWidget( refreshCustom(); resizeToWidth(width()); }, lifetime()); + + rpl::single( + rpl::empty + ) | rpl::then( + style::PaletteChanged() + ) | rpl::start_with_next([=] { + initButton(_add, tr::lng_stickers_featured_add(tr::now), false); + initButton(_unlock, tr::lng_emoji_featured_unlock(tr::now), true); + initButton(_restore, tr::lng_emoji_premium_restore(tr::now), true); + }, lifetime()); } EmojiListWidget::~EmojiListWidget() { @@ -581,8 +595,6 @@ bool EmojiListWidget::enumerateSections(Callback callback) const { auto i = 0; auto info = SectionInfo(); const auto session = &controller()->session(); - const auto premiumMayBeBought = !session->premium() - && session->premiumPossible(); const auto next = [&] { info.rowsCount = (info.count + _columnCount - 1) / _columnCount; info.rowsTop = info.top @@ -605,7 +617,7 @@ bool EmojiListWidget::enumerateSections(Callback callback) const { } for (auto §ion : _custom) { info.section = i++; - info.premiumRequired = section.premium && premiumMayBeBought; + info.premiumRequired = section.premiumRequired; info.count = int(section.list.size()); if (!next()) { return false; @@ -763,23 +775,13 @@ void EmojiListWidget::paintEvent(QPaintEvent *e) { } else if (r.top() + r.height() <= info.top) { return false; } - auto widthForTitle = emojiRight() - (st::emojiPanHeaderLeft - st::roundRadiusSmall); + const auto buttonSelected = selectedButton + ? (selectedButton->section == info.section) + : false; + const auto widthForTitle = emojiRight() + - (st::emojiPanHeaderLeft - st::roundRadiusSmall) + - paintButtonGetWidth(p, info, buttonSelected, r); const auto skip = st::roundRadiusSmall; - if (hasRemoveButton(info.section)) { - auto &custom = _custom[info.section - kEmojiSectionCount]; - auto remove = removeButtonRect(info.section); - if (remove.intersects(r)) { - auto selected = selectedButton ? (selectedButton->section == info.section) : false; - if (custom.ripple) { - custom.ripple->paint(p, remove.x() + st::stickerPanRemoveSet.rippleAreaPosition.x(), remove.y() + st::stickerPanRemoveSet.rippleAreaPosition.y(), width()); - if (custom.ripple->empty()) { - custom.ripple.reset(); - } - } - (selected ? st::stickerPanRemoveSet.iconOver : st::stickerPanRemoveSet.icon).paint(p, remove.topLeft() + st::stickerPanRemoveSet.iconPosition, width()); - } - widthForTitle -= remove.width(); - } if (info.section > 0 && r.top() < info.rowsTop) { p.setFont(st::emojiPanHeaderFont); p.setPen(st::emojiPanHeaderFg); @@ -1010,7 +1012,14 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { } else if (auto button = std::get_if(&pressed)) { Assert(button->section >= kEmojiSectionCount && button->section < kEmojiSectionCount + _custom.size()); - removeSet(_custom[button->section - kEmojiSectionCount].id); + const auto id = _custom[button->section - kEmojiSectionCount].id; + if (hasRemoveButton(button->section)) { + removeSet(id); + } else if (hasAddButton(button->section)) { + _localSetsManager->install(id); + } else { + Settings::ShowPremium(controller(), u"animated_emoji"_q); + } } } @@ -1089,18 +1098,88 @@ bool EmojiListWidget::hasRemoveButton(int index) const { || index >= kEmojiSectionCount + _custom.size()) { return false; } - return true; + const auto &set = _custom[index - kEmojiSectionCount]; + return set.canRemove && !set.premiumRequired; } QRect EmojiListWidget::removeButtonRect(int index) const { - auto buttonw = st::stickerPanRemoveSet.rippleAreaPosition.x() + return removeButtonRect(sectionInfo(index)); +} + +QRect EmojiListWidget::removeButtonRect(const SectionInfo &info) const { + const auto buttonw = st::stickerPanRemoveSet.rippleAreaPosition.x() + st::stickerPanRemoveSet.rippleAreaSize; - auto buttonh = st::stickerPanRemoveSet.height; - auto buttonx = emojiRight() - st::emojiPanRemoveSkip - buttonw; - auto buttony = sectionInfo(index).top + (st::emojiPanHeader - buttonh) / 2; + const auto buttonh = st::stickerPanRemoveSet.height; + const auto buttonx = emojiRight() - st::emojiPanRemoveSkip - buttonw; + const auto buttony = info.top + (st::emojiPanHeader - buttonh) / 2; return QRect(buttonx, buttony, buttonw, buttonh); } +bool EmojiListWidget::hasAddButton(int index) const { + if (index < kEmojiSectionCount + || index >= kEmojiSectionCount + _custom.size()) { + return false; + } + const auto &set = _custom[index - kEmojiSectionCount]; + return !set.canRemove && !set.premiumRequired; +} + +QRect EmojiListWidget::addButtonRect(int index) const { + return buttonRect(sectionInfo(index), _add); +} + +bool EmojiListWidget::hasUnlockButton(int index) const { + if (index < kEmojiSectionCount + || index >= kEmojiSectionCount + _custom.size()) { + return false; + } + const auto &set = _custom[index - kEmojiSectionCount]; + return set.premiumRequired; +} + +QRect EmojiListWidget::unlockButtonRect(int index) const { + Expects(index >= kEmojiSectionCount + && index < kEmojiSectionCount + _custom.size()); + + return buttonRect(sectionInfo(index), rightButton(index)); +} + +bool EmojiListWidget::hasButton(int index) const { + if (index < kEmojiSectionCount + || index >= kEmojiSectionCount + _custom.size()) { + return false; + } + return true; +} + +QRect EmojiListWidget::buttonRect(int index) const { + return hasRemoveButton(index) + ? removeButtonRect(index) + : hasAddButton(index) + ? addButtonRect(index) + : unlockButtonRect(index); +} + +QRect EmojiListWidget::buttonRect( + const SectionInfo &info, + const RightButton &button) const { + const auto buttonw = button.textWidth - st::emojiPanButton.width; + const auto buttonh = st::emojiPanButton.height; + const auto buttonx = emojiRight() - buttonw - st::emojiPanButtonRight; + const auto buttony = info.top + st::emojiPanButtonTop; + return QRect(buttonx, buttony, buttonw, buttonh); +} + +auto EmojiListWidget::rightButton(int index) const -> const RightButton & { + Expects(index >= kEmojiSectionCount + && index < kEmojiSectionCount + _custom.size()); + return hasAddButton(index) + ? _add + : _custom[index - kEmojiSectionCount].canRemove + ? _restore + : _unlock; +} + int EmojiListWidget::emojiRight() const { return emojiLeft() + (_columnCount * _singleSize.width()); } @@ -1221,15 +1300,22 @@ void EmojiListWidget::refreshRecent() { void EmojiListWidget::refreshCustom() { auto old = base::take(_custom); - const auto owner = &controller()->session().data(); - const auto &order = owner->stickers().emojiSetsOrder(); - const auto &featured = owner->stickers().featuredEmojiSetsOrder(); + const auto session = &controller()->session(); + const auto premiumPossible = session->premiumPossible(); + const auto premiumMayBeBought = premiumPossible && !session->premium(); + const auto owner = &session->data(); const auto &sets = owner->stickers().sets(); - for (const auto setId : ranges::views::concat(order, featured)) { + const auto push = [&](uint64 setId, bool installed) { auto it = sets.find(setId); if (it == sets.cend() || it->second->stickers.isEmpty()) { - continue; + return; } + const auto canRemove = !!(it->second->flags + & Data::StickersSetFlag::Installed); + if (canRemove != installed) { + return; + } + auto premium = false; const auto &list = it->second->stickers; const auto i = ranges::find(old, setId, &CustomSet::id); if (i != end(old)) { @@ -1239,19 +1325,26 @@ void EmojiListWidget::refreshCustom() { return false; } for (auto k = 0; k != count; ++k) { + if (!premium && list[k]->isPremiumEmoji()) { + premium = true; + } if (i->list[k].document != list[k]) { return false; } } return true; }(); - if (valid) { + if (premium && !premiumPossible) { + return; + } else if (valid) { i->thumbnailDocument = it->second->lookupThumbnailDocument(); + i->canRemove = canRemove; + i->premiumRequired = premium && premiumMayBeBought; + i->ripple.reset(); _custom.push_back(std::move(*i)); - continue; + return; } } - auto premium = false; auto set = std::vector(); set.reserve(list.size()); for (const auto document : list) { @@ -1260,13 +1353,13 @@ void EmojiListWidget::refreshCustom() { .instance = resolveCustomInstance(document, setId), .document = document, }); - if (document->isPremiumEmoji()) { + if (!premium && document->isPremiumEmoji()) { premium = true; } } } - if (premium && !controller()->session().premiumPossible()) { - continue; + if (premium && !premiumPossible) { + return; } _custom.push_back({ .id = setId, @@ -1274,13 +1367,22 @@ void EmojiListWidget::refreshCustom() { .thumbnailDocument = it->second->lookupThumbnailDocument(), .title = it->second->title, .list = std::move(set), - .premium = premium, + .canRemove = canRemove, + .premiumRequired = premium && premiumMayBeBought, }); + }; + for (const auto setId : owner->stickers().emojiSetsOrder()) { + push(setId, true); } + for (const auto setId : owner->stickers().featuredEmojiSetsOrder()) { + push(setId, false); + } + _footer->refreshIcons( fillIcons(), nullptr, ValidateIconAnimations::None); + update(); } auto EmojiListWidget::customInstanceWithLoader( @@ -1419,6 +1521,64 @@ std::vector EmojiListWidget::fillIcons() { return result; } +int EmojiListWidget::paintButtonGetWidth( + QPainter &p, + const SectionInfo &info, + bool selected, + QRect clip) const { + if (info.section < kEmojiSectionCount + || info.section >= kEmojiSectionCount + _custom.size()) { + return 0; + } + auto &custom = _custom[info.section - kEmojiSectionCount]; + if (hasRemoveButton(info.section)) { + const auto remove = removeButtonRect(info); + if (remove.intersects(clip)) { + if (custom.ripple) { + custom.ripple->paint( + p, + remove.x() + st::stickerPanRemoveSet.rippleAreaPosition.x(), + remove.y() + st::stickerPanRemoveSet.rippleAreaPosition.y(), + width()); + if (custom.ripple->empty()) { + custom.ripple.reset(); + } + } + (selected + ? st::stickerPanRemoveSet.iconOver + : st::stickerPanRemoveSet.icon).paint( + p, + remove.topLeft() + st::stickerPanRemoveSet.iconPosition, + width()); + } + return remove.width(); + } + const auto canAdd = hasAddButton(info.section); + const auto &button = rightButton(info.section); + const auto rect = buttonRect(info, button); + p.drawImage(rect.topLeft(), selected ? button.backOver : button.back); + if (custom.ripple) { + const auto ripple = QColor(0, 0, 0, 36); + custom.ripple->paint(p, rect.x(), rect.y(), width(), &ripple); + if (custom.ripple->empty()) { + custom.ripple.reset(); + } + } + p.setPen(!canAdd + ? st::premiumButtonFg + : selected + ? st::emojiPanButton.textFgOver + : st::emojiPanButton.textFg); + p.setFont(st::emojiPanButton.font); + p.drawText( + rect.x() - (st::emojiPanButton.width / 2), + (rect.y() + + st::emojiPanButton.textTop + + st::emojiPanButton.font->ascent), + button.text); + return rect.width(); +} + bool EmojiListWidget::eventHook(QEvent *e) { if (e->type() == QEvent::ParentChange) { if (_picker->parentWidget() != parentWidget()) { @@ -1439,9 +1599,8 @@ void EmojiListWidget::updateSelected() { auto info = sectionInfoByOffset(p.y()); auto section = info.section; if (p.y() >= info.top && p.y() < info.rowsTop) { - if (hasRemoveButton(section) - && myrtlrect( - removeButtonRect(section)).contains(p.x(), p.y())) { + if (hasButton(section) + && myrtlrect(buttonRect(section)).contains(p.x(), p.y())) { newSelected = OverButton{ section }; } else if (section >= kEmojiSectionCount) { newSelected = OverSet{ section }; @@ -1470,7 +1629,7 @@ void EmojiListWidget::setSelected(OverState newSelected) { if (const auto sticker = std::get_if(&_selected)) { rtlupdate(emojiRect(sticker->section, sticker->index)); } else if (const auto button = std::get_if(&_selected)) { - rtlupdate(removeButtonRect(button->section)); + rtlupdate(buttonRect(button->section)); } }; updateSelected(); @@ -1513,27 +1672,72 @@ void EmojiListWidget::setPressed(OverState newPressed) { } } +void EmojiListWidget::initButton( + RightButton &button, + const QString &text, + bool gradient) { + button.text = text; + button.textWidth = st::emojiPanButton.font->width(text); + const auto width = button.textWidth - st::emojiPanButton.width; + const auto height = st::emojiPanButton.height; + const auto factor = style::DevicePixelRatio(); + auto prepare = [&](QColor bg, QBrush fg) { + auto image = QImage( + QSize(width, height) * factor, + QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(factor); + image.fill(Qt::transparent); + auto p = QPainter(&image); + auto hq = PainterHighQualityEnabler(p); + p.setPen(Qt::NoPen); + p.setBrush(fg); + const auto radius = height / 2.; + p.drawRoundedRect(QRect(0, 0, width, height), radius, radius); + p.end(); + return image; + }; + button.back = prepare(Qt::transparent, [&]() -> QBrush { + if (gradient) { + auto result = QLinearGradient(QPointF(0, 0), QPointF(width, 0)); + result.setStops(Ui::Premium::ButtonGradientStops()); + return result; + } + return st::emojiPanButton.textBg; + }()); + button.backOver = gradient + ? button.back + : prepare(Qt::transparent, st::emojiPanButton.textBgOver); + button.rippleMask = prepare(Qt::black, Qt::white); +} + std::unique_ptr EmojiListWidget::createButtonRipple( int section) { Expects(section >= kEmojiSectionCount && section < kEmojiSectionCount + _custom.size()); - auto maskSize = QSize( - st::stickerPanRemoveSet.rippleAreaSize, - st::stickerPanRemoveSet.rippleAreaSize); - auto mask = Ui::RippleAnimation::ellipseMask(maskSize); + const auto remove = hasRemoveButton(section); + const auto &st = remove + ? st::stickerPanRemoveSet.ripple + : st::emojiPanButton.ripple; + auto mask = remove + ? Ui::RippleAnimation::ellipseMask(QSize( + st::stickerPanRemoveSet.rippleAreaSize, + st::stickerPanRemoveSet.rippleAreaSize)) + : rightButton(section).rippleMask; return std::make_unique( - st::stickerPanRemoveSet.ripple, + st, std::move(mask), - [this, section] { rtlupdate(removeButtonRect(section)); }); + [this, section] { rtlupdate(buttonRect(section)); }); } QPoint EmojiListWidget::buttonRippleTopLeft(int section) const { Expects(section >= kEmojiSectionCount && section < kEmojiSectionCount + _custom.size()); - return myrtlrect(removeButtonRect(section)).topLeft() - + st::stickerPanRemoveSet.rippleAreaPosition; + return myrtlrect(buttonRect(section)).topLeft() + + (hasRemoveButton(section) + ? st::stickerPanRemoveSet.rippleAreaPosition + : QPoint()); } void EmojiListWidget::showEmojiSection(Section section) { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h index 2b013f96d7..53ce0a929f 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h @@ -49,6 +49,7 @@ inline constexpr auto kEmojiSectionCount = 8; struct StickerIcon; class EmojiColorPicker; class StickersListFooter; +class LocalStickersManager; class EmojiListWidget : public TabbedSelector::Inner @@ -120,9 +121,17 @@ private: DocumentData *thumbnailDocument = nullptr; QString title; std::vector list; - std::unique_ptr ripple; + mutable std::unique_ptr ripple; bool painted = false; - bool premium = false; + bool canRemove = false; + bool premiumRequired = false; + }; + struct RightButton { + QImage back; + QImage backOver; + QImage rippleMask; + QString text; + int textWidth = 0; }; struct RecentOne; struct RepaintSet { @@ -210,15 +219,32 @@ private: int index); [[nodiscard]] bool hasRemoveButton(int index) const; [[nodiscard]] QRect removeButtonRect(int index) const; + [[nodiscard]] QRect removeButtonRect(const SectionInfo &info) const; + [[nodiscard]] bool hasAddButton(int index) const; + [[nodiscard]] QRect addButtonRect(int index) const; + [[nodiscard]] bool hasUnlockButton(int index) const; + [[nodiscard]] QRect unlockButtonRect(int index) const; + [[nodiscard]] bool hasButton(int index) const; + [[nodiscard]] QRect buttonRect(int index) const; + [[nodiscard]] QRect buttonRect( + const SectionInfo &info, + const RightButton &button) const; + [[nodiscard]] const RightButton &rightButton(int index) const; [[nodiscard]] QRect emojiRect(int section, int index) const; [[nodiscard]] int emojiRight() const; [[nodiscard]] int emojiLeft() const; [[nodiscard]] uint64 sectionSetId(int section) const; [[nodiscard]] std::vector fillIcons(); + int paintButtonGetWidth( + QPainter &p, + const SectionInfo &info, + bool selected, + QRect clip) const; void displaySet(uint64 setId); void removeSet(uint64 setId); + void initButton(RightButton &button, const QString &text, bool gradient); [[nodiscard]] std::unique_ptr createButtonRipple( int section); [[nodiscard]] QPoint buttonRippleTopLeft(int section) const; @@ -246,6 +272,7 @@ private: uint64 setId); StickersListFooter *_footer = nullptr; + std::unique_ptr _localSetsManager; int _counts[kEmojiSectionCount]; std::vector _recent; @@ -261,6 +288,10 @@ private: QPoint _areaPosition; QPoint _innerPosition; + RightButton _add; + RightButton _unlock; + RightButton _restore; + OverState _selected; OverState _pressed; OverState _pickerSelected; diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index c42da30ddc..9e47c9054f 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -1275,4 +1275,79 @@ void StickersListFooter::paintSetIcon( } } +LocalStickersManager::LocalStickersManager(not_null session) +: _session(session) +, _api(&session->mtp()) { +} + +void LocalStickersManager::install(uint64 setId) { + const auto &sets = _session->data().stickers().sets(); + const auto it = sets.find(setId); + if (it == sets.cend()) { + return; + } + const auto set = it->second.get(); + const auto input = set->mtpInput(); + if (!(set->flags & Data::StickersSetFlag::NotLoaded) + && !set->stickers.empty()) { + sendInstallRequest(setId, input); + return; + } + _api.request(MTPmessages_GetStickerSet( + input, + MTP_int(0) // hash + )).done([=](const MTPmessages_StickerSet &result) { + result.match([&](const MTPDmessages_stickerSet &data) { + _session->data().stickers().feedSetFull(data); + }, [](const MTPDmessages_stickerSetNotModified &) { + LOG(("API Error: Unexpected messages.stickerSetNotModified.")); + }); + sendInstallRequest(setId, input); + }).send(); +} + +bool LocalStickersManager::isInstalledLocally(uint64 setId) const { + return _installedLocallySets.contains(setId); +} + +void LocalStickersManager::sendInstallRequest( + uint64 setId, + const MTPInputStickerSet &input) { + _api.request(MTPmessages_InstallStickerSet( + input, + MTP_bool(false) + )).done([=](const MTPmessages_StickerSetInstallResult &result) { + if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { + _session->data().stickers().applyArchivedResult( + result.c_messages_stickerSetInstallResultArchive()); + } + }).fail([=] { + notInstalledLocally(setId); + _session->data().stickers().undoInstallLocally(setId); + }).send(); + + installedLocally(setId); + _session->data().stickers().installLocally(setId); +} + +void LocalStickersManager::installedLocally(uint64 setId) { + _installedLocallySets.insert(setId); +} + +void LocalStickersManager::notInstalledLocally(uint64 setId) { + _installedLocallySets.remove(setId); +} + +void LocalStickersManager::removeInstalledLocally(uint64 setId) { + _installedLocallySets.remove(setId); +} + +bool LocalStickersManager::clearInstalledLocally() { + if (_installedLocallySets.empty()) { + return false; + } + _installedLocallySets.clear(); + return true; +} + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index 716ed504a1..c7a28973a6 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/clip/media_clip_reader.h" #include "chat_helpers/tabbed_selector.h" +#include "mtproto/sender.h" #include "ui/round_rect.h" namespace Ui { @@ -251,4 +252,27 @@ private: }; +class LocalStickersManager final { +public: + explicit LocalStickersManager(not_null session); + + void install(uint64 setId); + [[nodiscard]] bool isInstalledLocally(uint64 setId) const; + void removeInstalledLocally(uint64 setId); + bool clearInstalledLocally(); + +private: + void sendInstallRequest( + uint64 setId, + const MTPInputStickerSet &input); + void installedLocally(uint64 setId); + void notInstalledLocally(uint64 setId); + + const not_null _session; + MTP::Sender _api; + + base::flat_set _installedLocallySets; + +}; + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index 5d74b09aea..1ffce3fe8d 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -165,6 +165,8 @@ StickersListWidget::StickersListWidget( bool masks) : Inner(parent, controller) , _api(&controller->session().mtp()) +, _localSetsManager( + std::make_unique(&controller->session())) , _section(Section::Stickers) , _isMasks(masks) , _updateItemsTimer([=] { updateItems(); }) @@ -495,21 +497,6 @@ int StickersListWidget::countDesiredHeight(int newWidth) { + st::stickerPanPadding; } -void StickersListWidget::installedLocally(uint64 setId) { - _installedLocallySets.insert(setId); -} - -void StickersListWidget::notInstalledLocally(uint64 setId) { - _installedLocallySets.remove(setId); -} - -void StickersListWidget::clearInstalledLocally() { - if (!_installedLocallySets.empty()) { - _installedLocallySets.clear(); - refreshStickers(); - } -} - void StickersListWidget::sendSearchRequest() { if (_searchRequestId || _searchNextQuery.isEmpty()) { return; @@ -851,7 +838,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::roundRadiusSmall); if (featuredHasAddButton(info.section)) { - auto add = featuredAddRect(info.section); + auto add = featuredAddRect(info); auto selected = selectedButton ? (selectedButton->section == info.section) : false; auto &textBg = selected ? st::stickersTrendingAdd.textBgOver : st::stickersTrendingAdd.textBg; @@ -868,7 +855,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { widthForTitle -= add.width() - (st::stickersTrendingAdd.width / 2); } else { - auto add = featuredAddRect(info.section); + auto add = featuredAddRect(info); int checkx = add.left() + (add.width() - st::stickersFeaturedInstalled.width()) / 2; int checky = add.top() + (add.height() - st::stickersFeaturedInstalled.height()) / 2; st::stickersFeaturedInstalled.paint(p, QPoint(checkx, checky), width()); @@ -924,7 +911,7 @@ void StickersListWidget::paintStickers(Painter &p, QRect clip) { auto titleWidth = st::stickersTrendingHeaderFont->width(titleText); auto widthForTitle = stickersRight() - (st::emojiPanHeaderLeft - st::roundRadiusSmall); if (hasRemoveButton(info.section)) { - auto remove = removeButtonRect(info.section); + auto remove = removeButtonRect(info); auto selected = selectedButton ? (selectedButton->section == info.section) : false; if (set.ripple) { set.ripple->paint(p, remove.x() + st::stickerPanRemoveSet.rippleAreaPosition.x(), remove.y() + st::stickerPanRemoveSet.rippleAreaPosition.y(), width()); @@ -1430,10 +1417,14 @@ bool StickersListWidget::featuredHasAddButton(int index) const { } QRect StickersListWidget::featuredAddRect(int index) const { + return featuredAddRect(sectionInfo(index)); +} + +QRect StickersListWidget::featuredAddRect(const SectionInfo &info) const { auto addw = _addWidth - st::stickersTrendingAdd.width; auto addh = st::stickersTrendingAdd.height; auto addx = stickersRight() - addw; - auto addy = sectionInfo(index).top + st::stickersTrendingAddTop; + auto addy = info.top + st::stickersTrendingAddTop; return QRect(addx, addy, addw, addh); } @@ -1460,10 +1451,14 @@ bool StickersListWidget::hasRemoveButton(int index) const { } QRect StickersListWidget::removeButtonRect(int index) const { + return removeButtonRect(sectionInfo(index)); +} + +QRect StickersListWidget::removeButtonRect(const SectionInfo &info) const { auto buttonw = st::stickerPanRemoveSet.width; auto buttonh = st::stickerPanRemoveSet.height; auto buttonx = stickersRight() - buttonw; - auto buttony = sectionInfo(index).top + (st::emojiPanHeader - buttonh) / 2; + auto buttony = info.top + (st::emojiPanHeader - buttonh) / 2; return QRect(buttonx, buttony, buttonw, buttonh); } @@ -1692,7 +1687,7 @@ void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) { } else if (auto button = std::get_if(&pressed)) { Assert(button->section >= 0 && button->section < sets.size()); if (sets[button->section].externalLayout) { - installSet(sets[button->section].id); + _localSetsManager->install(sets[button->section].id); } else if (sets[button->section].id == Data::Stickers::MegagroupSetId) { auto removeLocally = sets[button->section].stickers.empty() || !_megagroupSet->canEditStickers(); @@ -1823,7 +1818,9 @@ void StickersListWidget::processHideFinished() { } void StickersListWidget::processPanelHideFinished() { - clearInstalledLocally(); + if (_localSetsManager->clearInstalledLocally()) { + refreshStickers(); + } clearHeavyData(); if (_footer) { _footer->clearHeavyData(); @@ -1925,7 +1922,7 @@ void StickersListWidget::refreshFeaturedSets() { if (it == sets.cend() || ((it->second->flags & SetFlag::Installed) && !(it->second->flags & SetFlag::Archived) - && !_installedLocallySets.contains(set.id))) { + && !_localSetsManager->isInstalledLocally(set.id))) { continue; } set.flags = it->second->flags; @@ -1949,7 +1946,7 @@ void StickersListWidget::refreshSearchSets() { entry.stickers = std::move(elements); } if (!SetInMyList(entry.flags)) { - _installedLocallySets.remove(entry.id); + _localSetsManager->removeInstalledLocally(entry.id); entry.externalLayout = true; } } @@ -2017,7 +2014,7 @@ bool StickersListWidget::appendSet( if ((skip == AppendSkip::Installed) && (set->flags & SetFlag::Installed) && !(set->flags & SetFlag::Archived)) { - if (!_installedLocallySets.contains(setId)) { + if (!_localSetsManager->isInstalledLocally(setId)) { return false; } } @@ -2379,22 +2376,22 @@ void StickersListWidget::updateSelected() { auto info = sectionInfoByOffset(p.y()); auto section = info.section; if (p.y() >= info.top && p.y() < info.rowsTop) { - if (hasRemoveButton(section) && myrtlrect(removeButtonRect(section)).contains(p.x(), p.y())) { - newSelected = OverButton { section }; - } else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(section)).contains(p.x(), p.y())) { + if (hasRemoveButton(section) && myrtlrect(removeButtonRect(info)).contains(p.x(), p.y())) { + newSelected = OverButton{ section }; + } else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(info)).contains(p.x(), p.y())) { newSelected = OverButton{ section }; } else if (!(sets[section].flags & SetFlag::Special)) { - newSelected = OverSet { section }; + newSelected = OverSet{ section }; } else if (sets[section].id == Data::Stickers::MegagroupSetId && (_megagroupSet->canEditStickers() || !sets[section].stickers.empty())) { - newSelected = OverSet { section }; + newSelected = OverSet{ section }; } } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom && sx >= 0) { auto yOffset = p.y() - info.rowsTop; auto &set = sets[section]; if (set.id == Data::Stickers::MegagroupSetId && set.stickers.empty()) { if (_megagroupSetButtonRect.contains(stickersLeft() + sx, yOffset)) { - newSelected = OverGroupAdd {}; + newSelected = OverGroupAdd{}; } } else { auto rowIndex = qFloor(yOffset / _singleSize.height()); @@ -2616,50 +2613,6 @@ void StickersListWidget::displaySet(uint64 setId) { } } -void StickersListWidget::installSet(uint64 setId) { - const auto &sets = session().data().stickers().sets(); - const auto it = sets.find(setId); - if (it != sets.cend()) { - const auto set = it->second.get(); - const auto input = set->mtpInput(); - if ((set->flags & SetFlag::NotLoaded) || set->stickers.empty()) { - _api.request(MTPmessages_GetStickerSet( - input, - MTP_int(0) // hash - )).done([=](const MTPmessages_StickerSet &result) { - result.match([&](const MTPDmessages_stickerSet &data) { - session().data().stickers().feedSetFull(data); - }, [](const MTPDmessages_stickerSetNotModified &) { - LOG(("API Error: Unexpected messages.stickerSetNotModified.")); - }); - sendInstallRequest(setId, input); - }).send(); - } else { - sendInstallRequest(setId, input); - } - } -} - -void StickersListWidget::sendInstallRequest( - uint64 setId, - const MTPInputStickerSet &input) { - _api.request(MTPmessages_InstallStickerSet( - input, - MTP_bool(false) - )).done([=](const MTPmessages_StickerSetInstallResult &result) { - if (result.type() == mtpc_messages_stickerSetInstallResultArchive) { - session().data().stickers().applyArchivedResult( - result.c_messages_stickerSetInstallResultArchive()); - } - }).fail([=] { - notInstalledLocally(setId); - session().data().stickers().undoInstallLocally(setId); - }).send(); - - installedLocally(setId); - session().data().stickers().installLocally(setId); -} - void StickersListWidget::removeMegagroupSet(bool locally) { if (locally) { session().settings().setGroupStickersSectionHidden(_megagroupSet->id); @@ -2693,15 +2646,15 @@ void StickersListWidget::removeSet(uint64 setId) { } const Data::StickersSetsOrder &StickersListWidget::defaultSetsOrder() const { - return !_isMasks - ? session().data().stickers().setsOrder() - : session().data().stickers().maskSetsOrder(); + return _isMasks + ? session().data().stickers().maskSetsOrder() + : session().data().stickers().setsOrder(); } Data::StickersSetsOrder &StickersListWidget::defaultSetsOrderRef() { - return !_isMasks - ? session().data().stickers().setsOrderRef() - : session().data().stickers().maskSetsOrderRef(); + return _isMasks + ? session().data().stickers().maskSetsOrderRef() + : session().data().stickers().setsOrderRef(); } bool StickersListWidget::mySetsEmpty() const { @@ -2762,9 +2715,9 @@ object_ptr MakeConfirmRemoveSetBox( // && !(set->flags & SetFlag::Special)) { // sets.erase(it); //} - auto &orderRef = (set->flags & SetFlag::Emoji) + auto &orderRef = (set->type() == Data::StickersType::Emoji) ? session->data().stickers().emojiSetsOrderRef() - : (set->flags & SetFlag::Masks) + : (set->type() == Data::StickersType::Masks) ? session->data().stickers().maskSetsOrderRef() : session->data().stickers().setsOrderRef(); const auto removeIndex = orderRef.indexOf(setId); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h index 2a29e9d0bf..5b2d338a14 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.h @@ -49,6 +49,7 @@ namespace ChatHelpers { struct StickerIcon; enum class ValidateIconAnimations; class StickersListFooter; +class LocalStickersManager; class StickersListWidget final : public TabbedSelector::Inner { public: @@ -78,10 +79,6 @@ public: uint64 currentSet(int yOffset) const; - void installedLocally(uint64 setId); - void notInstalledLocally(uint64 setId); - void clearInstalledLocally(); - void sendSearchRequest(); void searchForSets(const QString &query); @@ -202,12 +199,8 @@ private: void setSection(Section section); void displaySet(uint64 setId); - void installSet(uint64 setId); void removeMegagroupSet(bool locally); void removeSet(uint64 setId); - void sendInstallRequest( - uint64 setId, - const MTPInputStickerSet &input); void refreshMySets(); void refreshFeaturedSets(); void refreshSearchSets(); @@ -277,14 +270,16 @@ private: const SectionInfo &info, crl::time now); - int stickersRight() const; - bool featuredHasAddButton(int index) const; - QRect featuredAddRect(int index) const; - bool hasRemoveButton(int index) const; - QRect removeButtonRect(int index) const; - int megagroupSetInfoLeft() const; + [[nodiscard]] int stickersRight() const; + [[nodiscard]] bool featuredHasAddButton(int index) const; + [[nodiscard]] QRect featuredAddRect(int index) const; + [[nodiscard]] QRect featuredAddRect(const SectionInfo &info) const; + [[nodiscard]] bool hasRemoveButton(int index) const; + [[nodiscard]] QRect removeButtonRect(int index) const; + [[nodiscard]] QRect removeButtonRect(const SectionInfo &info) const; + [[nodiscard]] int megagroupSetInfoLeft() const; void refreshMegagroupSetGeometry(); - QRect megagroupSetButtonRectFinal() const; + [[nodiscard]] QRect megagroupSetButtonRectFinal() const; [[nodiscard]] const Data::StickersSetsOrder &defaultSetsOrder() const; [[nodiscard]] Data::StickersSetsOrder &defaultSetsOrderRef(); @@ -332,6 +327,7 @@ private: not_null document); MTP::Sender _api; + std::unique_ptr _localSetsManager; ChannelData *_megagroupSet = nullptr; uint64 _megagroupSetIdRequested = 0; std::vector _mySets; @@ -339,7 +335,6 @@ private: std::vector _searchSets; int _premiumsIndex = -1; int _featuredSetsCount = 0; - base::flat_set _installedLocallySets; std::vector _custom; base::flat_set> _favedStickersMap; std::weak_ptr _lottieRenderer; diff --git a/Telegram/SourceFiles/data/stickers/data_stickers.cpp b/Telegram/SourceFiles/data/stickers/data_stickers.cpp index 42d88ea4df..467a707650 100644 --- a/Telegram/SourceFiles/data/stickers/data_stickers.cpp +++ b/Telegram/SourceFiles/data/stickers/data_stickers.cpp @@ -356,9 +356,12 @@ void Stickers::applyArchivedResult( if (set->flags & SetFlag::NotLoaded) { setsToRequest.insert(set->id, set->accessHash); } - const auto masks = !!(set->flags & SetFlag::Masks); - (masks ? masksCount : stickersCount)++; - auto &order = masks ? maskSetsOrderRef() : setsOrderRef(); + if (set->type() == StickersType::Emoji) { + continue; + } + const auto isMasks = (set->type() == StickersType::Masks); + (isMasks ? masksCount : stickersCount)++; + auto &order = isMasks ? maskSetsOrderRef() : setsOrderRef(); const auto index = order.indexOf(set->id); if (index >= 0) { order.removeAt(index); @@ -692,12 +695,7 @@ void Stickers::somethingReceived( QMap setsToRequest; for (auto &[id, set] : sets) { const auto archived = !!(set->flags & SetFlag::Archived); - const auto setType = !!(set->flags & SetFlag::Emoji) - ? StickersType::Emoji - : !!(set->flags & SetFlag::Masks) - ? StickersType::Masks - : StickersType::Stickers; - if (!archived && (type == setType)) { + if (!archived && (type == set->type())) { // Mark for removing. set->flags &= ~SetFlag::Installed; set->installDate = 0; @@ -964,10 +962,10 @@ void Stickers::featuredReceived( }; const auto isEmoji = (type == StickersType::Emoji); - auto &setsOrder = isEmoji + auto &featuredOrder = isEmoji ? featuredEmojiSetsOrderRef() : featuredSetsOrderRef(); - setsOrder.clear(); + featuredOrder.clear(); auto &sets = setsRef(); auto setsToRequest = base::flat_map(); @@ -1000,15 +998,14 @@ void Stickers::featuredReceived( } return ImageWithLocation(); }(); + const auto setId = data->vid().v; const auto flags = SetFlag::Featured - | (unreadMap.contains(data->vid().v) - ? SetFlag::Unread - : SetFlag()) + | (unreadMap.contains(setId) ? SetFlag::Unread : SetFlag()) | ParseStickersSetFlags(*data); if (it == sets.cend()) { it = sets.emplace(data->vid().v, std::make_unique( &owner(), - data->vid().v, + setId, data->vaccess_hash().v, data->vhash().v, title, @@ -1032,7 +1029,7 @@ void Stickers::featuredReceived( } it->second->setThumbnail(thumbnail); it->second->thumbnailDocumentId = data->vthumb_document_id().value_or_empty(); - setsOrder.push_back(data->vid().v); + featuredOrder.push_back(data->vid().v); if (it->second->stickers.isEmpty() || (it->second->flags & SetFlag::NotLoaded)) { setsToRequest.emplace(data->vid().v, data->vaccess_hash().v); @@ -1361,8 +1358,8 @@ not_null Stickers::feedSet(const MTPStickerSet &info) { set->thumbnailDocumentId = data.vthumb_document_id().value_or_empty(); auto changedFlags = (oldFlags ^ set->flags); if (changedFlags & SetFlag::Archived) { - const auto masks = !!(set->flags & SetFlag::Masks); - auto &archivedOrder = masks + const auto isMasks = (set->type() == StickersType::Masks); + auto &archivedOrder = isMasks ? archivedMaskSetsOrderRef() : archivedSetsOrderRef(); const auto index = archivedOrder.indexOf(set->id); diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 5982a5f92d..dd326f7fa8 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -403,7 +403,7 @@ void Form::processDetails(const MTPDpayments_paymentForm &data) { _invoice.cover.title = qs(data.vtitle()); _invoice.cover.description = TextUtilities::ParseEntities( qs(data.vdescription()), - TextParseLinks | TextParseMultiline) + TextParseLinks | TextParseMultiline); if (_invoice.cover.thumbnail.isNull() && !_thumbnailLoadProcess) { if (const auto photo = data.vphoto()) { loadThumbnail( @@ -435,7 +435,7 @@ void Form::processDetails(const MTPDpayments_paymentReceipt &data) { .providerId = data.vprovider_id().v, }; if (_invoice.cover.title.isEmpty() - && _invoice.cover.description.isEmpty() + && _invoice.cover.description.empty() && _invoice.cover.thumbnail.isNull() && !_thumbnailLoadProcess) { _invoice.cover = Ui::Cover{ diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index de5614bff7..22d66ac1e1 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -2019,9 +2019,8 @@ void Account::writeInstalledStickers() { return StickerSetCheckResult::Skip; } } else if (!(set.flags & SetFlag::Installed) - || (set.flags & SetFlag::Archived)) { - return StickerSetCheckResult::Skip; - } else if (set.flags & (SetFlag::Masks | SetFlag::Emoji)) { + || (set.flags & SetFlag::Archived) + || (set.type() != Data::StickersType::Stickers)) { return StickerSetCheckResult::Skip; } else if (set.flags & SetFlag::NotLoaded) { // waiting to receive @@ -2042,9 +2041,9 @@ void Account::writeFeaturedStickers() { || set.id == Data::Stickers::CloudRecentAttachedSetId) { // separate files for them return StickerSetCheckResult::Skip; - } else if (set.flags & (SetFlag::Special | SetFlag::Emoji)) { - return StickerSetCheckResult::Skip; - } else if (!(set.flags & SetFlag::Featured)) { + } else if ((set.flags & SetFlag::Special) + || !(set.flags & SetFlag::Featured) + || (set.type() != Data::StickersType::Stickers)) { return StickerSetCheckResult::Skip; } else if (set.flags & SetFlag::NotLoaded) { // waiting to receive return StickerSetCheckResult::Abort; @@ -2059,9 +2058,8 @@ void Account::writeFeaturedCustomEmoji() { using SetFlag = Data::StickersSetFlag; writeStickerSets(_featuredCustomEmojiKey, [](const Data::StickersSet &set) { - if (!(set.flags & SetFlag::Emoji)) { - return StickerSetCheckResult::Skip; - } else if (!(set.flags & SetFlag::Featured)) { + if (!(set.flags & SetFlag::Featured) + || (set.type() != Data::StickersType::Emoji)) { return StickerSetCheckResult::Skip; } else if (set.flags & SetFlag::NotLoaded) { // waiting to receive return StickerSetCheckResult::Abort; @@ -2095,10 +2093,8 @@ void Account::writeArchivedStickers() { using SetFlag = Data::StickersSetFlag; writeStickerSets(_archivedStickersKey, [](const Data::StickersSet &set) { - if (set.flags & (SetFlag::Masks | SetFlag::Emoji)) { - return StickerSetCheckResult::Skip; - } if (!(set.flags & SetFlag::Archived) + || (set.type() != Data::StickersType::Stickers) || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } @@ -2110,10 +2106,9 @@ void Account::writeArchivedMasks() { using SetFlag = Data::StickersSetFlag; writeStickerSets(_archivedStickersKey, [](const Data::StickersSet &set) { - if (!(set.flags & SetFlag::Masks)) { - return StickerSetCheckResult::Skip; - } - if (!(set.flags & SetFlag::Archived) || set.stickers.isEmpty()) { + if (!(set.flags & SetFlag::Archived) + || (set.type() != Data::StickersType::Masks) + || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; @@ -2124,7 +2119,10 @@ void Account::writeInstalledMasks() { using SetFlag = Data::StickersSetFlag; writeStickerSets(_installedMasksKey, [](const Data::StickersSet &set) { - if (!(set.flags & SetFlag::Masks) || set.stickers.isEmpty()) { + if (!(set.flags & SetFlag::Installed) + || (set.flags & SetFlag::Archived) + || (set.type() != Data::StickersType::Masks) + || set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write; @@ -2145,14 +2143,14 @@ void Account::writeInstalledCustomEmoji() { using SetFlag = Data::StickersSetFlag; writeStickerSets(_installedCustomEmojiKey, [](const Data::StickersSet &set) { - if (!(set.flags & SetFlag::Emoji)) { + if (!(set.flags & SetFlag::Installed) + || (set.flags & SetFlag::Archived) + || (set.type() != Data::StickersType::Emoji)) { return StickerSetCheckResult::Skip; } else if (set.flags & SetFlag::NotLoaded) { // waiting to receive return StickerSetCheckResult::Abort; - } else if (!(set.flags & SetFlag::Installed) - || (set.flags & SetFlag::Archived) - || set.stickers.isEmpty()) { + } else if (set.stickers.isEmpty()) { return StickerSetCheckResult::Skip; } return StickerSetCheckResult::Write;