/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/userpic/info_userpic_emoji_builder_menu_item.h" #include "base/random.h" #include "base/timer.h" #include "data/data_document.h" #include "data/data_document_media.h" #include "data/data_session.h" #include "data/stickers/data_custom_emoji.h" #include "info/userpic/info_userpic_emoji_builder.h" #include "info/userpic/info_userpic_emoji_builder_common.h" #include "info/userpic/info_userpic_emoji_builder_widget.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/menu/menu_common.h" #include "ui/widgets/popup_menu.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_menu_icons.h" #include namespace UserpicBuilder { namespace { constexpr auto kTimeout = crl::time(1500); class StickerProvider final { public: StickerProvider(not_null owner); void setDocuments(std::vector documents); [[nodiscard]] DocumentId documentId() const; [[nodiscard]] auto documentChanged() const -> rpl::producer>; private: void processDocumentIndex(int documentIndex); [[nodiscard]] DocumentData *lookupAndRememberSticker(int documentIndex); [[nodiscard]] std::pair lookupSticker( int documentIndex) const; const not_null _owner; int _documentIndex = 0; std::vector _shuffledDocuments; base::Timer _timer; rpl::event_stream> _documentChanged; rpl::lifetime _resolvingLifetime; rpl::lifetime _downloadFinishedLifetime; }; StickerProvider::StickerProvider(not_null owner) : _owner(owner) { _timer.setCallback([=] { _documentIndex++; if (_documentIndex >= _shuffledDocuments.size()) { _documentIndex = 0; } processDocumentIndex(_documentIndex); }); } DocumentId StickerProvider::documentId() const { const auto &[document, index] = lookupSticker(_documentIndex); return document ? document->id : DocumentId(0); } void StickerProvider::setDocuments(std::vector documents) { if (documents.empty()) { return; } auto rd = std::random_device(); ranges::shuffle(documents, std::mt19937(rd())); _shuffledDocuments = std::move(documents); _documentIndex = 0; processDocumentIndex(_documentIndex); } auto StickerProvider::documentChanged() const -> rpl::producer> { return _documentChanged.events(); } void StickerProvider::processDocumentIndex(int documentIndex) { if (const auto document = lookupAndRememberSticker(documentIndex)) { _resolvingLifetime.destroy(); _owner->customEmojiManager().resolve( document->id ) | rpl::start_with_next([=](not_null d) { _resolvingLifetime.destroy(); _downloadFinishedLifetime.destroy(); const auto mediaView = d->createMediaView(); _downloadFinishedLifetime.add([=] { [[maybe_unused]] const auto copy = mediaView; }); mediaView->checkStickerLarge(); mediaView->goodThumbnailWanted(); rpl::single( rpl::empty_value() ) | rpl::then( _owner->session().downloaderTaskFinished() ) | rpl::start_with_next([=] { if (mediaView->loaded()) { _timer.callOnce(kTimeout); _documentChanged.fire_copy(mediaView->owner()); _downloadFinishedLifetime.destroy(); } }, _downloadFinishedLifetime); }, _resolvingLifetime); } else if (!_resolvingLifetime) { _timer.callOnce(kTimeout); } } DocumentData *StickerProvider::lookupAndRememberSticker(int documentIndex) { const auto &[document, index] = lookupSticker(documentIndex); if (document) { _documentIndex = index; } return document; } std::pair StickerProvider::lookupSticker( int documentIndex) const { const auto size = _shuffledDocuments.size(); for (auto i = 0; i < size; i++) { const auto unrestrictedIndex = documentIndex + i; const auto index = (unrestrictedIndex >= size) ? (unrestrictedIndex - size) : unrestrictedIndex; const auto id = _shuffledDocuments[index]; const auto document = _owner->document(id); if (document->sticker()) { return { document, index }; } } return { nullptr, 0 }; } } // namespace void AddEmojiBuilderAction( not_null controller, not_null menu, rpl::producer> documents, Fn &&done, bool isForum) { struct State final { State(not_null controller) : manager(&controller->session().data()) , colorIndex(rpl::single( rpl::empty_value() ) | rpl::then( manager.documentChanged() | rpl::skip(1) | rpl::to_empty ) | rpl::map([] { return base::RandomIndex(std::numeric_limits::max()); })) { } StickerProvider manager; rpl::variable colorIndex; }; const auto state = menu->lifetime().make_state(controller); auto item = base::make_unique_q( menu.get(), menu->st().menu, Ui::Menu::CreateAction( menu.get(), tr::lng_attach_profile_emoji(tr::now), [=, done = std::move(done), docs = rpl::duplicate(documents)] { const auto id = state->manager.documentId(); UserpicBuilder::ShowLayer( controller, { id, state->colorIndex.current(), docs, {}, isForum }, base::duplicate(done)); }), nullptr, nullptr); const auto icon = UserpicBuilder::CreateEmojiUserpic( item.get(), st::restoreUserpicIcon.size, state->manager.documentChanged(), state->colorIndex.value(), isForum); icon->setAttribute(Qt::WA_TransparentForMouseEvents); icon->move(menu->st().menu.itemIconPosition + QPoint( (st::menuIconRemove.width() - icon->width()) / 2, (st::menuIconRemove.height() - icon->height()) / 2)); rpl::duplicate( documents ) | rpl::start_with_next([=](std::vector documents) { state->manager.setDocuments(std::move(documents)); }, item->lifetime()); menu->addAction(std::move(item)); } } // namespace UserpicBuilder