stickers_list_widget.cpp 86 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "chat_helpers/stickers_list_widget.h"
  8. #include "base/timer_rpl.h"
  9. #include "core/application.h"
  10. #include "data/data_document.h"
  11. #include "data/data_document_media.h"
  12. #include "data/data_session.h"
  13. #include "data/data_channel.h"
  14. #include "data/data_file_origin.h"
  15. #include "data/data_cloud_file.h"
  16. #include "data/data_changes.h"
  17. #include "data/data_peer_values.h"
  18. #include "menu/menu_send.h" // SendMenu::FillSendMenu
  19. #include "chat_helpers/stickers_lottie.h"
  20. #include "chat_helpers/stickers_list_footer.h"
  21. #include "ui/controls/tabbed_search.h"
  22. #include "ui/widgets/buttons.h"
  23. #include "ui/widgets/popup_menu.h"
  24. #include "ui/effects/animations.h"
  25. #include "ui/effects/ripple_animation.h"
  26. #include "ui/effects/path_shift_gradient.h"
  27. #include "ui/effects/premium_graphics.h"
  28. #include "ui/image/image.h"
  29. #include "ui/cached_round_corners.h"
  30. #include "ui/power_saving.h"
  31. #include "lottie/lottie_multi_player.h"
  32. #include "lottie/lottie_single_player.h"
  33. #include "lottie/lottie_animation.h"
  34. #include "boxes/stickers_box.h"
  35. #include "inline_bots/inline_bot_result.h"
  36. #include "storage/storage_account.h"
  37. #include "lang/lang_keys.h"
  38. #include "mainwindow.h"
  39. #include "dialogs/ui/dialogs_layout.h"
  40. #include "boxes/sticker_set_box.h"
  41. #include "boxes/stickers_box.h"
  42. #include "ui/boxes/confirm_box.h"
  43. #include "ui/painter.h"
  44. #include "window/window_session_controller.h" // GifPauseReason.
  45. #include "main/main_session.h"
  46. #include "main/main_session_settings.h"
  47. #include "media/clip/media_clip_reader.h"
  48. #include "apiwrap.h"
  49. #include "api/api_toggling_media.h" // Api::ToggleFavedSticker
  50. #include "api/api_premium.h"
  51. #include "styles/style_chat_helpers.h"
  52. #include "styles/style_window.h"
  53. #include "styles/style_menu_icons.h"
  54. #include <QtWidgets/QApplication>
  55. namespace ChatHelpers {
  56. namespace {
  57. constexpr auto kSearchRequestDelay = 400;
  58. constexpr auto kRecentDisplayLimit = 20;
  59. constexpr auto kPreloadOfficialPages = 4;
  60. constexpr auto kOfficialLoadLimit = 40;
  61. constexpr auto kMinRepaintDelay = crl::time(33);
  62. constexpr auto kMinAfterScrollDelay = crl::time(33);
  63. using Data::StickersSet;
  64. using Data::StickersPack;
  65. using Data::StickersSetThumbnailView;
  66. using SetFlag = Data::StickersSetFlag;
  67. [[nodiscard]] bool SetInMyList(Data::StickersSetFlags flags) {
  68. return (flags & SetFlag::Installed) && !(flags & SetFlag::Archived);
  69. }
  70. } // namespace
  71. struct StickersListWidget::Sticker {
  72. not_null<DocumentData*> document;
  73. std::shared_ptr<Data::DocumentMedia> documentMedia;
  74. Lottie::Animation *lottie = nullptr;
  75. Media::Clip::ReaderPointer webm;
  76. QImage savedFrame;
  77. QSize savedFrameFor;
  78. QImage premiumLock;
  79. void ensureMediaCreated();
  80. };
  81. struct StickersListWidget::Set {
  82. Set(
  83. uint64 id,
  84. Data::StickersSet *set,
  85. Data::StickersSetFlags flags,
  86. const QString &title,
  87. const QString &shortName,
  88. int count,
  89. bool externalLayout,
  90. std::vector<Sticker> &&stickers = {});
  91. Set(Set &&other);
  92. Set &operator=(Set &&other);
  93. ~Set();
  94. uint64 id = 0;
  95. Data::StickersSet *set = nullptr;
  96. DocumentData *thumbnailDocument = nullptr;
  97. Data::StickersSetFlags flags;
  98. QString title;
  99. QString shortName;
  100. std::vector<Sticker> stickers;
  101. std::unique_ptr<Ui::RippleAnimation> ripple;
  102. crl::time lastUpdateTime = 0;
  103. std::unique_ptr<Lottie::MultiPlayer> lottiePlayer;
  104. rpl::lifetime lottieLifetime;
  105. int count = 0;
  106. bool externalLayout = false;
  107. };
  108. auto StickersListWidget::PrepareStickers(
  109. const QVector<DocumentData*> &pack,
  110. bool skipPremium)
  111. -> std::vector<Sticker> {
  112. return ranges::views::all(
  113. pack
  114. ) | ranges::views::filter([&](DocumentData *document) {
  115. return !skipPremium || !document->isPremiumSticker();
  116. }) | ranges::views::transform([](DocumentData *document) {
  117. return Sticker{ document };
  118. }) | ranges::to_vector;
  119. }
  120. StickersListWidget::Set::Set(
  121. uint64 id,
  122. StickersSet *set,
  123. Data::StickersSetFlags flags,
  124. const QString &title,
  125. const QString &shortName,
  126. int count,
  127. bool externalLayout,
  128. std::vector<Sticker> &&stickers)
  129. : id(id)
  130. , set(set)
  131. , flags(flags)
  132. , title(title)
  133. , shortName(shortName)
  134. , stickers(std::move(stickers))
  135. , count(count)
  136. , externalLayout(externalLayout) {
  137. }
  138. StickersListWidget::Set::Set(Set &&other) = default;
  139. StickersListWidget::Set &StickersListWidget::Set::operator=(
  140. Set &&other) = default;
  141. StickersListWidget::Set::~Set() = default;
  142. void StickersListWidget::Sticker::ensureMediaCreated() {
  143. if (documentMedia) {
  144. return;
  145. }
  146. documentMedia = document->createMediaView();
  147. }
  148. StickersListWidget::StickersListWidget(
  149. QWidget *parent,
  150. not_null<Window::SessionController*> controller,
  151. PauseReason level,
  152. Mode mode)
  153. : StickersListWidget(parent, {
  154. .show = controller->uiShow(),
  155. .mode = mode,
  156. .paused = Window::PausedIn(controller, level),
  157. }) {
  158. }
  159. StickersListWidget::StickersListWidget(
  160. QWidget *parent,
  161. StickersListDescriptor &&descriptor)
  162. : Inner(
  163. parent,
  164. descriptor.st ? *descriptor.st : st::defaultEmojiPan,
  165. descriptor.show,
  166. descriptor.paused)
  167. , _mode(descriptor.mode)
  168. , _show(std::move(descriptor.show))
  169. , _features(descriptor.features)
  170. , _overBg(st::roundRadiusLarge, st().overBg)
  171. , _api(&session().mtp())
  172. , _localSetsManager(std::make_unique<LocalStickersManager>(&session()))
  173. , _customRecentIds(std::move(descriptor.customRecentList))
  174. , _section(Section::Stickers)
  175. , _isMasks(_mode == Mode::Masks)
  176. , _isEffects(_mode == Mode::MessageEffects)
  177. , _updateItemsTimer([=] { updateItems(); })
  178. , _updateSetsTimer([=] { updateSets(); })
  179. , _trendingAddBgOver(
  180. ImageRoundRadius::Large,
  181. st::stickersTrendingAdd.textBgOver)
  182. , _trendingAddBg(ImageRoundRadius::Large, st::stickersTrendingAdd.textBg)
  183. , _inactiveButtonBg(
  184. ImageRoundRadius::Large,
  185. st::stickersTrendingInstalled.textBg)
  186. , _groupCategoryAddBgOver(
  187. ImageRoundRadius::Large,
  188. st::stickerGroupCategoryAdd.textBgOver)
  189. , _groupCategoryAddBg(
  190. ImageRoundRadius::Large,
  191. st::stickerGroupCategoryAdd.textBg)
  192. , _pathGradient(std::make_unique<Ui::PathShiftGradient>(
  193. st().pathBg,
  194. st().pathFg,
  195. [=] { update(); }))
  196. , _megagroupSetAbout(st::columnMinimalWidthThird
  197. - st::emojiScroll.width
  198. - st().headerLeft)
  199. , _addText(tr::lng_stickers_featured_add(tr::now))
  200. , _addWidth(st::stickersTrendingAdd.style.font->width(_addText))
  201. , _installedText(tr::lng_stickers_featured_installed(tr::now))
  202. , _installedWidth(
  203. st::stickersTrendingInstalled.style.font->width(_installedText))
  204. , _settings(this, tr::lng_stickers_you_have(tr::now))
  205. , _previewTimer([=] { showPreview(); })
  206. , _premiumMark(std::make_unique<StickerPremiumMark>(
  207. &session(),
  208. st::stickersPremiumLock))
  209. , _searchRequestTimer([=] { sendSearchRequest(); }) {
  210. setMouseTracking(true);
  211. if (st().bg->c.alpha() > 0) {
  212. setAttribute(Qt::WA_OpaquePaintEvent);
  213. }
  214. if (!_isMasks && !_isEffects) {
  215. setupSearch();
  216. }
  217. _settings->addClickHandler([=] {
  218. if (const auto window = _show->resolveWindow()) {
  219. // While media viewer can't show StickersBox.
  220. using Section = StickersBox::Section;
  221. window->show(
  222. Box<StickersBox>(_show, Section::Installed, _isMasks));
  223. Core::App().hideMediaView();
  224. Window::ActivateWindow(window);
  225. }
  226. });
  227. session().downloaderTaskFinished(
  228. ) | rpl::start_with_next([=] {
  229. if (isVisible()) {
  230. updateItems();
  231. readVisibleFeatured(getVisibleTop(), getVisibleBottom());
  232. }
  233. }, lifetime());
  234. session().changes().peerUpdates(
  235. Data::PeerUpdate::Flag::StickersSet
  236. ) | rpl::filter([=](const Data::PeerUpdate &update) {
  237. return (update.peer.get() == _megagroupSet);
  238. }) | rpl::start_with_next([=] {
  239. refreshStickers();
  240. }, lifetime());
  241. if (!_isEffects) {
  242. session().data().stickers().recentUpdated(_isMasks
  243. ? Data::StickersType::Masks
  244. : Data::StickersType::Stickers
  245. ) | rpl::start_with_next([=] {
  246. refreshRecent();
  247. }, lifetime());
  248. }
  249. positionValue(
  250. ) | rpl::skip(1) | rpl::map_to(
  251. TabbedSelector::Action::Update
  252. ) | rpl::start_to_stream(_choosingUpdated, lifetime());
  253. if (_isEffects) {
  254. refreshStickers();
  255. } else {
  256. rpl::merge(
  257. Data::AmPremiumValue(&session()) | rpl::to_empty,
  258. session().api().premium().cloudSetUpdated()
  259. ) | rpl::start_with_next([=] {
  260. refreshStickers();
  261. }, lifetime());
  262. }
  263. }
  264. rpl::producer<FileChosen> StickersListWidget::chosen() const {
  265. return _chosen.events();
  266. }
  267. rpl::producer<> StickersListWidget::scrollUpdated() const {
  268. return _scrollUpdated.events();
  269. }
  270. auto StickersListWidget::choosingUpdated() const
  271. -> rpl::producer<TabbedSelector::Action> {
  272. return _choosingUpdated.events();
  273. }
  274. object_ptr<TabbedSelector::InnerFooter> StickersListWidget::createFooter() {
  275. Expects(_footer == nullptr);
  276. const auto footerPaused = [method = pausedMethod()] {
  277. return On(PowerSaving::kStickersPanel) || method();
  278. };
  279. using FooterDescriptor = StickersListFooter::Descriptor;
  280. auto result = object_ptr<StickersListFooter>(FooterDescriptor{
  281. .session = &session(),
  282. .paused = footerPaused,
  283. .parent = this,
  284. .st = &st(),
  285. .features = _features,
  286. });
  287. _footer = result;
  288. _footer->setChosen(
  289. ) | rpl::start_with_next([=](uint64 setId) {
  290. showStickerSet(setId);
  291. }, _footer->lifetime());
  292. _footer->openSettingsRequests(
  293. ) | rpl::start_with_next([=] {
  294. const auto onlyFeatured = !_isMasks && _mySets.empty();
  295. _show->showBox(Box<StickersBox>(
  296. _show,
  297. (onlyFeatured
  298. ? StickersBox::Section::Featured
  299. : _isMasks
  300. ? StickersBox::Section::Masks
  301. : StickersBox::Section::Installed),
  302. onlyFeatured ? false : _isMasks));
  303. }, _footer->lifetime());
  304. return result;
  305. }
  306. void StickersListWidget::visibleTopBottomUpdated(
  307. int visibleTop,
  308. int visibleBottom) {
  309. const auto top = getVisibleTop();
  310. Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
  311. if (top != getVisibleTop()) {
  312. _lastScrolledAt = crl::now();
  313. _repaintSetsIds.clear();
  314. update();
  315. }
  316. if (_section == Section::Featured) {
  317. checkVisibleFeatured(visibleTop, visibleBottom);
  318. } else {
  319. checkVisibleLottie();
  320. }
  321. if (_footer) {
  322. _footer->validateSelectedIcon(
  323. currentSet(visibleTop),
  324. ValidateIconAnimations::Full);
  325. }
  326. }
  327. void StickersListWidget::checkVisibleFeatured(
  328. int visibleTop,
  329. int visibleBottom) {
  330. readVisibleFeatured(visibleTop, visibleBottom);
  331. const auto visibleHeight = visibleBottom - visibleTop;
  332. if (visibleBottom > height() - visibleHeight * kPreloadOfficialPages) {
  333. preloadMoreOfficial();
  334. }
  335. const auto rowHeight = featuredRowHeight();
  336. const auto destroyAbove = floorclamp(visibleTop - visibleHeight, rowHeight, 0, _officialSets.size());
  337. const auto destroyBelow = ceilclamp(visibleBottom + visibleHeight, rowHeight, 0, _officialSets.size());
  338. for (auto i = 0; i != destroyAbove; ++i) {
  339. clearHeavyIn(_officialSets[i]);
  340. }
  341. for (auto i = destroyBelow; i != _officialSets.size(); ++i) {
  342. clearHeavyIn(_officialSets[i]);
  343. }
  344. }
  345. void StickersListWidget::preloadMoreOfficial() {
  346. if (_officialRequestId) {
  347. return;
  348. }
  349. _officialRequestId = _api.request(MTPmessages_GetOldFeaturedStickers(
  350. MTP_int(_officialOffset),
  351. MTP_int(kOfficialLoadLimit),
  352. MTP_long(0) // hash
  353. )).done([=](const MTPmessages_FeaturedStickers &result) {
  354. _officialRequestId = 0;
  355. result.match([&](const MTPDmessages_featuredStickersNotModified &d) {
  356. LOG(("Api Error: messages.featuredStickersNotModified."));
  357. }, [&](const MTPDmessages_featuredStickers &data) {
  358. const auto &list = data.vsets().v;
  359. _officialOffset += list.size();
  360. for (int i = 0, l = list.size(); i != l; ++i) {
  361. const auto set = session().data().stickers().feedSet(
  362. list[i]);
  363. if (set->stickers.empty() && set->covers.empty()) {
  364. continue;
  365. }
  366. const auto externalLayout = true;
  367. appendSet(
  368. _officialSets,
  369. set->id,
  370. externalLayout,
  371. AppendSkip::Installed);
  372. }
  373. });
  374. resizeToWidth(width());
  375. repaintItems();
  376. }).send();
  377. }
  378. void StickersListWidget::readVisibleFeatured(
  379. int visibleTop,
  380. int visibleBottom) {
  381. const auto rowHeight = featuredRowHeight();
  382. const auto rowFrom = floorclamp(visibleTop, rowHeight, 0, _featuredSetsCount);
  383. const auto rowTo = ceilclamp(visibleBottom, rowHeight, 0, _featuredSetsCount);
  384. for (auto i = rowFrom; i < rowTo; ++i) {
  385. auto &set = _officialSets[i];
  386. if (!(set.flags & SetFlag::Unread)) {
  387. continue;
  388. }
  389. if (i * rowHeight < visibleTop || (i + 1) * rowHeight > visibleBottom) {
  390. continue;
  391. }
  392. int count = qMin(int(set.stickers.size()), _columnCount);
  393. int loaded = 0;
  394. for (int j = 0; j < count; ++j) {
  395. if (!set.stickers[j].document->hasThumbnail()
  396. || !set.stickers[j].document->thumbnailLoading()
  397. || (set.stickers[j].documentMedia
  398. && set.stickers[j].documentMedia->loaded())) {
  399. ++loaded;
  400. }
  401. }
  402. if (count > 0 && loaded == count) {
  403. session().api().readFeaturedSetDelayed(set.id);
  404. }
  405. }
  406. }
  407. int StickersListWidget::featuredRowHeight() const {
  408. return st::stickersTrendingHeader
  409. + _singleSize.height()
  410. + st::stickersTrendingSkip;
  411. }
  412. template <typename Callback>
  413. bool StickersListWidget::enumerateSections(Callback callback) const {
  414. auto info = SectionInfo();
  415. info.top = _search ? _search->height() : 0;
  416. const auto &sets = shownSets();
  417. for (auto i = 0; i != sets.size(); ++i) {
  418. auto &set = sets[i];
  419. info.section = i;
  420. info.count = set.stickers.size();
  421. const auto titleSkip = set.externalLayout
  422. ? st::stickersTrendingHeader
  423. : setHasTitle(set)
  424. ? st().header
  425. : st::stickerPanPadding;
  426. info.rowsTop = info.top + titleSkip;
  427. if (set.externalLayout) {
  428. info.rowsCount = 1;
  429. info.rowsBottom = info.top + featuredRowHeight();
  430. } else if (set.id == Data::Stickers::MegagroupSetId && !info.count) {
  431. info.rowsCount = 0;
  432. info.rowsBottom = info.rowsTop + _megagroupSetButtonRect.y() + _megagroupSetButtonRect.height() + st::stickerGroupCategoryAddMargin.bottom();
  433. } else {
  434. info.rowsCount = (info.count / _columnCount) + ((info.count % _columnCount) ? 1 : 0);
  435. info.rowsBottom = info.rowsTop + info.rowsCount * _singleSize.height();
  436. }
  437. if (!callback(info)) {
  438. return false;
  439. }
  440. info.top = info.rowsBottom;
  441. }
  442. return true;
  443. }
  444. StickersListWidget::SectionInfo StickersListWidget::sectionInfo(int section) const {
  445. Expects(section >= 0 && section < shownSets().size());
  446. auto result = SectionInfo();
  447. enumerateSections([searchForSection = section, &result](const SectionInfo &info) {
  448. if (info.section == searchForSection) {
  449. result = info;
  450. return false;
  451. }
  452. return true;
  453. });
  454. return result;
  455. }
  456. StickersListWidget::SectionInfo StickersListWidget::sectionInfoByOffset(int yOffset) const {
  457. auto result = SectionInfo();
  458. enumerateSections([this, &result, yOffset](const SectionInfo &info) {
  459. if (yOffset < info.rowsBottom || info.section == shownSets().size() - 1) {
  460. result = info;
  461. return false;
  462. }
  463. return true;
  464. });
  465. return result;
  466. }
  467. int StickersListWidget::countDesiredHeight(int newWidth) {
  468. const auto minSize = _isEffects
  469. ? st::stickerEffectWidthMin
  470. : st::stickerPanWidthMin;
  471. if (newWidth < 2 * minSize) {
  472. return 0;
  473. }
  474. auto availableWidth = newWidth - (st::stickerPanPadding - st().margin.left());
  475. auto columnCount = availableWidth / minSize;
  476. auto singleWidth = availableWidth / columnCount;
  477. auto fullWidth = (st().margin.left() + newWidth + st::emojiScroll.width);
  478. auto rowsRight = (fullWidth - columnCount * singleWidth) / 2;
  479. accumulate_max(rowsRight, st::emojiScroll.width);
  480. _rowsLeft = fullWidth
  481. - columnCount * singleWidth
  482. - rowsRight
  483. - st().margin.left();
  484. _singleSize = QSize(singleWidth, singleWidth);
  485. setColumnCount(columnCount);
  486. auto visibleHeight = minimalHeight();
  487. auto minimalHeight = (visibleHeight - st::stickerPanPadding);
  488. auto countResult = [this](int minimalLastHeight) {
  489. const auto &sets = shownSets();
  490. if (sets.empty()) {
  491. return 0;
  492. }
  493. const auto info = sectionInfo(sets.size() - 1);
  494. return info.top
  495. + qMax(info.rowsBottom - info.top, minimalLastHeight);
  496. };
  497. const auto minimalLastHeight = (_section == Section::Stickers)
  498. ? minimalHeight
  499. : 0;
  500. const auto result = qMax(minimalHeight, countResult(minimalLastHeight));
  501. return result ? (result + st::stickerPanPadding) : 0;
  502. }
  503. void StickersListWidget::sendSearchRequest() {
  504. if (_searchRequestId || _searchNextQuery.isEmpty() || _isEffects) {
  505. return;
  506. }
  507. _searchRequestTimer.cancel();
  508. _searchQuery = _searchNextQuery;
  509. auto it = _searchCache.find(_searchQuery);
  510. if (it != _searchCache.cend()) {
  511. toggleSearchLoading(false);
  512. return;
  513. }
  514. toggleSearchLoading(true);
  515. if (_searchQuery == Ui::PremiumGroupFakeEmoticon()) {
  516. toggleSearchLoading(false);
  517. _searchRequestId = 0;
  518. _searchCache.emplace(_searchQuery, std::vector<uint64>());
  519. showSearchResults();
  520. return;
  521. }
  522. const auto hash = uint64(0);
  523. _searchRequestId = _api.request(MTPmessages_SearchStickerSets(
  524. MTP_flags(0),
  525. MTP_string(_searchQuery),
  526. MTP_long(hash)
  527. )).done([=](const MTPmessages_FoundStickerSets &result) {
  528. searchResultsDone(result);
  529. }).fail([=] {
  530. // show error?
  531. toggleSearchLoading(false);
  532. _searchRequestId = 0;
  533. }).handleAllErrors().send();
  534. }
  535. void StickersListWidget::searchForSets(
  536. const QString &query,
  537. std::vector<EmojiPtr> emoji) {
  538. const auto cleaned = query.trimmed();
  539. if (cleaned.isEmpty()) {
  540. cancelSetsSearch();
  541. return;
  542. }
  543. _filterStickersCornerEmoji.clear();
  544. if (_isEffects) {
  545. filterEffectsByEmoji(std::move(emoji));
  546. } else if (query == Ui::PremiumGroupFakeEmoticon()) {
  547. _filteredStickers = session().data().stickers().getPremiumList(0);
  548. } else {
  549. _filteredStickers = session().data().stickers().getListByEmoji(
  550. std::move(emoji),
  551. 0,
  552. true);
  553. }
  554. if (_searchQuery != cleaned) {
  555. toggleSearchLoading(false);
  556. if (const auto requestId = base::take(_searchRequestId)) {
  557. _api.request(requestId).cancel();
  558. }
  559. if (_searchCache.find(cleaned) != _searchCache.cend()) {
  560. _searchRequestTimer.cancel();
  561. _searchQuery = _searchNextQuery = cleaned;
  562. } else {
  563. _searchNextQuery = cleaned;
  564. _searchRequestTimer.callOnce(kSearchRequestDelay);
  565. }
  566. showSearchResults();
  567. }
  568. }
  569. void StickersListWidget::cancelSetsSearch() {
  570. toggleSearchLoading(false);
  571. if (const auto requestId = base::take(_searchRequestId)) {
  572. _api.request(requestId).cancel();
  573. }
  574. _searchRequestTimer.cancel();
  575. _searchQuery = _searchNextQuery = QString();
  576. _filteredStickers.clear();
  577. _filterStickersCornerEmoji.clear();
  578. _searchCache.clear();
  579. refreshSearchRows(nullptr);
  580. }
  581. void StickersListWidget::showSearchResults() {
  582. refreshSearchRows();
  583. scrollTo(0);
  584. }
  585. void StickersListWidget::refreshSearchRows() {
  586. auto it = _searchCache.find(_searchQuery);
  587. auto sets = (it != end(_searchCache))
  588. ? &it->second
  589. : nullptr;
  590. refreshSearchRows(sets);
  591. }
  592. void StickersListWidget::refreshSearchRows(
  593. const std::vector<uint64> *cloudSets) {
  594. clearSelection();
  595. const auto wasSection = _section;
  596. auto wasSets = base::take(_searchSets);
  597. const auto guard = gsl::finally([&] {
  598. if (_section == wasSection && _section == Section::Search) {
  599. takeHeavyData(_searchSets, wasSets);
  600. }
  601. });
  602. fillFilteredStickersRow();
  603. if (!_isEffects) {
  604. fillLocalSearchRows(_searchNextQuery);
  605. }
  606. if (!cloudSets && _searchNextQuery.isEmpty()) {
  607. showStickerSet(!_mySets.empty()
  608. ? _mySets[0].id
  609. : Data::Stickers::FeaturedSetId);
  610. return;
  611. }
  612. setSection(Section::Search);
  613. if (!_isEffects && cloudSets) {
  614. fillCloudSearchRows(*cloudSets);
  615. }
  616. refreshIcons(ValidateIconAnimations::Scroll);
  617. _lastMousePosition = QCursor::pos();
  618. resizeToWidth(width());
  619. _recentShownCount = _filteredStickers.size();
  620. updateSelected();
  621. }
  622. rpl::producer<int> StickersListWidget::recentShownCount() const {
  623. return _recentShownCount.value();
  624. }
  625. void StickersListWidget::fillLocalSearchRows(const QString &query) {
  626. const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
  627. if (searchWordsList.isEmpty()) {
  628. return;
  629. }
  630. auto searchWordInTitle = [](
  631. const QStringList &titleWords,
  632. const QString &searchWord) {
  633. for (const auto &titleWord : titleWords) {
  634. if (titleWord.startsWith(searchWord)) {
  635. return true;
  636. }
  637. }
  638. return false;
  639. };
  640. auto allSearchWordsInTitle = [&](
  641. const QStringList &titleWords) {
  642. for (const auto &searchWord : searchWordsList) {
  643. if (!searchWordInTitle(titleWords, searchWord)) {
  644. return false;
  645. }
  646. }
  647. return true;
  648. };
  649. const auto &sets = session().data().stickers().sets();
  650. for (const auto &[setId, titleWords] : _searchIndex) {
  651. if (allSearchWordsInTitle(titleWords)) {
  652. if (const auto it = sets.find(setId); it != sets.end()) {
  653. addSearchRow(it->second.get());
  654. }
  655. }
  656. }
  657. }
  658. void StickersListWidget::fillCloudSearchRows(
  659. const std::vector<uint64> &cloudSets) {
  660. const auto &sets = session().data().stickers().sets();
  661. for (const auto setId : cloudSets) {
  662. if (const auto it = sets.find(setId); it != sets.end()) {
  663. addSearchRow(it->second.get());
  664. }
  665. }
  666. }
  667. void StickersListWidget::fillFilteredStickersRow() {
  668. if (_filteredStickers.empty()) {
  669. return;
  670. }
  671. auto elements = ranges::views::all(
  672. _filteredStickers
  673. ) | ranges::views::transform([](not_null<DocumentData*> document) {
  674. return Sticker{ document };
  675. }) | ranges::to_vector;
  676. _searchSets.emplace_back(
  677. SearchEmojiSectionSetId(),
  678. nullptr,
  679. Data::StickersSetFlag::Special,
  680. _isEffects ? tr::lng_effect_stickers_title(tr::now) : QString(),
  681. QString(), // shortName
  682. _filteredStickers.size(),
  683. false, // externalLayout
  684. std::move(elements));
  685. }
  686. void StickersListWidget::addSearchRow(not_null<StickersSet*> set) {
  687. const auto skipPremium = !session().premiumPossible();
  688. auto elements = PrepareStickers(
  689. set->stickers.empty() ? set->covers : set->stickers,
  690. skipPremium);
  691. _searchSets.emplace_back(
  692. set->id,
  693. set,
  694. set->flags,
  695. set->title,
  696. set->shortName,
  697. set->count,
  698. !SetInMyList(set->flags),
  699. std::move(elements));
  700. }
  701. void StickersListWidget::toggleSearchLoading(bool loading) {
  702. if (_search) {
  703. _search->setLoading(loading);
  704. }
  705. }
  706. void StickersListWidget::takeHeavyData(
  707. std::vector<Set> &to,
  708. std::vector<Set> &from) {
  709. auto indices = base::flat_map<uint64, int>();
  710. indices.reserve(from.size());
  711. auto index = 0;
  712. for (const auto &set : from) {
  713. indices.emplace(set.id, index++);
  714. }
  715. for (auto &toSet : to) {
  716. const auto i = indices.find(toSet.id);
  717. if (i != end(indices)) {
  718. takeHeavyData(toSet, from[i->second]);
  719. }
  720. }
  721. }
  722. void StickersListWidget::takeHeavyData(Set &to, Set &from) {
  723. to.lottiePlayer = std::move(from.lottiePlayer);
  724. to.lottieLifetime = std::move(from.lottieLifetime);
  725. auto &toList = to.stickers;
  726. auto &fromList = from.stickers;
  727. const auto same = ranges::equal(
  728. toList,
  729. fromList,
  730. ranges::equal_to(),
  731. &Sticker::document,
  732. &Sticker::document);
  733. if (same) {
  734. for (auto i = 0, count = int(toList.size()); i != count; ++i) {
  735. takeHeavyData(toList[i], fromList[i]);
  736. }
  737. } else {
  738. auto indices = base::flat_map<not_null<DocumentData*>, int>();
  739. indices.reserve(fromList.size());
  740. auto index = 0;
  741. for (const auto &fromSticker : fromList) {
  742. indices.emplace(fromSticker.document, index++);
  743. }
  744. for (auto &toSticker : toList) {
  745. const auto i = indices.find(toSticker.document);
  746. if (i != end(indices)) {
  747. takeHeavyData(toSticker, fromList[i->second]);
  748. }
  749. }
  750. for (const auto &sticker : fromList) {
  751. if (sticker.lottie) {
  752. to.lottiePlayer->remove(sticker.lottie);
  753. }
  754. }
  755. }
  756. }
  757. void StickersListWidget::takeHeavyData(Sticker &to, Sticker &from) {
  758. to.documentMedia = std::move(from.documentMedia);
  759. to.savedFrame = std::move(from.savedFrame);
  760. to.savedFrameFor = from.savedFrameFor;
  761. to.lottie = base::take(from.lottie);
  762. to.webm = base::take(from.webm);
  763. }
  764. auto StickersListWidget::shownSets() const -> const std::vector<Set> & {
  765. switch (_section) {
  766. case Section::Featured: return _officialSets;
  767. case Section::Search: return _searchSets;
  768. case Section::Stickers: return _mySets;
  769. }
  770. Unexpected("Section in StickersListWidget.");
  771. }
  772. auto StickersListWidget::shownSets() -> std::vector<Set> & {
  773. switch (_section) {
  774. case Section::Featured: return _officialSets;
  775. case Section::Search: return _searchSets;
  776. case Section::Stickers: return _mySets;
  777. }
  778. Unexpected("Section in StickersListWidget.");
  779. }
  780. void StickersListWidget::searchResultsDone(
  781. const MTPmessages_FoundStickerSets &result) {
  782. toggleSearchLoading(false);
  783. _searchRequestId = 0;
  784. if (result.type() == mtpc_messages_foundStickerSetsNotModified) {
  785. LOG(("API Error: "
  786. "messages.foundStickerSetsNotModified not expected."));
  787. return;
  788. }
  789. Assert(result.type() == mtpc_messages_foundStickerSets);
  790. auto it = _searchCache.find(_searchQuery);
  791. if (it == _searchCache.cend()) {
  792. it = _searchCache.emplace(
  793. _searchQuery,
  794. std::vector<uint64>()).first;
  795. }
  796. auto &d = result.c_messages_foundStickerSets();
  797. for (const auto &data : d.vsets().v) {
  798. const auto set = session().data().stickers().feedSet(data);
  799. if (set->stickers.empty() && set->covers.empty()) {
  800. continue;
  801. }
  802. it->second.push_back(set->id);
  803. }
  804. showSearchResults();
  805. }
  806. int StickersListWidget::stickersLeft() const {
  807. return _rowsLeft;
  808. }
  809. QRect StickersListWidget::stickerRect(int section, int sel) {
  810. auto info = sectionInfo(section);
  811. if (sel >= shownSets()[section].stickers.size()) {
  812. sel -= shownSets()[section].stickers.size();
  813. }
  814. auto countTillItem = (sel - (sel % _columnCount));
  815. auto rowsToSkip = (countTillItem / _columnCount) + ((countTillItem % _columnCount) ? 1 : 0);
  816. auto x = stickersLeft() + ((sel % _columnCount) * _singleSize.width());
  817. auto y = info.rowsTop + rowsToSkip * _singleSize.height();
  818. return QRect(QPoint(x, y), _singleSize);
  819. }
  820. void StickersListWidget::paintEvent(QPaintEvent *e) {
  821. Painter p(this);
  822. auto clip = e->rect();
  823. if (st().bg->c.alpha() > 0) {
  824. p.fillRect(clip, st().bg);
  825. }
  826. paintStickers(p, clip);
  827. }
  828. void StickersListWidget::paintStickers(Painter &p, QRect clip) {
  829. auto fromColumn = floorclamp(clip.x() - stickersLeft(), _singleSize.width(), 0, _columnCount);
  830. auto toColumn = ceilclamp(clip.x() + clip.width() - stickersLeft(), _singleSize.width(), 0, _columnCount);
  831. if (rtl()) {
  832. qSwap(fromColumn, toColumn);
  833. fromColumn = _columnCount - fromColumn;
  834. toColumn = _columnCount - toColumn;
  835. }
  836. _paintAsPremium = session().premium();
  837. _pathGradient->startFrame(0, width(), width() / 2);
  838. auto &sets = shownSets();
  839. auto selectedSticker = std::get_if<OverSticker>(&_selected);
  840. auto selectedButton = std::get_if<OverButton>(!v::is_null(_pressed)
  841. ? &_pressed
  842. : &_selected);
  843. const auto now = crl::now();
  844. const auto paused = On(PowerSaving::kStickersPanel)
  845. || this->paused();
  846. if (sets.empty() && _section == Section::Search) {
  847. paintEmptySearchResults(p);
  848. }
  849. const auto badgeText = tr::lng_stickers_creator_badge(tr::now);
  850. const auto &badgeFont = st::stickersHeaderBadgeFont;
  851. const auto badgeWidth = badgeFont->width(badgeText);
  852. enumerateSections([&](const SectionInfo &info) {
  853. if (clip.top() >= info.rowsBottom) {
  854. return true;
  855. } else if (clip.top() + clip.height() <= info.top) {
  856. return false;
  857. }
  858. auto &set = sets[info.section];
  859. if (set.externalLayout) {
  860. const auto loadedCount = int(set.stickers.size());
  861. const auto count = (set.flags & SetFlag::NotLoaded)
  862. ? set.count
  863. : loadedCount;
  864. auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left());
  865. {
  866. const auto installedSet = !featuredHasAddButton(info.section);
  867. const auto add = featuredAddRect(info, installedSet);
  868. const auto selected = selectedButton
  869. ? (selectedButton->section == info.section)
  870. : false;
  871. (installedSet
  872. ? _inactiveButtonBg
  873. : selected
  874. ? _trendingAddBgOver
  875. : _trendingAddBg).paint(p, myrtlrect(add));
  876. if (set.ripple) {
  877. set.ripple->paint(p, add.x(), add.y(), width());
  878. if (set.ripple->empty()) {
  879. set.ripple.reset();
  880. }
  881. }
  882. const auto &text = installedSet ? _installedText : _addText;
  883. const auto textWidth = installedSet
  884. ? _installedWidth
  885. : _addWidth;
  886. const auto &st = installedSet
  887. ? st::stickersTrendingInstalled
  888. : st::stickersTrendingAdd;
  889. p.setFont(st.style.font);
  890. p.setPen(selected ? st.textFgOver : st.textFg);
  891. p.drawTextLeft(
  892. add.x() - (st.width / 2),
  893. add.y() + st.textTop,
  894. width(),
  895. text,
  896. textWidth);
  897. widthForTitle -= add.width() - (st.width / 2);
  898. }
  899. if (set.flags & SetFlag::Unread) {
  900. widthForTitle -= st::stickersFeaturedUnreadSize + st::stickersFeaturedUnreadSkip;
  901. }
  902. auto titleText = set.title;
  903. auto titleWidth = st::stickersTrendingHeaderFont->width(titleText);
  904. if (titleWidth > widthForTitle) {
  905. titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle);
  906. titleWidth = st::stickersTrendingHeaderFont->width(titleText);
  907. }
  908. p.setFont(st::stickersTrendingHeaderFont);
  909. p.setPen(st().trendingHeaderFg);
  910. p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st::stickersTrendingHeaderTop, width(), titleText, titleWidth);
  911. if (set.flags & SetFlag::Unread) {
  912. p.setPen(Qt::NoPen);
  913. p.setBrush(st().trendingUnreadFg);
  914. {
  915. PainterHighQualityEnabler hq(p);
  916. p.drawEllipse(style::rtlrect(st().headerLeft - st().margin.left() + titleWidth + st::stickersFeaturedUnreadSkip, info.top + st::stickersTrendingHeaderTop + st::stickersFeaturedUnreadTop, st::stickersFeaturedUnreadSize, st::stickersFeaturedUnreadSize, width()));
  917. }
  918. }
  919. auto statusText = (count > 0) ? tr::lng_stickers_count(tr::now, lt_count, count) : tr::lng_contacts_loading(tr::now);
  920. p.setFont(st::stickersTrendingSubheaderFont);
  921. p.setPen(st().trendingSubheaderFg);
  922. p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st::stickersTrendingSubheaderTop, width(), statusText);
  923. if (info.rowsTop >= clip.y() + clip.height()) {
  924. return true;
  925. }
  926. for (int j = fromColumn; j < toColumn; ++j) {
  927. int index = j;
  928. if (index >= loadedCount) break;
  929. auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
  930. auto deleteSelected = false;
  931. paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected);
  932. }
  933. if (!paused) {
  934. markLottieFrameShown(set);
  935. }
  936. return true;
  937. }
  938. if (setHasTitle(set) && clip.top() < info.rowsTop) {
  939. auto titleText = set.title;
  940. auto titleWidth = st::stickersTrendingHeaderFont->width(titleText);
  941. auto widthForTitle = stickersRight() - (st().headerLeft - st().margin.left());
  942. if (hasRemoveButton(info.section)) {
  943. auto remove = removeButtonRect(info);
  944. auto selected = selectedButton ? (selectedButton->section == info.section) : false;
  945. const auto &removeSt = st().removeSet;
  946. if (set.ripple) {
  947. set.ripple->paint(p, remove.x() + removeSt.rippleAreaPosition.x(), remove.y() + removeSt.rippleAreaPosition.y(), width());
  948. if (set.ripple->empty()) {
  949. set.ripple.reset();
  950. }
  951. }
  952. const auto &icon = selected ? removeSt.iconOver : removeSt.icon;
  953. icon.paint(
  954. p,
  955. remove.x() + (remove.width() - icon.width()) / 2,
  956. remove.y() + (remove.height() - icon.height()) / 2,
  957. width());
  958. widthForTitle -= remove.width();
  959. }
  960. const auto amCreator = (set.flags & Data::StickersSetFlag::AmCreator);
  961. if (amCreator) {
  962. widthForTitle -= badgeWidth
  963. + st::stickersFeaturedUnreadSkip
  964. + st::stickersHeaderBadgeFontSkip;
  965. }
  966. if (titleWidth > widthForTitle) {
  967. titleText = st::stickersTrendingHeaderFont->elided(titleText, widthForTitle);
  968. titleWidth = st::stickersTrendingHeaderFont->width(titleText);
  969. }
  970. p.setFont(st::emojiPanHeaderFont);
  971. p.setPen(st().headerFg);
  972. p.drawTextLeft(st().headerLeft - st().margin.left(), info.top + st().headerTop, width(), titleText, titleWidth);
  973. if (amCreator) {
  974. const auto badgeLeft = st().headerLeft
  975. - st().margin.left()
  976. + titleWidth
  977. + st::stickersFeaturedUnreadSkip;
  978. {
  979. auto color = st().headerFg->c;
  980. color.setAlphaF(st().headerFg->c.alphaF() * 0.15);
  981. p.setPen(Qt::NoPen);
  982. p.setBrush(color);
  983. auto hq = PainterHighQualityEnabler(p);
  984. p.drawRoundedRect(
  985. style::rtlrect(
  986. badgeLeft,
  987. info.top + st::stickersHeaderBadgeFontTop,
  988. badgeWidth + badgeFont->height,
  989. badgeFont->height,
  990. width()),
  991. badgeFont->height / 2.,
  992. badgeFont->height / 2.);
  993. }
  994. p.setPen(st().headerFg);
  995. p.setBrush(Qt::NoBrush);
  996. p.setFont(badgeFont);
  997. p.drawText(
  998. QRect(
  999. badgeLeft + badgeFont->height / 2,
  1000. info.top + st::stickersHeaderBadgeFontTop,
  1001. badgeWidth,
  1002. badgeFont->height),
  1003. badgeText,
  1004. style::al_center);
  1005. }
  1006. }
  1007. if (clip.top() + clip.height() <= info.rowsTop) {
  1008. return true;
  1009. } else if (set.id == Data::Stickers::MegagroupSetId && set.stickers.empty()) {
  1010. auto buttonSelected = (std::get_if<OverGroupAdd>(&_selected) != nullptr);
  1011. paintMegagroupEmptySet(p, info.rowsTop, buttonSelected);
  1012. return true;
  1013. }
  1014. auto fromRow = floorclamp(clip.y() - info.rowsTop, _singleSize.height(), 0, info.rowsCount);
  1015. auto toRow = ceilclamp(clip.y() + clip.height() - info.rowsTop, _singleSize.height(), 0, info.rowsCount);
  1016. for (int i = fromRow; i < toRow; ++i) {
  1017. for (int j = fromColumn; j < toColumn; ++j) {
  1018. int index = i * _columnCount + j;
  1019. if (index >= info.count) break;
  1020. auto selected = selectedSticker ? (selectedSticker->section == info.section && selectedSticker->index == index) : false;
  1021. auto deleteSelected = selected && selectedSticker->overDelete;
  1022. paintSticker(p, set, info.rowsTop, info.section, index, now, paused, selected, deleteSelected);
  1023. }
  1024. }
  1025. if (!paused) {
  1026. markLottieFrameShown(set);
  1027. }
  1028. return true;
  1029. });
  1030. }
  1031. void StickersListWidget::markLottieFrameShown(Set &set) {
  1032. if (const auto player = set.lottiePlayer.get()) {
  1033. player->markFrameShown();
  1034. }
  1035. }
  1036. void StickersListWidget::checkVisibleLottie() {
  1037. if (shownSets().empty()) {
  1038. return;
  1039. }
  1040. const auto visibleTop = getVisibleTop();
  1041. const auto visibleBottom = getVisibleBottom();
  1042. const auto destroyAfterDistance = (visibleBottom - visibleTop) * 2;
  1043. const auto destroyAbove = visibleTop - destroyAfterDistance;
  1044. const auto destroyBelow = visibleBottom + destroyAfterDistance;
  1045. enumerateSections([&](const SectionInfo &info) {
  1046. if (destroyBelow <= info.rowsTop
  1047. || destroyAbove >= info.rowsBottom) {
  1048. clearHeavyIn(shownSets()[info.section]);
  1049. } else if ((visibleTop > info.rowsTop && visibleTop < info.rowsBottom)
  1050. || (visibleBottom > info.rowsTop
  1051. && visibleBottom < info.rowsBottom)) {
  1052. pauseInvisibleLottieIn(info);
  1053. }
  1054. return true;
  1055. });
  1056. }
  1057. void StickersListWidget::clearHeavyIn(Set &set, bool clearSavedFrames) {
  1058. const auto player = base::take(set.lottiePlayer);
  1059. const auto lifetime = base::take(set.lottieLifetime);
  1060. for (auto &sticker : set.stickers) {
  1061. if (clearSavedFrames) {
  1062. sticker.savedFrame = QImage();
  1063. sticker.savedFrameFor = QSize();
  1064. }
  1065. sticker.webm = nullptr;
  1066. sticker.lottie = nullptr;
  1067. sticker.documentMedia = nullptr;
  1068. }
  1069. }
  1070. void StickersListWidget::pauseInvisibleLottieIn(const SectionInfo &info) {
  1071. auto &set = shownSets()[info.section];
  1072. const auto player = set.lottiePlayer.get();
  1073. if (!player) {
  1074. return;
  1075. }
  1076. const auto pauseInRows = [&](int fromRow, int tillRow) {
  1077. Expects(fromRow <= tillRow);
  1078. for (auto i = fromRow; i != tillRow; ++i) {
  1079. for (auto j = 0; j != _columnCount; ++j) {
  1080. const auto index = i * _columnCount + j;
  1081. if (index >= info.count) {
  1082. break;
  1083. }
  1084. if (const auto animated = set.stickers[index].lottie) {
  1085. player->pause(animated);
  1086. }
  1087. }
  1088. }
  1089. };
  1090. const auto visibleTop = getVisibleTop();
  1091. const auto visibleBottom = getVisibleBottom();
  1092. if (visibleTop >= info.rowsTop + _singleSize.height()
  1093. && visibleTop < info.rowsBottom) {
  1094. const auto pauseHeight = (visibleTop - info.rowsTop);
  1095. const auto pauseRows = std::min(
  1096. pauseHeight / _singleSize.height(),
  1097. info.rowsCount);
  1098. pauseInRows(0, pauseRows);
  1099. }
  1100. if (visibleBottom > info.rowsTop
  1101. && visibleBottom + _singleSize.height() <= info.rowsBottom) {
  1102. const auto pauseHeight = (info.rowsBottom - visibleBottom);
  1103. const auto pauseRows = std::min(
  1104. pauseHeight / _singleSize.height(),
  1105. info.rowsCount);
  1106. pauseInRows(info.rowsCount - pauseRows, info.rowsCount);
  1107. }
  1108. }
  1109. void StickersListWidget::paintEmptySearchResults(Painter &p) {
  1110. Inner::paintEmptySearchResults(
  1111. p,
  1112. st::stickersEmpty,
  1113. tr::lng_stickers_nothing_found(tr::now));
  1114. }
  1115. int StickersListWidget::megagroupSetInfoLeft() const {
  1116. return st().headerLeft - st().margin.left();
  1117. }
  1118. void StickersListWidget::paintMegagroupEmptySet(Painter &p, int y, bool buttonSelected) {
  1119. p.setPen(st().headerFg);
  1120. auto infoLeft = megagroupSetInfoLeft();
  1121. _megagroupSetAbout.drawLeft(p, infoLeft, y, width() - infoLeft, width());
  1122. auto button = _megagroupSetButtonRect.translated(0, y);
  1123. (buttonSelected ? _groupCategoryAddBgOver : _groupCategoryAddBg).paint(
  1124. p,
  1125. myrtlrect(button));
  1126. if (_megagroupSetButtonRipple) {
  1127. _megagroupSetButtonRipple->paint(p, button.x(), button.y(), width());
  1128. if (_megagroupSetButtonRipple->empty()) {
  1129. _megagroupSetButtonRipple.reset();
  1130. }
  1131. }
  1132. p.setFont(st::stickerGroupCategoryAdd.style.font);
  1133. p.setPen(buttonSelected ? st::stickerGroupCategoryAdd.textFgOver : st::stickerGroupCategoryAdd.textFg);
  1134. p.drawTextLeft(button.x() - (st::stickerGroupCategoryAdd.width / 2), button.y() + st::stickerGroupCategoryAdd.textTop, width(), _megagroupSetButtonText, _megagroupSetButtonTextWidth);
  1135. }
  1136. void StickersListWidget::ensureLottiePlayer(Set &set) {
  1137. if (set.lottiePlayer) {
  1138. return;
  1139. }
  1140. set.lottiePlayer = std::make_unique<Lottie::MultiPlayer>(
  1141. Lottie::Quality::Default,
  1142. getLottieRenderer());
  1143. const auto raw = set.lottiePlayer.get();
  1144. raw->updates(
  1145. ) | rpl::start_with_next([=] {
  1146. auto &sets = shownSets();
  1147. enumerateSections([&](const SectionInfo &info) {
  1148. if (sets[info.section].lottiePlayer.get() != raw) {
  1149. return true;
  1150. }
  1151. updateSet(info);
  1152. return false;
  1153. });
  1154. }, set.lottieLifetime);
  1155. }
  1156. void StickersListWidget::setupLottie(Set &set, int section, int index) {
  1157. auto &sticker = set.stickers[index];
  1158. ensureLottiePlayer(set);
  1159. // Document should be loaded already for the animation to be set up.
  1160. Assert(sticker.documentMedia != nullptr);
  1161. sticker.lottie = LottieAnimationFromDocument(
  1162. set.lottiePlayer.get(),
  1163. sticker.documentMedia.get(),
  1164. StickerLottieSize::StickersPanel,
  1165. boundingBoxSize() * style::DevicePixelRatio());
  1166. }
  1167. void StickersListWidget::setupWebm(Set &set, int section, int index) {
  1168. auto &sticker = set.stickers[index];
  1169. // Document should be loaded already for the animation to be set up.
  1170. Assert(sticker.documentMedia != nullptr);
  1171. const auto setId = set.id;
  1172. const auto document = sticker.document;
  1173. auto callback = [=](Media::Clip::Notification notification) {
  1174. clipCallback(notification, setId, document, index);
  1175. };
  1176. sticker.webm = Media::Clip::MakeReader(
  1177. sticker.documentMedia->owner()->location(),
  1178. sticker.documentMedia->bytes(),
  1179. std::move(callback));
  1180. }
  1181. void StickersListWidget::clipCallback(
  1182. Media::Clip::Notification notification,
  1183. uint64 setId,
  1184. not_null<DocumentData*> document,
  1185. int indexHint) {
  1186. Expects(indexHint >= 0);
  1187. auto &sets = shownSets();
  1188. enumerateSections([&](const SectionInfo &info) {
  1189. auto &set = sets[info.section];
  1190. if (set.id != setId) {
  1191. return true;
  1192. }
  1193. using namespace Media::Clip;
  1194. switch (notification) {
  1195. case Notification::Reinit: {
  1196. const auto j = (indexHint < set.stickers.size()
  1197. && set.stickers[indexHint].document == document)
  1198. ? (begin(set.stickers) + indexHint)
  1199. : ranges::find(set.stickers, document, &Sticker::document);
  1200. if (j == end(set.stickers) || !j->webm) {
  1201. break;
  1202. }
  1203. const auto index = j - begin(set.stickers);
  1204. auto &webm = j->webm;
  1205. if (webm->state() == State::Error) {
  1206. webm.setBad();
  1207. } else if (webm->ready() && !webm->started()) {
  1208. const auto size = ComputeStickerSize(
  1209. j->document,
  1210. boundingBoxSize());
  1211. webm->start({ .frame = size, .keepAlpha = true });
  1212. } else if (webm->autoPausedGif() && !itemVisible(info, index)) {
  1213. webm = nullptr;
  1214. }
  1215. } break;
  1216. case Notification::Repaint: break;
  1217. }
  1218. updateSet(info);
  1219. return false;
  1220. });
  1221. }
  1222. bool StickersListWidget::itemVisible(
  1223. const SectionInfo &info,
  1224. int index) const {
  1225. const auto visibleTop = getVisibleTop();
  1226. const auto visibleBottom = getVisibleBottom();
  1227. const auto row = index / _columnCount;
  1228. const auto top = info.rowsTop + row * _singleSize.height();
  1229. const auto bottom = top + _singleSize.height();
  1230. return (visibleTop < bottom) && (visibleBottom > top);
  1231. }
  1232. void StickersListWidget::updateSets() {
  1233. if (_repaintSetsIds.empty()) {
  1234. return;
  1235. }
  1236. auto repaint = base::take(_repaintSetsIds);
  1237. auto &sets = shownSets();
  1238. enumerateSections([&](const SectionInfo &info) {
  1239. if (repaint.contains(sets[info.section].id)) {
  1240. updateSet(info);
  1241. }
  1242. return true;
  1243. });
  1244. }
  1245. void StickersListWidget::updateSet(const SectionInfo &info) {
  1246. auto &set = shownSets()[info.section];
  1247. const auto now = crl::now();
  1248. const auto delay = std::max(
  1249. _lastScrolledAt + kMinAfterScrollDelay - now,
  1250. set.lastUpdateTime + kMinRepaintDelay - now);
  1251. if (delay <= 0) {
  1252. repaintItems(info, now);
  1253. } else {
  1254. _repaintSetsIds.emplace(set.id);
  1255. if (!_updateSetsTimer.isActive()
  1256. || _updateSetsTimer.remainingTime() > kMinRepaintDelay) {
  1257. _updateSetsTimer.callOnce(std::max(delay, kMinRepaintDelay));
  1258. }
  1259. }
  1260. }
  1261. void StickersListWidget::repaintItems(
  1262. const SectionInfo &info,
  1263. crl::time now) {
  1264. update(
  1265. 0,
  1266. info.rowsTop,
  1267. width(),
  1268. info.rowsBottom - info.rowsTop);
  1269. auto &set = shownSets()[info.section];
  1270. set.lastUpdateTime = now;
  1271. }
  1272. void StickersListWidget::updateItems() {
  1273. const auto now = crl::now();
  1274. const auto delay = std::max(
  1275. _lastScrolledAt + kMinAfterScrollDelay - now,
  1276. _lastFullUpdatedAt + kMinRepaintDelay - now);
  1277. if (delay <= 0) {
  1278. repaintItems(now);
  1279. } else if (!_updateItemsTimer.isActive()
  1280. || _updateItemsTimer.remainingTime() > kMinRepaintDelay) {
  1281. _updateItemsTimer.callOnce(std::max(delay, kMinRepaintDelay));
  1282. }
  1283. }
  1284. void StickersListWidget::repaintItems(crl::time now) {
  1285. update();
  1286. _repaintSetsIds.clear();
  1287. if (!now) {
  1288. now = crl::now();
  1289. }
  1290. _lastFullUpdatedAt = now;
  1291. for (auto &set : shownSets()) {
  1292. set.lastUpdateTime = now;
  1293. }
  1294. }
  1295. QSize StickersListWidget::boundingBoxSize() const {
  1296. return QSize(
  1297. _singleSize.width() - st::roundRadiusSmall * 2,
  1298. _singleSize.height() - st::roundRadiusSmall * 2);
  1299. }
  1300. void StickersListWidget::paintSticker(
  1301. Painter &p,
  1302. Set &set,
  1303. int y,
  1304. int section,
  1305. int index,
  1306. crl::time now,
  1307. bool paused,
  1308. bool selected,
  1309. bool deleteSelected) {
  1310. auto &sticker = set.stickers[index];
  1311. sticker.ensureMediaCreated();
  1312. const auto document = sticker.document;
  1313. const auto &media = sticker.documentMedia;
  1314. if (!document->sticker()) {
  1315. return;
  1316. }
  1317. const auto premium = document->isPremiumSticker();
  1318. const auto isLottie = document->sticker()->isLottie();
  1319. const auto isWebm = document->sticker()->isWebm();
  1320. if (isLottie
  1321. && !sticker.lottie
  1322. && media->loaded()) {
  1323. setupLottie(set, section, index);
  1324. } else if (isWebm && !sticker.webm && media->loaded()) {
  1325. setupWebm(set, section, index);
  1326. }
  1327. int row = (index / _columnCount), col = (index % _columnCount);
  1328. auto pos = QPoint(stickersLeft() + col * _singleSize.width(), y + row * _singleSize.height());
  1329. if (selected) {
  1330. auto tl = pos;
  1331. if (rtl()) tl.setX(width() - tl.x() - _singleSize.width());
  1332. _overBg.paint(p, QRect(tl, _singleSize));
  1333. }
  1334. media->checkStickerSmall();
  1335. const auto size = ComputeStickerSize(document, boundingBoxSize());
  1336. const auto ppos = pos + QPoint(
  1337. (_singleSize.width() - size.width()) / 2,
  1338. (_singleSize.height() - size.height()) / 2);
  1339. auto lottieFrame = QImage();
  1340. if (sticker.lottie && sticker.lottie->ready()) {
  1341. auto request = Lottie::FrameRequest();
  1342. request.box = boundingBoxSize() * style::DevicePixelRatio();
  1343. lottieFrame = sticker.lottie->frame(request);
  1344. p.drawImage(
  1345. QRect(ppos, lottieFrame.size() / style::DevicePixelRatio()),
  1346. lottieFrame);
  1347. if (sticker.savedFrame.isNull()) {
  1348. sticker.savedFrame = lottieFrame;
  1349. sticker.savedFrame.setDevicePixelRatio(style::DevicePixelRatio());
  1350. sticker.savedFrameFor = _singleSize;
  1351. }
  1352. set.lottiePlayer->unpause(sticker.lottie);
  1353. } else if (sticker.webm && sticker.webm->started()) {
  1354. const auto frame = sticker.webm->current(
  1355. { .frame = size, .keepAlpha = true },
  1356. paused ? 0 : now);
  1357. if (sticker.savedFrame.isNull()) {
  1358. sticker.savedFrame = frame;
  1359. sticker.savedFrame.setDevicePixelRatio(style::DevicePixelRatio());
  1360. sticker.savedFrameFor = _singleSize;
  1361. }
  1362. p.drawImage(ppos, frame);
  1363. } else {
  1364. const auto image = media->getStickerSmall();
  1365. const auto useSavedFrame = !sticker.savedFrame.isNull()
  1366. && (sticker.savedFrameFor == _singleSize);
  1367. if (useSavedFrame) {
  1368. p.drawImage(ppos, sticker.savedFrame);
  1369. if (premium) {
  1370. lottieFrame = sticker.savedFrame;
  1371. }
  1372. } else if (image) {
  1373. const auto pixmap = image->pixSingle(size, { .outer = size });
  1374. p.drawPixmapLeft(ppos, width(), pixmap);
  1375. if (sticker.savedFrame.isNull()) {
  1376. sticker.savedFrame = pixmap.toImage().convertToFormat(
  1377. QImage::Format_ARGB32_Premultiplied);
  1378. sticker.savedFrameFor = _singleSize;
  1379. }
  1380. if (premium) {
  1381. lottieFrame = pixmap.toImage().convertToFormat(
  1382. QImage::Format_ARGB32_Premultiplied);
  1383. }
  1384. } else {
  1385. p.setOpacity(1.);
  1386. PaintStickerThumbnailPath(
  1387. p,
  1388. media.get(),
  1389. QRect(ppos, size),
  1390. _pathGradient.get());
  1391. }
  1392. }
  1393. if (selected && stickerHasDeleteButton(set, index)) {
  1394. auto xPos = pos + QPoint(_singleSize.width() - st::stickerPanDeleteIconBg.width(), 0);
  1395. p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg);
  1396. st::stickerPanDeleteIconBg.paint(p, xPos, width());
  1397. p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg);
  1398. st::stickerPanDeleteIconFg.paint(p, xPos, width());
  1399. p.setOpacity(1.);
  1400. }
  1401. auto cornerPainted = false;
  1402. const auto corner = (set.id == Data::Stickers::RecentSetId)
  1403. ? &_cornerEmoji
  1404. : (set.id == SearchEmojiSectionSetId())
  1405. ? &_filterStickersCornerEmoji
  1406. : nullptr;
  1407. if (corner && !corner->empty() && _paintAsPremium) {
  1408. Assert(index < corner->size());
  1409. if (const auto emoji = (*corner)[index]) {
  1410. const auto size = Ui::Emoji::GetSizeNormal();
  1411. const auto ratio = style::DevicePixelRatio();
  1412. const auto radius = st::roundRadiusSmall;
  1413. const auto position = pos
  1414. + QPoint(_singleSize.width(), _singleSize.height())
  1415. - QPoint(size / ratio + radius, size / ratio + radius);
  1416. Ui::Emoji::Draw(p, emoji, size, position.x(), position.y());
  1417. cornerPainted = true;
  1418. }
  1419. }
  1420. if (!cornerPainted && premium) {
  1421. _premiumMark->paint(
  1422. p,
  1423. lottieFrame,
  1424. sticker.premiumLock,
  1425. pos,
  1426. _singleSize,
  1427. width());
  1428. }
  1429. }
  1430. int StickersListWidget::stickersRight() const {
  1431. return stickersLeft() + (_columnCount * _singleSize.width());
  1432. }
  1433. bool StickersListWidget::featuredHasAddButton(int index) const {
  1434. if (index < 0
  1435. || index >= shownSets().size()
  1436. || !shownSets()[index].externalLayout) {
  1437. return false;
  1438. }
  1439. const auto flags = shownSets()[index].flags;
  1440. return !SetInMyList(flags);
  1441. }
  1442. QRect StickersListWidget::featuredAddRect(int index) const {
  1443. return featuredAddRect(sectionInfo(index), false);
  1444. }
  1445. QRect StickersListWidget::featuredAddRect(
  1446. const SectionInfo &info,
  1447. bool installedSet) const {
  1448. const auto addw = (installedSet ? _installedWidth : _addWidth)
  1449. - st::stickersTrendingAdd.width;
  1450. const auto addh = st::stickersTrendingAdd.height;
  1451. const auto addx = stickersRight() - addw;
  1452. const auto addy = info.top + st::stickersTrendingAddTop;
  1453. return QRect(addx, addy, addw, addh);
  1454. }
  1455. bool StickersListWidget::hasRemoveButton(int index) const {
  1456. if (index < 0 || index >= shownSets().size()) {
  1457. return false;
  1458. }
  1459. auto &set = shownSets()[index];
  1460. if (set.externalLayout) {
  1461. return false;
  1462. }
  1463. auto flags = set.flags;
  1464. if (!(flags & SetFlag::Special)) {
  1465. return true;
  1466. }
  1467. if (set.id == Data::Stickers::MegagroupSetId) {
  1468. Assert(_megagroupSet != nullptr);
  1469. if (index + 1 != shownSets().size()) {
  1470. return true;
  1471. }
  1472. return !set.stickers.empty() && _megagroupSet->canEditStickers();
  1473. }
  1474. return false;
  1475. }
  1476. QRect StickersListWidget::removeButtonRect(int index) const {
  1477. return removeButtonRect(sectionInfo(index));
  1478. }
  1479. QRect StickersListWidget::removeButtonRect(const SectionInfo &info) const {
  1480. const auto &removeSt = st().removeSet;
  1481. auto buttonw = removeSt.width;
  1482. auto buttonh = removeSt.height;
  1483. auto buttonx = stickersRight() - buttonw;
  1484. auto buttony = info.top + (st().header - buttonh) / 2;
  1485. return QRect(buttonx, buttony, buttonw, buttonh);
  1486. }
  1487. void StickersListWidget::mousePressEvent(QMouseEvent *e) {
  1488. if (e->button() != Qt::LeftButton) {
  1489. return;
  1490. }
  1491. _lastMousePosition = e->globalPos();
  1492. updateSelected();
  1493. setPressed(_selected);
  1494. ClickHandler::pressed();
  1495. _previewTimer.callOnce(QApplication::startDragTime());
  1496. }
  1497. void StickersListWidget::setPressed(OverState newPressed) {
  1498. if (auto button = std::get_if<OverButton>(&_pressed)) {
  1499. auto &sets = shownSets();
  1500. Assert(button->section >= 0 && button->section < sets.size());
  1501. auto &set = sets[button->section];
  1502. if (set.ripple) {
  1503. set.ripple->lastStop();
  1504. }
  1505. } else if (std::get_if<OverGroupAdd>(&_pressed)) {
  1506. if (_megagroupSetButtonRipple) {
  1507. _megagroupSetButtonRipple->lastStop();
  1508. }
  1509. }
  1510. _pressed = newPressed;
  1511. if (auto button = std::get_if<OverButton>(&_pressed)) {
  1512. auto &sets = shownSets();
  1513. Assert(button->section >= 0 && button->section < sets.size());
  1514. auto &set = sets[button->section];
  1515. if (!set.ripple) {
  1516. set.ripple = createButtonRipple(button->section);
  1517. }
  1518. set.ripple->add(mapFromGlobal(QCursor::pos()) - buttonRippleTopLeft(button->section));
  1519. } else if (std::get_if<OverGroupAdd>(&_pressed)) {
  1520. if (!_megagroupSetButtonRipple) {
  1521. auto maskSize = _megagroupSetButtonRect.size();
  1522. auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusLarge);
  1523. _megagroupSetButtonRipple = std::make_unique<Ui::RippleAnimation>(st::stickerGroupCategoryAdd.ripple, std::move(mask), [this] {
  1524. rtlupdate(megagroupSetButtonRectFinal());
  1525. });
  1526. }
  1527. _megagroupSetButtonRipple->add(mapFromGlobal(QCursor::pos()) - myrtlrect(megagroupSetButtonRectFinal()).topLeft());
  1528. }
  1529. }
  1530. QRect StickersListWidget::megagroupSetButtonRectFinal() const {
  1531. auto result = QRect();
  1532. if (_section == Section::Stickers) {
  1533. enumerateSections([this, &result](const SectionInfo &info) {
  1534. if (shownSets()[info.section].id == Data::Stickers::MegagroupSetId) {
  1535. result = _megagroupSetButtonRect.translated(0, info.rowsTop);
  1536. return false;
  1537. }
  1538. return true;
  1539. });
  1540. }
  1541. return result;
  1542. }
  1543. std::unique_ptr<Ui::RippleAnimation> StickersListWidget::createButtonRipple(int section) {
  1544. Expects(section >= 0 && section < shownSets().size());
  1545. if (shownSets()[section].externalLayout) {
  1546. auto maskSize = QSize(_addWidth - st::stickersTrendingAdd.width, st::stickersTrendingAdd.height);
  1547. auto mask = Ui::RippleAnimation::RoundRectMask(maskSize, st::roundRadiusLarge);
  1548. return std::make_unique<Ui::RippleAnimation>(
  1549. st::stickersTrendingAdd.ripple,
  1550. std::move(mask),
  1551. [this, section] { rtlupdate(featuredAddRect(section)); });
  1552. }
  1553. const auto &removeSt = st().removeSet;
  1554. auto maskSize = QSize(removeSt.rippleAreaSize, removeSt.rippleAreaSize);
  1555. auto mask = Ui::RippleAnimation::EllipseMask(maskSize);
  1556. return std::make_unique<Ui::RippleAnimation>(
  1557. removeSt.ripple,
  1558. std::move(mask),
  1559. [this, section] { rtlupdate(removeButtonRect(section)); });
  1560. }
  1561. QPoint StickersListWidget::buttonRippleTopLeft(int section) const {
  1562. Expects(section >= 0 && section < shownSets().size());
  1563. if (shownSets()[section].externalLayout) {
  1564. return myrtlrect(featuredAddRect(section)).topLeft();
  1565. }
  1566. return myrtlrect(removeButtonRect(section)).topLeft()
  1567. + st().removeSet.rippleAreaPosition;
  1568. }
  1569. void StickersListWidget::showStickerSetBox(
  1570. not_null<DocumentData*> document,
  1571. uint64 setId) {
  1572. if (document->sticker() && document->sticker()->set) {
  1573. checkHideWithBox(Box<StickerSetBox>(
  1574. _show,
  1575. document->sticker()->set,
  1576. document->sticker()->setType));
  1577. } else if ((setId == Data::Stickers::FavedSetId)
  1578. || (setId == Data::Stickers::RecentSetId)) {
  1579. const auto lifetime = std::make_shared<rpl::lifetime>();
  1580. constexpr auto kTimeout = 10000;
  1581. rpl::merge(
  1582. base::timer_once(kTimeout),
  1583. document->owner().stickers().updated(
  1584. Data::StickersType::Stickers)
  1585. ) | rpl::start_with_next([=, weak = Ui::MakeWeak(this)] {
  1586. if (weak.data()) {
  1587. showStickerSetBox(document, setId);
  1588. }
  1589. lifetime->destroy();
  1590. }, *lifetime);
  1591. document->owner().session().api().requestSpecialStickersForce(
  1592. setId == Data::Stickers::FavedSetId,
  1593. setId == Data::Stickers::RecentSetId,
  1594. false);
  1595. }
  1596. }
  1597. base::unique_qptr<Ui::PopupMenu> StickersListWidget::fillContextMenu(
  1598. const SendMenu::Details &details) {
  1599. auto selected = _selected;
  1600. auto &sets = shownSets();
  1601. if (v::is_null(selected) || !v::is_null(_pressed)) {
  1602. return nullptr;
  1603. }
  1604. const auto sticker = std::get_if<OverSticker>(&selected);
  1605. if (!sticker) {
  1606. return nullptr;
  1607. }
  1608. const auto section = sticker->section;
  1609. const auto index = sticker->index;
  1610. Assert(section >= 0 && section < sets.size());
  1611. auto &set = sets[section];
  1612. Assert(index >= 0 && index < set.stickers.size());
  1613. auto menu = base::make_unique_q<Ui::PopupMenu>(this, st().menu);
  1614. const auto document = set.stickers[sticker->index].document;
  1615. const auto send = crl::guard(this, [=](Api::SendOptions options) {
  1616. _chosen.fire({
  1617. .document = document,
  1618. .options = options,
  1619. .messageSendingFrom = options.scheduled
  1620. ? Ui::MessageSendingAnimationFrom()
  1621. : messageSentAnimationInfo(section, index, document),
  1622. });
  1623. });
  1624. const auto icons = &st().icons;
  1625. // In case we're adding items after FillSendMenu we have
  1626. // to pass nullptr for showForEffect and attach selector later.
  1627. // Otherwise added items widths won't be respected in menu geometry.
  1628. SendMenu::FillSendMenu(
  1629. menu,
  1630. nullptr, // showForEffect
  1631. details,
  1632. SendMenu::DefaultCallback(_show, send),
  1633. icons);
  1634. const auto show = _show;
  1635. const auto toggleFavedSticker = [=] {
  1636. Api::ToggleFavedSticker(
  1637. show,
  1638. document,
  1639. Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0));
  1640. };
  1641. const auto isFaved = document->owner().stickers().isFaved(document);
  1642. menu->addAction(
  1643. (isFaved
  1644. ? tr::lng_faved_stickers_remove
  1645. : tr::lng_faved_stickers_add)(tr::now),
  1646. toggleFavedSticker,
  1647. isFaved ? &icons->menuUnfave : &icons->menuFave);
  1648. if (_features.openStickerSets) {
  1649. menu->addAction(tr::lng_context_pack_info(tr::now), [=, id = set.id] {
  1650. showStickerSetBox(document, id);
  1651. }, &icons->menuStickerSet);
  1652. }
  1653. if (const auto id = set.id; id == Data::Stickers::RecentSetId) {
  1654. menu->addAction(tr::lng_recent_stickers_remove(tr::now), [=] {
  1655. Api::ToggleRecentSticker(
  1656. document,
  1657. Data::FileOriginStickerSet(id, 0),
  1658. false);
  1659. }, &icons->menuRecentRemove);
  1660. }
  1661. SendMenu::AttachSendMenuEffect(
  1662. menu,
  1663. _show,
  1664. details,
  1665. SendMenu::DefaultCallback(_show, send));
  1666. return menu;
  1667. }
  1668. Ui::MessageSendingAnimationFrom StickersListWidget::messageSentAnimationInfo(
  1669. int section,
  1670. int index,
  1671. not_null<DocumentData*> document) {
  1672. const auto rect = stickerRect(section, index);
  1673. const auto size = ComputeStickerSize(document, boundingBoxSize());
  1674. const auto innerPos = QPoint(
  1675. (rect.width() - size.width()) / 2,
  1676. (rect.height() - size.height()) / 2);
  1677. return {
  1678. .type = Ui::MessageSendingAnimationFrom::Type::Sticker,
  1679. .localId = session().data().nextLocalMessageId(),
  1680. .globalStartGeometry = mapToGlobal(
  1681. QRect(rect.topLeft() + innerPos, size)),
  1682. };
  1683. }
  1684. void StickersListWidget::mouseReleaseEvent(QMouseEvent *e) {
  1685. _previewTimer.cancel();
  1686. auto pressed = _pressed;
  1687. setPressed(v::null);
  1688. if (pressed != _selected) {
  1689. repaintItems();
  1690. }
  1691. auto activated = ClickHandler::unpressed();
  1692. if (_previewShown) {
  1693. _previewShown = false;
  1694. return;
  1695. }
  1696. _lastMousePosition = e->globalPos();
  1697. updateSelected();
  1698. auto &sets = shownSets();
  1699. if (!v::is_null(pressed) && pressed == _selected) {
  1700. if (auto sticker = std::get_if<OverSticker>(&pressed)) {
  1701. Assert(sticker->section >= 0 && sticker->section < sets.size());
  1702. auto &set = sets[sticker->section];
  1703. Assert(sticker->index >= 0 && sticker->index < set.stickers.size());
  1704. if (stickerHasDeleteButton(set, sticker->index) && sticker->overDelete) {
  1705. if (set.id == Data::Stickers::RecentSetId) {
  1706. removeRecentSticker(sticker->section, sticker->index);
  1707. } else if (set.id == Data::Stickers::FavedSetId) {
  1708. removeFavedSticker(sticker->section, sticker->index);
  1709. } else {
  1710. Unexpected("Single sticker delete click.");
  1711. }
  1712. return;
  1713. }
  1714. const auto document = set.stickers[sticker->index].document;
  1715. if (_features.openStickerSets
  1716. && (e->modifiers() & Qt::ControlModifier)) {
  1717. showStickerSetBox(document, set.id);
  1718. } else {
  1719. _chosen.fire({
  1720. .document = document,
  1721. .messageSendingFrom = messageSentAnimationInfo(
  1722. sticker->section,
  1723. sticker->index,
  1724. document),
  1725. });
  1726. }
  1727. } else if (auto set = std::get_if<OverSet>(&pressed)) {
  1728. Assert(set->section >= 0 && set->section < sets.size());
  1729. displaySet(sets[set->section].id);
  1730. } else if (auto button = std::get_if<OverButton>(&pressed)) {
  1731. Assert(button->section >= 0 && button->section < sets.size());
  1732. if (sets[button->section].externalLayout) {
  1733. _localSetsManager->install(sets[button->section].id);
  1734. update();
  1735. } else {
  1736. removeSet(sets[button->section].id);
  1737. }
  1738. } else if (std::get_if<OverGroupAdd>(&pressed)) {
  1739. const auto isEmoji = false;
  1740. _show->showBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
  1741. }
  1742. }
  1743. }
  1744. void StickersListWidget::removeRecentSticker(int section, int index) {
  1745. if ((_section != Section::Stickers)
  1746. || (section >= int(_mySets.size()))
  1747. || (_mySets[section].id != Data::Stickers::RecentSetId)) {
  1748. return;
  1749. }
  1750. clearSelection();
  1751. bool refresh = false;
  1752. const auto &sticker = _mySets[section].stickers[index];
  1753. const auto document = sticker.document;
  1754. auto &recent = session().data().stickers().getRecentPack();
  1755. for (int32 i = 0, l = recent.size(); i < l; ++i) {
  1756. if (recent.at(i).first == document) {
  1757. recent.removeAt(i);
  1758. session().saveSettings();
  1759. refresh = true;
  1760. break;
  1761. }
  1762. }
  1763. auto &sets = session().data().stickers().setsRef();
  1764. auto it = sets.find(Data::Stickers::CustomSetId);
  1765. if (it != sets.cend()) {
  1766. const auto set = it->second.get();
  1767. for (int i = 0, l = set->stickers.size(); i < l; ++i) {
  1768. if (set->stickers.at(i) == document) {
  1769. set->stickers.removeAt(i);
  1770. if (set->stickers.isEmpty()) {
  1771. sets.erase(it);
  1772. }
  1773. session().local().writeInstalledStickers();
  1774. refresh = true;
  1775. break;
  1776. }
  1777. }
  1778. }
  1779. if (refresh) {
  1780. refreshRecentStickers();
  1781. updateSelected();
  1782. repaintItems();
  1783. }
  1784. }
  1785. void StickersListWidget::removeFavedSticker(int section, int index) {
  1786. if ((_section != Section::Stickers)
  1787. || (section >= int(_mySets.size()))
  1788. || (_mySets[section].id != Data::Stickers::FavedSetId)) {
  1789. return;
  1790. }
  1791. clearSelection();
  1792. const auto &sticker = _mySets[section].stickers[index];
  1793. const auto document = sticker.document;
  1794. session().data().stickers().setFaved(_show, document, false);
  1795. Api::ToggleFavedSticker(
  1796. _show,
  1797. document,
  1798. Data::FileOriginStickerSet(Data::Stickers::FavedSetId, 0),
  1799. false);
  1800. }
  1801. void StickersListWidget::setColumnCount(int count) {
  1802. Expects(count > 0);
  1803. if (_columnCount != count) {
  1804. _columnCount = count;
  1805. refreshFooterIcons();
  1806. }
  1807. }
  1808. void StickersListWidget::mouseMoveEvent(QMouseEvent *e) {
  1809. _lastMousePosition = e->globalPos();
  1810. updateSelected();
  1811. }
  1812. void StickersListWidget::resizeEvent(QResizeEvent *e) {
  1813. _settings->moveToLeft(
  1814. (width() - _settings->width()) / 2,
  1815. height() / 3);
  1816. if (!_megagroupSetAbout.isEmpty()) {
  1817. refreshMegagroupSetGeometry();
  1818. }
  1819. }
  1820. void StickersListWidget::leaveEventHook(QEvent *e) {
  1821. clearSelection();
  1822. }
  1823. void StickersListWidget::leaveToChildEvent(QEvent *e, QWidget *child) {
  1824. clearSelection();
  1825. }
  1826. void StickersListWidget::enterFromChildEvent(QEvent *e, QWidget *child) {
  1827. _lastMousePosition = QCursor::pos();
  1828. updateSelected();
  1829. }
  1830. void StickersListWidget::clearSelection() {
  1831. setPressed(v::null);
  1832. setSelected(v::null);
  1833. repaintItems();
  1834. }
  1835. TabbedSelector::InnerFooter *StickersListWidget::getFooter() const {
  1836. return _footer;
  1837. }
  1838. void StickersListWidget::processHideFinished() {
  1839. _choosingUpdated.fire(TabbedSelector::Action::Cancel);
  1840. clearSelection();
  1841. clearHeavyData();
  1842. if (_footer) {
  1843. _footer->clearHeavyData();
  1844. }
  1845. }
  1846. void StickersListWidget::processPanelHideFinished() {
  1847. if (_localSetsManager->clearInstalledLocally()) {
  1848. refreshStickers();
  1849. }
  1850. clearHeavyData();
  1851. if (_footer) {
  1852. _footer->clearHeavyData();
  1853. }
  1854. }
  1855. void StickersListWidget::setSection(Section section) {
  1856. if (_section == section) {
  1857. return;
  1858. }
  1859. clearHeavyData();
  1860. _section = section;
  1861. _recentShownCount = (section == Section::Search)
  1862. ? _filteredStickers.size()
  1863. : _mySets.empty()
  1864. ? 0
  1865. : _mySets.front().stickers.size();
  1866. }
  1867. void StickersListWidget::clearHeavyData() {
  1868. for (auto &set : shownSets()) {
  1869. clearHeavyIn(set, false);
  1870. }
  1871. }
  1872. void StickersListWidget::refreshStickers() {
  1873. clearSelection();
  1874. if (_isEffects) {
  1875. refreshEffects();
  1876. } else {
  1877. refreshMySets();
  1878. refreshFeaturedSets();
  1879. refreshSearchSets();
  1880. }
  1881. resizeToWidth(width());
  1882. if (_footer) {
  1883. refreshFooterIcons();
  1884. }
  1885. refreshSettingsVisibility();
  1886. _lastMousePosition = QCursor::pos();
  1887. updateSelected();
  1888. repaintItems();
  1889. visibleTopBottomUpdated(getVisibleTop(), getVisibleBottom());
  1890. }
  1891. void StickersListWidget::refreshEffects() {
  1892. auto wasSets = base::take(_mySets);
  1893. _mySets.reserve(1);
  1894. refreshRecentStickers(false);
  1895. takeHeavyData(_mySets, wasSets);
  1896. }
  1897. void StickersListWidget::refreshMySets() {
  1898. auto wasSets = base::take(_mySets);
  1899. _favedStickersMap.clear();
  1900. _mySets.reserve(defaultSetsOrder().size() + 3);
  1901. refreshFavedStickers();
  1902. refreshRecentStickers(false);
  1903. refreshMegagroupStickers(GroupStickersPlace::Visible);
  1904. for (const auto setId : defaultSetsOrder()) {
  1905. const auto externalLayout = false;
  1906. appendSet(_mySets, setId, externalLayout, AppendSkip::Archived);
  1907. }
  1908. refreshMegagroupStickers(GroupStickersPlace::Hidden);
  1909. takeHeavyData(_mySets, wasSets);
  1910. }
  1911. void StickersListWidget::refreshFeaturedSets() {
  1912. auto wasFeaturedSetsCount = base::take(_featuredSetsCount);
  1913. auto wereOfficial = base::take(_officialSets);
  1914. _officialSets.reserve(
  1915. session().data().stickers().featuredSetsOrder().size()
  1916. + wereOfficial.size()
  1917. - wasFeaturedSetsCount);
  1918. for (const auto setId : session().data().stickers().featuredSetsOrder()) {
  1919. const auto externalLayout = true;
  1920. appendSet(_officialSets, setId, externalLayout, AppendSkip::Installed);
  1921. }
  1922. _featuredSetsCount = _officialSets.size();
  1923. if (wereOfficial.size() > wasFeaturedSetsCount) {
  1924. const auto &sets = session().data().stickers().sets();
  1925. const auto from = begin(wereOfficial) + wasFeaturedSetsCount;
  1926. const auto till = end(wereOfficial);
  1927. for (auto i = from; i != till; ++i) {
  1928. auto &set = *i;
  1929. auto it = sets.find(set.id);
  1930. if (it == sets.cend()
  1931. || ((it->second->flags & SetFlag::Installed)
  1932. && !(it->second->flags & SetFlag::Archived)
  1933. && !_localSetsManager->isInstalledLocally(set.id))) {
  1934. continue;
  1935. }
  1936. set.flags = it->second->flags;
  1937. _officialSets.push_back(std::move(set));
  1938. }
  1939. }
  1940. }
  1941. void StickersListWidget::refreshSearchSets() {
  1942. refreshSearchIndex();
  1943. const auto &sets = session().data().stickers().sets();
  1944. const auto skipPremium = !session().premiumPossible();
  1945. for (auto &entry : _searchSets) {
  1946. if (const auto it = sets.find(entry.id); it != sets.end()) {
  1947. const auto set = it->second.get();
  1948. entry.flags = set->flags;
  1949. auto elements = PrepareStickers(set->stickers, skipPremium);
  1950. if (!elements.empty()) {
  1951. entry.lottiePlayer = nullptr;
  1952. entry.stickers = std::move(elements);
  1953. }
  1954. if (!SetInMyList(entry.flags)) {
  1955. _localSetsManager->removeInstalledLocally(entry.id);
  1956. entry.externalLayout = true;
  1957. }
  1958. }
  1959. }
  1960. }
  1961. void StickersListWidget::refreshSearchIndex() {
  1962. _searchIndex.clear();
  1963. for (const auto &set : _mySets) {
  1964. if (set.flags & SetFlag::Special) {
  1965. continue;
  1966. }
  1967. const auto string = set.title + ' ' + set.shortName;
  1968. const auto list = TextUtilities::PrepareSearchWords(string);
  1969. _searchIndex.emplace_back(set.id, list);
  1970. }
  1971. }
  1972. void StickersListWidget::refreshSettingsVisibility() {
  1973. const auto visible = (_section == Section::Stickers)
  1974. && _mySets.empty()
  1975. && !_isMasks;
  1976. _settings->setVisible(visible);
  1977. }
  1978. void StickersListWidget::refreshFooterIcons() {
  1979. refreshIcons(ValidateIconAnimations::None);
  1980. }
  1981. void StickersListWidget::preloadImages() {
  1982. if (_footer) {
  1983. _footer->preloadImages();
  1984. }
  1985. }
  1986. uint64 StickersListWidget::currentSet(int yOffset) const {
  1987. if (_section == Section::Featured) {
  1988. return Data::Stickers::FeaturedSetId;
  1989. }
  1990. const auto &sets = shownSets();
  1991. return sets.empty()
  1992. ? Data::Stickers::RecentSetId
  1993. : sets[sectionInfoByOffset(yOffset).section].id;
  1994. }
  1995. bool StickersListWidget::appendSet(
  1996. std::vector<Set> &to,
  1997. uint64 setId,
  1998. bool externalLayout,
  1999. AppendSkip skip) {
  2000. const auto &sets = session().data().stickers().sets();
  2001. auto it = sets.find(setId);
  2002. if (it == sets.cend()
  2003. || (!externalLayout && it->second->stickers.isEmpty())) {
  2004. return false;
  2005. }
  2006. const auto set = it->second.get();
  2007. if ((skip == AppendSkip::Archived)
  2008. && (set->flags & SetFlag::Archived)) {
  2009. return false;
  2010. }
  2011. if ((skip == AppendSkip::Installed)
  2012. && (set->flags & SetFlag::Installed)
  2013. && !(set->flags & SetFlag::Archived)) {
  2014. if (!_localSetsManager->isInstalledLocally(setId)) {
  2015. return false;
  2016. }
  2017. }
  2018. const auto skipPremium = !session().premiumPossible();
  2019. auto elements = PrepareStickers(
  2020. ((set->stickers.empty() && externalLayout)
  2021. ? set->covers
  2022. : set->stickers),
  2023. skipPremium);
  2024. if (elements.empty()) {
  2025. return false;
  2026. }
  2027. to.emplace_back(
  2028. set->id,
  2029. set,
  2030. set->flags,
  2031. set->title,
  2032. set->shortName,
  2033. set->count,
  2034. externalLayout,
  2035. std::move(elements));
  2036. to.back().thumbnailDocument = set->lookupThumbnailDocument();
  2037. return true;
  2038. }
  2039. void StickersListWidget::refreshRecent() {
  2040. if (_section == Section::Stickers) {
  2041. refreshRecentStickers();
  2042. }
  2043. }
  2044. auto StickersListWidget::collectCustomRecents() -> std::vector<Sticker> {
  2045. _custom.clear();
  2046. _cornerEmoji.clear();
  2047. auto result = std::vector<Sticker>();
  2048. result.reserve(_customRecentIds.size());
  2049. for (const auto &descriptor : _customRecentIds) {
  2050. if (const auto document = descriptor.document; document->sticker()) {
  2051. result.push_back(Sticker{ document });
  2052. _custom.push_back(false);
  2053. _cornerEmoji.push_back(Ui::Emoji::Find(descriptor.cornerEmoji));
  2054. }
  2055. }
  2056. return result;
  2057. }
  2058. auto StickersListWidget::collectRecentStickers() -> std::vector<Sticker> {
  2059. if (_isEffects) {
  2060. return collectCustomRecents();
  2061. }
  2062. _custom.clear();
  2063. auto result = std::vector<Sticker>();
  2064. const auto &sets = session().data().stickers().sets();
  2065. const auto &recent = _isMasks
  2066. ? RecentStickerPack()
  2067. : session().data().stickers().getRecentPack();
  2068. const auto customIt = _isMasks
  2069. ? sets.cend()
  2070. : sets.find(Data::Stickers::CustomSetId);
  2071. const auto cloudIt = sets.find(_isMasks
  2072. ? Data::Stickers::CloudRecentAttachedSetId
  2073. : Data::Stickers::CloudRecentSetId);
  2074. const auto customCount = (customIt != sets.cend())
  2075. ? customIt->second->stickers.size()
  2076. : 0;
  2077. const auto cloudCount = (cloudIt != sets.cend())
  2078. ? cloudIt->second->stickers.size()
  2079. : 0;
  2080. result.reserve(cloudCount + recent.size() + customCount);
  2081. _custom.reserve(cloudCount + recent.size() + customCount);
  2082. auto add = [&](not_null<DocumentData*> document, bool custom) {
  2083. if (result.size() >= kRecentDisplayLimit) {
  2084. return;
  2085. }
  2086. const auto i = ranges::find(result, document, &Sticker::document);
  2087. if (i != end(result)) {
  2088. const auto index = (i - begin(result));
  2089. if (index >= cloudCount && custom) {
  2090. // Mark stickers from local recent as custom.
  2091. _custom[index] = true;
  2092. }
  2093. } else if (!_favedStickersMap.contains(document)) {
  2094. result.push_back(Sticker{
  2095. document
  2096. });
  2097. _custom.push_back(custom);
  2098. }
  2099. };
  2100. if (cloudCount > 0) {
  2101. for (const auto document : std::as_const(cloudIt->second->stickers)) {
  2102. add(document, false);
  2103. }
  2104. }
  2105. for (const auto &recentSticker : recent) {
  2106. add(recentSticker.first, false);
  2107. }
  2108. if (customCount > 0) {
  2109. for (const auto document : std::as_const(customIt->second->stickers)) {
  2110. add(document, true);
  2111. }
  2112. }
  2113. return result;
  2114. }
  2115. void StickersListWidget::refreshRecentStickers(bool performResize) {
  2116. clearSelection();
  2117. auto recentPack = collectRecentStickers();
  2118. if (_section == Section::Stickers) {
  2119. _recentShownCount = recentPack.size();
  2120. }
  2121. auto recentIt = std::find_if(_mySets.begin(), _mySets.end(), [](auto &set) {
  2122. return set.id == Data::Stickers::RecentSetId;
  2123. });
  2124. if (!recentPack.empty()) {
  2125. const auto shortName = QString();
  2126. const auto externalLayout = false;
  2127. auto set = Set(
  2128. Data::Stickers::RecentSetId,
  2129. nullptr,
  2130. (SetFlag::Official | SetFlag::Special),
  2131. (_isEffects
  2132. ? tr::lng_effect_stickers_title(tr::now)
  2133. : tr::lng_recent_stickers(tr::now)),
  2134. shortName,
  2135. recentPack.size(),
  2136. externalLayout,
  2137. std::move(recentPack));
  2138. if (recentIt == _mySets.end()) {
  2139. const auto where = (_mySets.empty()
  2140. || _mySets.begin()->id != Data::Stickers::FavedSetId)
  2141. ? _mySets.begin()
  2142. : (_mySets.begin() + 1);
  2143. _mySets.insert(where, std::move(set));
  2144. } else {
  2145. std::swap(*recentIt, set);
  2146. takeHeavyData(*recentIt, set);
  2147. }
  2148. } else if (recentIt != _mySets.end()) {
  2149. _mySets.erase(recentIt);
  2150. }
  2151. if (performResize && (_section == Section::Stickers || _section == Section::Featured)) {
  2152. resizeToWidth(width());
  2153. updateSelected();
  2154. }
  2155. }
  2156. void StickersListWidget::refreshFavedStickers() {
  2157. if (_isMasks) {
  2158. return;
  2159. }
  2160. clearSelection();
  2161. const auto &sets = session().data().stickers().sets();
  2162. const auto it = sets.find(Data::Stickers::FavedSetId);
  2163. if (it == sets.cend()) {
  2164. return;
  2165. }
  2166. const auto skipPremium = !session().premiumPossible();
  2167. const auto set = it->second.get();
  2168. const auto externalLayout = false;
  2169. const auto shortName = QString();
  2170. auto elements = PrepareStickers(set->stickers, skipPremium);
  2171. if (elements.empty()) {
  2172. return;
  2173. }
  2174. _mySets.insert(_mySets.begin(), Set{
  2175. Data::Stickers::FavedSetId,
  2176. nullptr,
  2177. (SetFlag::Official | SetFlag::Special),
  2178. Lang::Hard::FavedSetTitle(),
  2179. shortName,
  2180. set->count,
  2181. externalLayout,
  2182. std::move(elements)
  2183. });
  2184. _favedStickersMap = base::flat_set<not_null<DocumentData*>> {
  2185. set->stickers.begin(),
  2186. set->stickers.end()
  2187. };
  2188. }
  2189. void StickersListWidget::refreshMegagroupStickers(GroupStickersPlace place) {
  2190. if (!_features.megagroupSet || !_megagroupSet || _isMasks) {
  2191. return;
  2192. }
  2193. auto canEdit = _megagroupSet->canEditStickers();
  2194. auto isShownHere = [place](bool hidden) {
  2195. return (hidden == (place == GroupStickersPlace::Hidden));
  2196. };
  2197. if (!_megagroupSet->mgInfo->stickerSet) {
  2198. if (canEdit) {
  2199. auto hidden = session().settings().isGroupStickersSectionHidden(
  2200. _megagroupSet->id);
  2201. if (isShownHere(hidden)) {
  2202. const auto shortName = QString();
  2203. const auto externalLayout = false;
  2204. const auto count = 0;
  2205. _mySets.emplace_back(
  2206. Data::Stickers::MegagroupSetId,
  2207. nullptr,
  2208. SetFlag::Special,
  2209. tr::lng_group_stickers(tr::now),
  2210. shortName,
  2211. count,
  2212. externalLayout);
  2213. }
  2214. }
  2215. return;
  2216. }
  2217. auto hidden = session().settings().isGroupStickersSectionHidden(_megagroupSet->id);
  2218. auto removeHiddenForGroup = [this, &hidden] {
  2219. if (hidden) {
  2220. session().settings().removeGroupStickersSectionHidden(_megagroupSet->id);
  2221. session().saveSettings();
  2222. hidden = false;
  2223. }
  2224. };
  2225. if (canEdit && hidden) {
  2226. removeHiddenForGroup();
  2227. }
  2228. const auto &set = _megagroupSet->mgInfo->stickerSet;
  2229. if (!set.id) {
  2230. return;
  2231. }
  2232. const auto &sets = session().data().stickers().sets();
  2233. const auto it = sets.find(set.id);
  2234. if (it != sets.cend()) {
  2235. const auto set = it->second.get();
  2236. auto isInstalled = (set->flags & SetFlag::Installed)
  2237. && !(set->flags & SetFlag::Archived);
  2238. if (isInstalled && !canEdit) {
  2239. removeHiddenForGroup();
  2240. } else if (isShownHere(hidden)) {
  2241. const auto shortName = QString();
  2242. const auto externalLayout = false;
  2243. const auto skipPremium = !session().premiumPossible();
  2244. auto elements = PrepareStickers(set->stickers, skipPremium);
  2245. if (!elements.empty()) {
  2246. _mySets.emplace_back(
  2247. Data::Stickers::MegagroupSetId,
  2248. set,
  2249. SetFlag::Special,
  2250. tr::lng_group_stickers(tr::now),
  2251. shortName,
  2252. set->count,
  2253. externalLayout,
  2254. std::move(elements));
  2255. }
  2256. }
  2257. return;
  2258. } else if (!isShownHere(hidden) || _megagroupSetIdRequested == set.id) {
  2259. return;
  2260. }
  2261. _megagroupSetIdRequested = set.id;
  2262. _api.request(MTPmessages_GetStickerSet(
  2263. Data::InputStickerSet(set),
  2264. MTP_int(0) // hash
  2265. )).done([=](const MTPmessages_StickerSet &result) {
  2266. result.match([&](const MTPDmessages_stickerSet &data) {
  2267. if (const auto set = session().data().stickers().feedSetFull(data)) {
  2268. refreshStickers();
  2269. if (set->id == _megagroupSetIdRequested) {
  2270. _megagroupSetIdRequested = 0;
  2271. } else {
  2272. LOG(("API Error: Got different set."));
  2273. }
  2274. }
  2275. }, [](const MTPDmessages_stickerSetNotModified &) {
  2276. LOG(("API Error: Unexpected messages.stickerSetNotModified."));
  2277. });
  2278. }).send();
  2279. }
  2280. std::vector<StickerIcon> StickersListWidget::fillIcons() {
  2281. auto result = std::vector<StickerIcon>();
  2282. result.reserve(_mySets.size() + 1);
  2283. auto i = 0;
  2284. if (i != _mySets.size() && _mySets[i].id == Data::Stickers::FavedSetId) {
  2285. ++i;
  2286. result.emplace_back(Data::Stickers::FavedSetId);
  2287. }
  2288. if (i != _mySets.size() && _mySets[i].id == Data::Stickers::RecentSetId) {
  2289. ++i;
  2290. if (result.empty() || result.back().setId != Data::Stickers::FavedSetId) {
  2291. result.emplace_back(Data::Stickers::RecentSetId);
  2292. }
  2293. }
  2294. const auto side = StickersListFooter::IconFrameSize();
  2295. for (auto l = _mySets.size(); i != l; ++i) {
  2296. if (_mySets[i].id == Data::Stickers::MegagroupSetId) {
  2297. result.emplace_back(Data::Stickers::MegagroupSetId);
  2298. result.back().megagroup = _megagroupSet;
  2299. continue;
  2300. }
  2301. const auto set = _mySets[i].set;
  2302. Assert(set != nullptr);
  2303. const auto s = _mySets[i].thumbnailDocument;
  2304. const auto size = set->hasThumbnail()
  2305. ? QSize(
  2306. set->thumbnailLocation().width(),
  2307. set->thumbnailLocation().height())
  2308. : s->hasThumbnail()
  2309. ? QSize(
  2310. s->thumbnailLocation().width(),
  2311. s->thumbnailLocation().height())
  2312. : QSize();
  2313. const auto pix = size.scaled(side, side, Qt::KeepAspectRatio);
  2314. result.emplace_back(set, s, pix.width(), pix.height());
  2315. }
  2316. return result;
  2317. }
  2318. void StickersListWidget::updateSelected() {
  2319. if (!v::is_null(_pressed) && !_previewShown) {
  2320. return;
  2321. }
  2322. auto newSelected = OverState { v::null };
  2323. auto p = mapFromGlobal(_lastMousePosition);
  2324. if (!rect().contains(p)
  2325. || p.y() < getVisibleTop() || p.y() >= getVisibleBottom()
  2326. || !isVisible()) {
  2327. clearSelection();
  2328. return;
  2329. }
  2330. auto &sets = shownSets();
  2331. auto sx = (rtl() ? width() - p.x() : p.x()) - stickersLeft();
  2332. if (!shownSets().empty()) {
  2333. auto info = sectionInfoByOffset(p.y());
  2334. auto section = info.section;
  2335. if (p.y() >= info.top && p.y() < info.rowsTop) {
  2336. if (hasRemoveButton(section) && myrtlrect(removeButtonRect(info)).contains(p.x(), p.y())) {
  2337. newSelected = OverButton{ section };
  2338. } else if (featuredHasAddButton(section) && myrtlrect(featuredAddRect(info, false)).contains(p.x(), p.y())) {
  2339. newSelected = OverButton{ section };
  2340. } else if (_features.openStickerSets
  2341. && !(sets[section].flags & SetFlag::Special)) {
  2342. newSelected = OverSet{ section };
  2343. } else if ((sets[section].id == Data::Stickers::MegagroupSetId)
  2344. && (_megagroupSet->canEditStickers()
  2345. || !sets[section].stickers.empty())) {
  2346. newSelected = OverSet{ section };
  2347. }
  2348. } else if (p.y() >= info.rowsTop && p.y() < info.rowsBottom && sx >= 0) {
  2349. auto yOffset = p.y() - info.rowsTop;
  2350. auto &set = sets[section];
  2351. if (set.id == Data::Stickers::MegagroupSetId && set.stickers.empty()) {
  2352. if (_megagroupSetButtonRect.contains(stickersLeft() + sx, yOffset)) {
  2353. newSelected = OverGroupAdd{};
  2354. }
  2355. } else {
  2356. auto rowIndex = qFloor(yOffset / _singleSize.height());
  2357. auto columnIndex = qFloor(sx / _singleSize.width());
  2358. auto index = rowIndex * _columnCount + columnIndex;
  2359. if (index >= 0 && index < set.stickers.size()) {
  2360. auto overDelete = false;
  2361. if (stickerHasDeleteButton(set, index)) {
  2362. auto inx = sx - (columnIndex * _singleSize.width());
  2363. auto iny = yOffset - (rowIndex * _singleSize.height());
  2364. if (inx >= _singleSize.width() - st::stickerPanDeleteIconBg.width() && iny < st::stickerPanDeleteIconBg.height()) {
  2365. overDelete = true;
  2366. }
  2367. }
  2368. newSelected = OverSticker { section, index, overDelete };
  2369. }
  2370. }
  2371. }
  2372. }
  2373. setSelected(newSelected);
  2374. }
  2375. bool StickersListWidget::setHasTitle(const Set &set) const {
  2376. if (_isEffects) {
  2377. return true;
  2378. } else if (set.id == Data::Stickers::FavedSetId
  2379. || set.id == SearchEmojiSectionSetId()) {
  2380. return false;
  2381. } else if (set.id == Data::Stickers::RecentSetId) {
  2382. return !_mySets.empty()
  2383. && (_isMasks || (_mySets[0].id == Data::Stickers::FavedSetId));
  2384. }
  2385. return true;
  2386. }
  2387. bool StickersListWidget::stickerHasDeleteButton(const Set &set, int index) const {
  2388. if (set.id == Data::Stickers::RecentSetId) {
  2389. Assert(index >= 0 && index < _custom.size());
  2390. return _custom[index];
  2391. }
  2392. return (set.id == Data::Stickers::FavedSetId);
  2393. }
  2394. void StickersListWidget::setSelected(OverState newSelected) {
  2395. if (_selected != newSelected) {
  2396. setCursor(!v::is_null(newSelected)
  2397. ? style::cur_pointer
  2398. : style::cur_default);
  2399. auto &sets = shownSets();
  2400. auto updateSelected = [&]() {
  2401. if (auto sticker = std::get_if<OverSticker>(&_selected)) {
  2402. rtlupdate(stickerRect(sticker->section, sticker->index));
  2403. } else if (auto button = std::get_if<OverButton>(&_selected)) {
  2404. if (button->section >= 0
  2405. && button->section < sets.size()
  2406. && sets[button->section].externalLayout) {
  2407. rtlupdate(featuredAddRect(button->section));
  2408. } else {
  2409. rtlupdate(removeButtonRect(button->section));
  2410. }
  2411. } else if (std::get_if<OverGroupAdd>(&_selected)) {
  2412. rtlupdate(megagroupSetButtonRectFinal());
  2413. }
  2414. };
  2415. updateSelected();
  2416. _selected = newSelected;
  2417. updateSelected();
  2418. if (_previewShown && _pressed != _selected) {
  2419. if (const auto sticker = std::get_if<OverSticker>(&_selected)) {
  2420. _pressed = _selected;
  2421. Assert(sticker->section >= 0 && sticker->section < sets.size());
  2422. const auto &set = sets[sticker->section];
  2423. Assert(sticker->index >= 0 && sticker->index < set.stickers.size());
  2424. const auto document = set.stickers[sticker->index].document;
  2425. _show->showMediaPreview(document->stickerSetOrigin(), document);
  2426. }
  2427. }
  2428. }
  2429. }
  2430. void StickersListWidget::showPreview() {
  2431. if (const auto sticker = std::get_if<OverSticker>(&_pressed)) {
  2432. const auto &sets = shownSets();
  2433. Assert(sticker->section >= 0 && sticker->section < sets.size());
  2434. const auto &set = sets[sticker->section];
  2435. Assert(sticker->index >= 0 && sticker->index < set.stickers.size());
  2436. const auto document = set.stickers[sticker->index].document;
  2437. _show->showMediaPreview(document->stickerSetOrigin(), document);
  2438. _previewShown = true;
  2439. }
  2440. }
  2441. auto StickersListWidget::getLottieRenderer()
  2442. -> std::shared_ptr<Lottie::FrameRenderer> {
  2443. if (auto result = _lottieRenderer.lock()) {
  2444. return result;
  2445. }
  2446. auto result = Lottie::MakeFrameRenderer();
  2447. _lottieRenderer = result;
  2448. return result;
  2449. }
  2450. void StickersListWidget::showStickerSet(uint64 setId) {
  2451. if (_showingSetById) {
  2452. return;
  2453. }
  2454. _showingSetById = true;
  2455. const auto guard = gsl::finally([&] { _showingSetById = false; });
  2456. clearSelection();
  2457. if (!_searchQuery.isEmpty() || !_searchNextQuery.isEmpty()) {
  2458. if (_search) {
  2459. _search->cancel();
  2460. }
  2461. cancelSetsSearch();
  2462. }
  2463. if (setId == Data::Stickers::FeaturedSetId) {
  2464. if (_section != Section::Featured) {
  2465. setSection(Section::Featured);
  2466. refreshRecentStickers(true);
  2467. refreshSettingsVisibility();
  2468. refreshIcons(ValidateIconAnimations::Scroll);
  2469. repaintItems();
  2470. }
  2471. scrollTo(0);
  2472. _scrollUpdated.fire({});
  2473. return;
  2474. }
  2475. auto needRefresh = (_section != Section::Stickers);
  2476. if (needRefresh) {
  2477. setSection(Section::Stickers);
  2478. refreshRecentStickers(true);
  2479. refreshSettingsVisibility();
  2480. }
  2481. auto y = 0;
  2482. enumerateSections([this, setId, &y](const SectionInfo &info) {
  2483. if (shownSets()[info.section].id == setId) {
  2484. y = info.section ? info.top : 0;
  2485. return false;
  2486. }
  2487. return true;
  2488. });
  2489. scrollTo(y);
  2490. _scrollUpdated.fire({});
  2491. if (needRefresh) {
  2492. refreshIcons(ValidateIconAnimations::Scroll);
  2493. }
  2494. _lastMousePosition = QCursor::pos();
  2495. repaintItems();
  2496. }
  2497. void StickersListWidget::refreshIcons(ValidateIconAnimations animations) {
  2498. if (_footer) {
  2499. _footer->refreshIcons(
  2500. fillIcons(),
  2501. currentSet(getVisibleTop()),
  2502. [=] { return getLottieRenderer(); },
  2503. animations);
  2504. }
  2505. }
  2506. void StickersListWidget::refreshMegagroupSetGeometry() {
  2507. auto left = megagroupSetInfoLeft();
  2508. auto availableWidth = (width() - left);
  2509. auto top = _megagroupSetAbout.countHeight(availableWidth) + st::stickerGroupCategoryAddMargin.top();
  2510. _megagroupSetButtonTextWidth = st::stickerGroupCategoryAdd.style.font->width(_megagroupSetButtonText);
  2511. auto buttonWidth = _megagroupSetButtonTextWidth - st::stickerGroupCategoryAdd.width;
  2512. _megagroupSetButtonRect = QRect(left, top, buttonWidth, st::stickerGroupCategoryAdd.height);
  2513. }
  2514. void StickersListWidget::showMegagroupSet(ChannelData *megagroup) {
  2515. Expects(!megagroup || megagroup->isMegagroup());
  2516. if (_megagroupSet != megagroup) {
  2517. _megagroupSet = megagroup;
  2518. if (_megagroupSetAbout.isEmpty()) {
  2519. _megagroupSetAbout.setText(
  2520. st::stickerGroupCategoryAbout,
  2521. tr::lng_group_stickers_description(tr::now));
  2522. _megagroupSetButtonText = tr::lng_group_stickers_add(tr::now);
  2523. refreshMegagroupSetGeometry();
  2524. }
  2525. _megagroupSetButtonRipple.reset();
  2526. refreshStickers();
  2527. }
  2528. }
  2529. void StickersListWidget::afterShown() {
  2530. if (_search) {
  2531. _search->stealFocus();
  2532. }
  2533. }
  2534. void StickersListWidget::beforeHiding() {
  2535. if (_search) {
  2536. _search->returnFocus();
  2537. }
  2538. }
  2539. void StickersListWidget::setupSearch() {
  2540. const auto session = &_show->session();
  2541. const auto type = (_mode == Mode::UserpicBuilder)
  2542. ? TabbedSearchType::ProfilePhoto
  2543. : (_mode == Mode::ChatIntro)
  2544. ? TabbedSearchType::Greeting
  2545. : TabbedSearchType::Stickers;
  2546. _search = MakeSearch(this, st(), [=](std::vector<QString> &&query) {
  2547. applySearchQuery(std::move(query));
  2548. }, session, type);
  2549. }
  2550. void StickersListWidget::applySearchQuery(std::vector<QString> &&query) {
  2551. auto set = base::flat_set<EmojiPtr>();
  2552. auto text = ranges::accumulate(query, QString(), [](
  2553. QString a,
  2554. QString b) {
  2555. return a.isEmpty() ? b : (a + ' ' + b);
  2556. });
  2557. searchForSets(std::move(text), SearchEmoji(query, set));
  2558. }
  2559. void StickersListWidget::displaySet(uint64 setId) {
  2560. if (setId == Data::Stickers::MegagroupSetId) {
  2561. if (_megagroupSet->canEditStickers()) {
  2562. const auto isEmoji = false;
  2563. checkHideWithBox(Box<StickersBox>(_show, _megagroupSet, isEmoji));
  2564. return;
  2565. } else if (_megagroupSet->mgInfo->stickerSet.id) {
  2566. setId = _megagroupSet->mgInfo->stickerSet.id;
  2567. } else {
  2568. return;
  2569. }
  2570. }
  2571. const auto &sets = session().data().stickers().sets();
  2572. auto it = sets.find(setId);
  2573. if (it != sets.cend()) {
  2574. checkHideWithBox(Box<StickerSetBox>(_show, it->second.get()));
  2575. }
  2576. }
  2577. void StickersListWidget::removeMegagroupSet(bool locally) {
  2578. if (locally) {
  2579. session().settings().setGroupStickersSectionHidden(_megagroupSet->id);
  2580. session().saveSettings();
  2581. refreshStickers();
  2582. return;
  2583. }
  2584. checkHideWithBox(Ui::MakeConfirmBox({
  2585. .text = tr::lng_stickers_remove_group_set(),
  2586. .confirmed = crl::guard(this, [this, group = _megagroupSet](
  2587. Fn<void()> &&close) {
  2588. Expects(group->mgInfo != nullptr);
  2589. if (group->mgInfo->stickerSet) {
  2590. session().api().setGroupStickerSet(group, {});
  2591. }
  2592. close();
  2593. }),
  2594. .cancelled = [](Fn<void()> &&close) { close(); },
  2595. .labelStyle = &st().boxLabel,
  2596. }));
  2597. }
  2598. void StickersListWidget::removeSet(uint64 setId) {
  2599. const auto &st = this->st().boxLabel;
  2600. if (setId == Data::Stickers::MegagroupSetId) {
  2601. const auto &sets = shownSets();
  2602. const auto i = ranges::find(sets, setId, &Set::id);
  2603. Assert(i != end(sets));
  2604. const auto removeLocally = i->stickers.empty()
  2605. || !_megagroupSet->canEditStickers();
  2606. removeMegagroupSet(removeLocally);
  2607. } else if (auto box = MakeConfirmRemoveSetBox(&session(), st, setId)) {
  2608. checkHideWithBox(std::move(box));
  2609. }
  2610. }
  2611. const Data::StickersSetsOrder &StickersListWidget::defaultSetsOrder() const {
  2612. return _isMasks
  2613. ? session().data().stickers().maskSetsOrder()
  2614. : session().data().stickers().setsOrder();
  2615. }
  2616. Data::StickersSetsOrder &StickersListWidget::defaultSetsOrderRef() {
  2617. return _isMasks
  2618. ? session().data().stickers().maskSetsOrderRef()
  2619. : session().data().stickers().setsOrderRef();
  2620. }
  2621. bool StickersListWidget::mySetsEmpty() const {
  2622. return _mySets.empty();
  2623. }
  2624. void StickersListWidget::filterEffectsByEmoji(
  2625. const std::vector<EmojiPtr> &emoji) {
  2626. _filteredStickers.clear();
  2627. _filterStickersCornerEmoji.clear();
  2628. if (_mySets.empty()
  2629. || _mySets.front().id != Data::Stickers::RecentSetId
  2630. || _mySets.front().stickers.empty()) {
  2631. return;
  2632. }
  2633. const auto &list = _mySets.front().stickers;
  2634. auto all = base::flat_set<EmojiPtr>();
  2635. for (const auto &one : emoji) {
  2636. all.emplace(one->original());
  2637. }
  2638. const auto count = int(list.size());
  2639. _filteredStickers.reserve(count);
  2640. _filterStickersCornerEmoji.reserve(count);
  2641. for (auto i = 0; i != count; ++i) {
  2642. Assert(i < _cornerEmoji.size());
  2643. if (all.contains(_cornerEmoji[i])) {
  2644. _filteredStickers.push_back(list[i].document);
  2645. _filterStickersCornerEmoji.push_back(_cornerEmoji[i]);
  2646. }
  2647. }
  2648. }
  2649. StickersListWidget::~StickersListWidget() = default;
  2650. object_ptr<Ui::BoxContent> MakeConfirmRemoveSetBox(
  2651. not_null<Main::Session*> session,
  2652. const style::FlatLabel &st,
  2653. uint64 setId) {
  2654. const auto &sets = session->data().stickers().sets();
  2655. const auto it = sets.find(setId);
  2656. if (it == sets.cend()) {
  2657. return nullptr;
  2658. }
  2659. const auto set = it->second.get();
  2660. const auto text = tr::lng_stickers_remove_pack(
  2661. tr::now,
  2662. lt_sticker_pack,
  2663. set->title);
  2664. return Ui::MakeConfirmBox({
  2665. .text = text,
  2666. .confirmed = [=](Fn<void()> &&close) {
  2667. close();
  2668. const auto &sets = session->data().stickers().sets();
  2669. const auto it = sets.find(setId);
  2670. if (it != sets.cend()) {
  2671. const auto set = it->second.get();
  2672. if (set->id && set->accessHash) {
  2673. session->api().request(MTPmessages_UninstallStickerSet(
  2674. MTP_inputStickerSetID(
  2675. MTP_long(set->id),
  2676. MTP_long(set->accessHash)))
  2677. ).send();
  2678. } else if (!set->shortName.isEmpty()) {
  2679. session->api().request(MTPmessages_UninstallStickerSet(
  2680. MTP_inputStickerSetShortName(
  2681. MTP_string(set->shortName)))
  2682. ).send();
  2683. }
  2684. auto writeRecent = false;
  2685. auto &recent = session->data().stickers().getRecentPack();
  2686. for (auto i = recent.begin(); i != recent.cend();) {
  2687. if (set->stickers.indexOf(i->first) >= 0) {
  2688. i = recent.erase(i);
  2689. writeRecent = true;
  2690. } else {
  2691. ++i;
  2692. }
  2693. }
  2694. set->flags &= ~SetFlag::Installed;
  2695. set->installDate = TimeId(0);
  2696. auto &orderRef = (set->type() == Data::StickersType::Emoji)
  2697. ? session->data().stickers().emojiSetsOrderRef()
  2698. : (set->type() == Data::StickersType::Masks)
  2699. ? session->data().stickers().maskSetsOrderRef()
  2700. : session->data().stickers().setsOrderRef();
  2701. const auto removeIndex = orderRef.indexOf(setId);
  2702. if (removeIndex >= 0) {
  2703. orderRef.removeAt(removeIndex);
  2704. }
  2705. if (set->type() == Data::StickersType::Emoji) {
  2706. session->local().writeInstalledCustomEmoji();
  2707. } else if (set->type() == Data::StickersType::Masks) {
  2708. session->local().writeInstalledMasks();
  2709. } else {
  2710. session->local().writeInstalledStickers();
  2711. }
  2712. if (writeRecent) {
  2713. session->saveSettings();
  2714. }
  2715. session->data().stickers().notifyUpdated(set->type());
  2716. }
  2717. },
  2718. .confirmText = tr::lng_stickers_remove_pack_confirm(),
  2719. .labelStyle = &st,
  2720. });
  2721. }
  2722. } // namespace ChatHelpers