dialogs_widget.cpp 116 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199
  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 "dialogs/dialogs_widget.h"
  8. #include "base/call_delayed.h"
  9. #include "base/qt/qt_key_modifiers.h"
  10. #include "base/options.h"
  11. #include "dialogs/ui/chat_search_in.h"
  12. #include "dialogs/ui/dialogs_stories_content.h"
  13. #include "dialogs/ui/dialogs_stories_list.h"
  14. #include "dialogs/ui/dialogs_suggestions.h"
  15. #include "dialogs/dialogs_inner_widget.h"
  16. #include "dialogs/dialogs_search_from_controllers.h"
  17. #include "dialogs/dialogs_quick_action.h"
  18. #include "dialogs/dialogs_key.h"
  19. #include "history/history.h"
  20. #include "history/history_item.h"
  21. #include "history/view/history_view_top_bar_widget.h"
  22. #include "history/view/history_view_contact_status.h"
  23. #include "history/view/history_view_requests_bar.h"
  24. #include "history/view/history_view_group_call_bar.h"
  25. #include "boxes/peers/edit_peer_requests_box.h"
  26. #include "ui/text/text_utilities.h"
  27. #include "ui/widgets/buttons.h"
  28. #include "ui/widgets/chat_filters_tabs_strip.h"
  29. #include "ui/widgets/elastic_scroll.h"
  30. #include "ui/widgets/fields/input_field.h"
  31. #include "ui/wrap/fade_wrap.h"
  32. #include "ui/effects/radial_animation.h"
  33. #include "ui/chat/requests_bar.h"
  34. #include "ui/chat/group_call_bar.h"
  35. #include "ui/chat/more_chats_bar.h"
  36. #include "ui/controls/download_bar.h"
  37. #include "ui/controls/jump_down_button.h"
  38. #include "ui/controls/swipe_handler.h"
  39. #include "ui/painter.h"
  40. #include "ui/rect.h"
  41. #include "ui/ui_utility.h"
  42. #include "lang/lang_keys.h"
  43. #include "mainwindow.h"
  44. #include "mainwidget.h"
  45. #include "main/main_domain.h"
  46. #include "main/main_session.h"
  47. #include "main/main_session_settings.h"
  48. #include "api/api_chat_filters.h"
  49. #include "apiwrap.h"
  50. #include "core/application.h"
  51. #include "core/ui_integration.h"
  52. #include "core/update_checker.h"
  53. #include "core/shortcuts.h"
  54. #include "window/window_controller.h"
  55. #include "window/window_session_controller.h"
  56. #include "window/window_slide_animation.h"
  57. #include "window/window_connecting_widget.h"
  58. #include "window/window_main_menu.h"
  59. #include "storage/storage_media_prepare.h"
  60. #include "storage/storage_account.h"
  61. #include "storage/storage_domain.h"
  62. #include "data/components/recent_peers.h"
  63. #include "data/data_session.h"
  64. #include "data/data_channel.h"
  65. #include "data/data_chat.h"
  66. #include "data/stickers/data_custom_emoji.h"
  67. #include "data/data_user.h"
  68. #include "data/data_folder.h"
  69. #include "data/data_forum.h"
  70. #include "data/data_forum_topic.h"
  71. #include "data/data_histories.h"
  72. #include "data/data_changes.h"
  73. #include "data/data_download_manager.h"
  74. #include "data/data_chat_filters.h"
  75. #include "data/data_saved_sublist.h"
  76. #include "data/data_stories.h"
  77. #include "info/downloads/info_downloads_widget.h"
  78. #include "info/info_memento.h"
  79. #include "inline_bots/bot_attach_web_view.h"
  80. #include "styles/style_dialogs.h"
  81. #include "styles/style_chat.h"
  82. #include "styles/style_chat_helpers.h"
  83. #include "styles/style_info.h"
  84. #include "styles/style_window.h"
  85. #include "base/qt/qt_common_adapters.h"
  86. #include <QtCore/QMimeData>
  87. #include <QtGui/QTextBlock>
  88. #include <QtWidgets/QScrollBar>
  89. #include <QtWidgets/QTextEdit>
  90. namespace Dialogs {
  91. namespace {
  92. constexpr auto kSearchPerPage = 50;
  93. constexpr auto kStoriesExpandDuration = crl::time(200);
  94. constexpr auto kSearchRequestDelay = crl::time(900);
  95. base::options::toggle OptionForumHideChatsList({
  96. .id = kOptionForumHideChatsList,
  97. .name = "Hide chats list in forums",
  98. .description = "Don't keep a narrow column of chats list.",
  99. });
  100. [[nodiscard]] bool RedirectTextToSearch(const QString &text) {
  101. for (const auto &ch : text) {
  102. if (ch.unicode() >= 32) {
  103. return true;
  104. }
  105. }
  106. return false;
  107. }
  108. [[nodiscard]] QImage UpdateIcon() {
  109. const auto iconSize = st::dialogsInstallUpdateIconSize;
  110. auto result = QImage(
  111. Size(iconSize) * style::DevicePixelRatio(),
  112. QImage::Format_ARGB32_Premultiplied);
  113. result.fill(Qt::transparent);
  114. {
  115. auto p = QPainter(&result);
  116. auto hq = PainterHighQualityEnabler(p);
  117. auto path = QPainterPath();
  118. const auto fullRect = QRectF(0, 0, iconSize, iconSize);
  119. const auto rect = fullRect
  120. - Margins(st::dialogsInstallUpdateIconInnerMargin);
  121. p.setPen(Qt::NoPen);
  122. p.setBrush(Qt::white);
  123. p.drawEllipse(fullRect);
  124. p.setCompositionMode(QPainter::CompositionMode_Clear);
  125. auto pen = QPen(Qt::black);
  126. pen.setWidthF(style::ConvertFloatScale(2.));
  127. pen.setCapStyle(Qt::RoundCap);
  128. p.setPen(pen);
  129. using namespace arc;
  130. constexpr auto kShift = int(20 * 16);
  131. p.drawArc(rect, -kShift, kQuarterLength + kShift);
  132. p.drawArc(rect, kHalfLength - kShift, kQuarterLength + kShift);
  133. const auto side1 = st::dialogsInstallUpdateIconSide1;
  134. const auto side2 = st::dialogsInstallUpdateIconSide2;
  135. const auto top = rect.y() - side1;
  136. const auto bottom = rect::bottom(rect) - side1;
  137. const auto centerX = rect::center(rect).x();
  138. path.moveTo(centerX, bottom + side1 + side2);
  139. path.lineTo(centerX, bottom + side1 - side2);
  140. path.lineTo(centerX + side2, bottom + side1);
  141. path.closeSubpath();
  142. path.moveTo(centerX, top + side1 + side2);
  143. path.lineTo(centerX, top + side1 - side2);
  144. path.lineTo(centerX - side2, top + side1);
  145. path.closeSubpath();
  146. p.fillPath(path, Qt::black);
  147. }
  148. return result;
  149. }
  150. } // namespace
  151. const char kOptionForumHideChatsList[] = "forum-hide-chats-list";
  152. class Widget::BottomButton : public Ui::RippleButton {
  153. public:
  154. BottomButton(
  155. QWidget *parent,
  156. const QString &text,
  157. const style::FlatButton &st,
  158. const style::icon &icon,
  159. const style::icon &iconOver,
  160. bool hasTextIcon);
  161. void setText(const QString &text);
  162. protected:
  163. void paintEvent(QPaintEvent *e) override;
  164. void onStateChanged(State was, StateChangeSource source) override;
  165. private:
  166. void radialAnimationCallback();
  167. QString _text;
  168. const style::FlatButton &_st;
  169. const style::icon &_icon;
  170. const style::icon &_iconOver;
  171. const bool _hasTextIcon;
  172. std::unique_ptr<Ui::InfiniteRadialAnimation> _loading;
  173. QImage _textIcon;
  174. };
  175. Widget::BottomButton::BottomButton(
  176. QWidget *parent,
  177. const QString &text,
  178. const style::FlatButton &st,
  179. const style::icon &icon,
  180. const style::icon &iconOver,
  181. bool hasTextIcon)
  182. : RippleButton(parent, st.ripple)
  183. , _text(text)
  184. , _st(st)
  185. , _icon(icon)
  186. , _iconOver(iconOver)
  187. , _hasTextIcon(hasTextIcon) {
  188. resize(st::columnMinimalWidthLeft, _st.height);
  189. if (_hasTextIcon) {
  190. rpl::single(rpl::empty_value()) | rpl::then(
  191. style::PaletteChanged()
  192. ) | rpl::start_with_next([this] {
  193. _textIcon = UpdateIcon();
  194. }, lifetime());
  195. }
  196. }
  197. void Widget::BottomButton::setText(const QString &text) {
  198. _text = text;
  199. update();
  200. }
  201. void Widget::BottomButton::radialAnimationCallback() {
  202. if (!anim::Disabled() && width() < st::columnMinimalWidthLeft) {
  203. update();
  204. }
  205. }
  206. void Widget::BottomButton::onStateChanged(
  207. State was,
  208. StateChangeSource source) {
  209. RippleButton::onStateChanged(was, source);
  210. if ((was & StateFlag::Disabled) != (state() & StateFlag::Disabled)) {
  211. _loading = isDisabled()
  212. ? std::make_unique<Ui::InfiniteRadialAnimation>(
  213. [=] { radialAnimationCallback(); },
  214. st::dialogsLoadMoreLoading)
  215. : nullptr;
  216. if (_loading) {
  217. _loading->start();
  218. }
  219. }
  220. update();
  221. }
  222. void Widget::BottomButton::paintEvent(QPaintEvent *e) {
  223. auto p = QPainter(this);
  224. const auto over = isOver() && !isDisabled();
  225. auto r = QRect(0, height() - _st.height, width(), _st.height);
  226. if (_hasTextIcon) {
  227. auto gradient = QLinearGradient(0, 0, width(), 0);
  228. gradient.setStops({
  229. { 0., st::groupCallLive1->c },
  230. { 1., st::groupCallLive2->c },
  231. });
  232. p.fillRect(r, QBrush(std::move(gradient)));
  233. if (over) {
  234. p.fillRect(
  235. r,
  236. anim::with_alpha(st::universalRippleAnimation.color->c, .3));
  237. }
  238. if (!isDisabled()) {
  239. paintRipple(p, 0, 0, &st::universalRippleAnimation.color->c);
  240. }
  241. } else {
  242. p.fillRect(r, over ? _st.overBgColor : _st.bgColor);
  243. if (!isDisabled()) {
  244. paintRipple(p, 0, 0);
  245. }
  246. }
  247. const auto &font = over ? _st.overFont : _st.font;
  248. p.setFont(font);
  249. p.setRenderHint(QPainter::TextAntialiasing);
  250. p.setPen(over ? _st.overColor : _st.color);
  251. if (width() >= st::columnMinimalWidthLeft) {
  252. r.setTop(_st.textTop);
  253. if (_hasTextIcon) {
  254. const auto &icon = _textIcon;
  255. const auto iconSize = icon.size() / style::DevicePixelRatio();
  256. const auto skip = st::dialogsInstallUpdateIconSkip;
  257. const auto textWidth = font->width(_text);
  258. const auto rect = QRect(
  259. (width() - (iconSize.width() + textWidth + skip)) / 2,
  260. r.y(),
  261. textWidth,
  262. r.height());
  263. p.drawText(
  264. rect.translated(iconSize.width() + skip, 0),
  265. _text,
  266. style::al_top);
  267. p.drawImage(rect.x(), (height() - iconSize.height()) / 2, icon);
  268. } else {
  269. p.drawText(r, _text, style::al_top);
  270. }
  271. } else if (isDisabled() && _loading) {
  272. _loading->draw(
  273. p,
  274. QPoint(
  275. (width() - st::dialogsLoadMoreLoading.size.width()) / 2,
  276. (height() - st::dialogsLoadMoreLoading.size.height()) / 2),
  277. width());
  278. } else {
  279. if (_hasTextIcon) {
  280. const auto size = _textIcon.size() / style::DevicePixelRatio();
  281. p.drawImage(
  282. (width() - size.width()) / 2,
  283. (height() - size.height()) / 2,
  284. _textIcon);
  285. } else {
  286. (over ? _iconOver : _icon).paintInCenter(p, r);
  287. }
  288. }
  289. }
  290. Widget::Widget(
  291. QWidget *parent,
  292. not_null<Window::SessionController*> controller,
  293. Layout layout)
  294. : Window::AbstractSectionWidget(parent, controller, nullptr)
  295. , _api(&controller->session().mtp())
  296. , _chooseByDragTimer([=] { _inner->chooseRow(); })
  297. , _layout(layout)
  298. , _narrowWidth(st::defaultDialogRow.padding.left()
  299. + st::defaultDialogRow.photoSize
  300. + st::defaultDialogRow.padding.left())
  301. , _searchControls(this)
  302. , _mainMenu({
  303. .toggle = object_ptr<Ui::IconButton>(
  304. _searchControls,
  305. st::dialogsMenuToggle),
  306. .under = object_ptr<Ui::AbstractButton>(_searchControls),
  307. })
  308. , _searchForNarrowLayout(_searchControls, st::dialogsSearchForNarrowFilters)
  309. , _search(_searchControls, st::dialogsFilter, tr::lng_dlg_filter())
  310. , _chooseFromUser(
  311. _searchControls,
  312. object_ptr<Ui::IconButton>(this, st::dialogsSearchFrom))
  313. , _jumpToDate(
  314. _searchControls,
  315. object_ptr<Ui::IconButton>(this, st::dialogsCalendar))
  316. , _cancelSearch(_searchControls, st::dialogsCancelSearch)
  317. , _lockUnlock(
  318. _searchControls,
  319. object_ptr<Ui::IconButton>(this, st::dialogsLock))
  320. , _scroll(this)
  321. , _scrollToTop(_scroll, st::dialogsToUp)
  322. , _stories((_layout != Layout::Child)
  323. ? std::make_unique<Stories::List>(
  324. this,
  325. st::dialogsStoriesList,
  326. _storiesContents.events() | rpl::flatten_latest())
  327. : nullptr)
  328. , _searchTimer([=] { search(); })
  329. , _singleMessageSearch(&controller->session()) {
  330. const auto makeChildListShown = [](PeerId peerId, float64 shown) {
  331. return InnerWidget::ChildListShown{ peerId, shown };
  332. };
  333. using OverscrollType = Ui::ElasticScroll::OverscrollType;
  334. _scroll->setOverscrollTypes(
  335. _stories ? OverscrollType::Virtual : OverscrollType::Real,
  336. OverscrollType::Real);
  337. _inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(
  338. this,
  339. controller,
  340. rpl::combine(
  341. _childListPeerId.value(),
  342. _childListShown.value(),
  343. makeChildListShown)));
  344. _scrollToTop->raise();
  345. _lockUnlock->toggle(false, anim::type::instant);
  346. _inner->updated(
  347. ) | rpl::start_with_next([=] {
  348. listScrollUpdated();
  349. }, lifetime());
  350. rpl::combine(
  351. session().api().dialogsLoadMayBlockByDate(),
  352. session().api().dialogsLoadBlockedByDate()
  353. ) | rpl::start_with_next([=](bool mayBlock, bool isBlocked) {
  354. refreshLoadMoreButton(mayBlock, isBlocked);
  355. }, lifetime());
  356. session().changes().historyUpdates(
  357. Data::HistoryUpdate::Flag::MessageSent
  358. ) | rpl::filter([=](const Data::HistoryUpdate &update) {
  359. if (_openedForum) {
  360. return (update.history == _openedForum->history());
  361. } else if (_openedFolder) {
  362. return (update.history->folder() == _openedFolder)
  363. && !update.history->isPinnedDialog(FilterId());
  364. } else {
  365. return !update.history->folder()
  366. && !update.history->isPinnedDialog(
  367. controller->activeChatsFilterCurrent());
  368. }
  369. }) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
  370. jumpToTop(true);
  371. }, lifetime());
  372. fullSearchRefreshOn(session().settings().skipArchiveInSearchChanges(
  373. ) | rpl::to_empty);
  374. _inner->scrollByDeltaRequests(
  375. ) | rpl::start_with_next([=](int delta) {
  376. if (_scroll) {
  377. _scroll->scrollToY(_scroll->scrollTop() + delta);
  378. }
  379. }, lifetime());
  380. _inner->mustScrollTo(
  381. ) | rpl::start_with_next([=](const Ui::ScrollToRequest &data) {
  382. if (_scroll) {
  383. _scroll->scrollToY(data.ymin, data.ymax);
  384. }
  385. }, lifetime());
  386. _inner->dialogMoved(
  387. ) | rpl::start_with_next([=](const Ui::ScrollToRequest &data) {
  388. const auto movedFrom = data.ymin;
  389. const auto movedTo = data.ymax;
  390. const auto st = _scroll->scrollTop();
  391. if (st > movedTo && st < movedFrom) {
  392. _scroll->scrollToY(st + _inner->st()->height);
  393. }
  394. }, lifetime());
  395. _inner->searchRequests(
  396. ) | rpl::start_with_next([=](SearchRequestDelay delay) {
  397. searchRequested(delay);
  398. }, lifetime());
  399. _inner->completeHashtagRequests(
  400. ) | rpl::start_with_next([=](const QString &tag) {
  401. completeHashtag(tag);
  402. }, lifetime());
  403. _inner->refreshHashtagsRequests(
  404. ) | rpl::start_with_next([=] {
  405. searchCursorMoved();
  406. }, lifetime());
  407. _inner->changeSearchTabRequests(
  408. ) | rpl::filter([=](ChatSearchTab tab) {
  409. return _searchState.tab != tab;
  410. }) | rpl::start_with_next([=](ChatSearchTab tab) {
  411. auto copy = _searchState;
  412. copy.tab = tab;
  413. applySearchState(std::move(copy));
  414. }, lifetime());
  415. _inner->changeSearchFilterRequests(
  416. ) | rpl::filter([=](ChatTypeFilter filter) {
  417. return (_searchState.filter != filter)
  418. && (_searchState.tab == ChatSearchTab::MyMessages);
  419. }) | rpl::start_with_next([=](ChatTypeFilter filter) {
  420. auto copy = _searchState;
  421. copy.filter = filter;
  422. applySearchState(copy);
  423. }, lifetime());
  424. _inner->cancelSearchRequests(
  425. ) | rpl::start_with_next([=] {
  426. cancelSearch({
  427. .forceFullCancel = true,
  428. .jumpBackToSearchedChat = true,
  429. });
  430. controller->widget()->setInnerFocus();
  431. }, lifetime());
  432. _inner->cancelSearchFromRequests(
  433. ) | rpl::start_with_next([=] {
  434. auto copy = _searchState;
  435. copy.fromPeer = nullptr;
  436. if (copy.inChat.sublist()) {
  437. copy.inChat = session().data().history(session().user());
  438. }
  439. applySearchState(std::move(copy));
  440. }, lifetime());
  441. _inner->changeSearchFromRequests(
  442. ) | rpl::start_with_next([=] {
  443. showSearchFrom();
  444. }, lifetime());
  445. _inner->chosenRow(
  446. ) | rpl::start_with_next([=](const ChosenRow &row) {
  447. chosenRow(row);
  448. }, lifetime());
  449. _inner->openBotMainAppRequests(
  450. ) | rpl::start_with_next([=](UserId userId) {
  451. if (const auto user = session().data().user(userId)) {
  452. openBotMainApp(user);
  453. }
  454. }, lifetime());
  455. _scroll->geometryChanged(
  456. ) | rpl::start_with_next(crl::guard(_inner, [=] {
  457. _inner->parentGeometryChanged();
  458. }), lifetime());
  459. _scroll->scrolls(
  460. ) | rpl::start_with_next([=] {
  461. listScrollUpdated();
  462. }, lifetime());
  463. session().data().chatsListChanges(
  464. ) | rpl::filter([=](Data::Folder *folder) {
  465. return (folder == _inner->shownFolder());
  466. }) | rpl::start_with_next([=] {
  467. Ui::PostponeCall(this, [=] { listScrollUpdated(); });
  468. }, lifetime());
  469. setAttribute(Qt::WA_InputMethodEnabled);
  470. controller->widget()->imeCompositionStarts(
  471. ) | rpl::filter([=] {
  472. return redirectImeToSearch();
  473. }) | rpl::start_with_next([=] {
  474. _search->setFocusFast();
  475. }, lifetime());
  476. _search->changes(
  477. ) | rpl::start_with_next([=] {
  478. crl::on_main(this, [=] { applySearchUpdate(); });
  479. }, _search->lifetime());
  480. _search->submits(
  481. ) | rpl::start_with_next([=] { submit(); }, _search->lifetime());
  482. QObject::connect(
  483. _search->rawTextEdit().get(),
  484. &QTextEdit::cursorPositionChanged,
  485. this,
  486. [=] { searchCursorMoved(); },
  487. Qt::QueuedConnection); // So getLastText() works already.
  488. if (!Core::UpdaterDisabled()) {
  489. Core::UpdateChecker checker;
  490. rpl::merge(
  491. rpl::single(rpl::empty),
  492. checker.isLatest(),
  493. checker.failed(),
  494. checker.ready()
  495. ) | rpl::start_with_next([=] {
  496. checkUpdateStatus();
  497. }, lifetime());
  498. }
  499. _cancelSearch->setClickedCallback([=] {
  500. cancelSearch({ .jumpBackToSearchedChat = true });
  501. });
  502. _jumpToDate->entity()->setClickedCallback([=] { showCalendar(); });
  503. _chooseFromUser->entity()->setClickedCallback([=] { showSearchFrom(); });
  504. rpl::single(rpl::empty) | rpl::then(
  505. session().domain().local().localPasscodeChanged()
  506. ) | rpl::start_with_next([=] {
  507. updateLockUnlockVisibility();
  508. }, lifetime());
  509. const auto lockUnlock = _lockUnlock->entity();
  510. lockUnlock->setClickedCallback([=] {
  511. lockUnlock->setIconOverride(
  512. &st::dialogsUnlockIcon,
  513. &st::dialogsUnlockIconOver);
  514. Core::App().maybeLockByPasscode();
  515. lockUnlock->setIconOverride(nullptr);
  516. });
  517. setupMainMenuToggle();
  518. setupShortcuts();
  519. if (_stories) {
  520. setupStories();
  521. }
  522. _searchForNarrowLayout->setClickedCallback([=] {
  523. _search->setFocusFast();
  524. if (_childList) {
  525. controller->closeForum();
  526. }
  527. });
  528. setAcceptDrops(true);
  529. _inner->setLoadMoreFilteredCallback([=] {
  530. const auto state = _inner->state();
  531. if (state == WidgetState::Filtered
  532. && !_topicSearchFull
  533. && searchForTopicsRequired(_topicSearchQuery)) {
  534. searchTopics();
  535. }
  536. });
  537. _inner->setLoadMoreCallback([=] {
  538. const auto state = _inner->state();
  539. const auto process = currentSearchProcess();
  540. if (state == WidgetState::Filtered
  541. && (!process->full
  542. || (_searchInMigrated && !_migratedProcess.full))) {
  543. searchMore();
  544. } else if (_openedForum && state == WidgetState::Default) {
  545. _openedForum->requestTopics();
  546. } else {
  547. const auto folder = _inner->shownFolder();
  548. if (!folder || !folder->chatsList()->loaded()) {
  549. session().api().requestDialogs(folder);
  550. }
  551. }
  552. });
  553. _inner->listBottomReached(
  554. ) | rpl::start_with_next([=] {
  555. loadMoreBlockedByDate();
  556. }, lifetime());
  557. _search->customUpDown(true);
  558. updateJumpToDateVisibility(true);
  559. updateSearchFromVisibility(true);
  560. setupSupportMode();
  561. setupScrollUpButton();
  562. setupTouchChatPreview();
  563. const auto overscrollBg = [=] {
  564. return anim::color(
  565. st::dialogsBg,
  566. st::dialogsBgOver,
  567. _childListShown.current());
  568. };
  569. _scroll->setOverscrollBg(overscrollBg());
  570. style::PaletteChanged(
  571. ) | rpl::start_with_next([=] {
  572. _scroll->setOverscrollBg(overscrollBg());
  573. }, lifetime());
  574. if (_layout != Layout::Child) {
  575. setupConnectingWidget();
  576. changeOpenedFolder(
  577. controller->openedFolder().current(),
  578. anim::type::instant);
  579. controller->openedFolder().changes(
  580. ) | rpl::start_with_next([=](Data::Folder *folder) {
  581. changeOpenedFolder(folder, anim::type::normal);
  582. }, lifetime());
  583. controller->shownForum().changes(
  584. ) | rpl::filter(!rpl::mappers::_1) | rpl::start_with_next([=] {
  585. if (_openedForum) {
  586. changeOpenedForum(nullptr, anim::type::normal);
  587. } else if (_childList) {
  588. closeChildList(anim::type::normal);
  589. }
  590. }, lifetime());
  591. _childListShown.changes(
  592. ) | rpl::start_with_next([=] {
  593. _scroll->setOverscrollBg(overscrollBg());
  594. updateControlsGeometry();
  595. }, lifetime());
  596. _childListShown.changes(
  597. ) | rpl::filter((rpl::mappers::_1 == 0.) || (rpl::mappers::_1 == 1.)
  598. ) | rpl::start_with_next([=](float64 shown) {
  599. const auto color = (shown > 0.) ? &st::dialogsRippleBg : nullptr;
  600. _mainMenu.toggle->setRippleColorOverride(color);
  601. _searchForNarrowLayout->setRippleColorOverride(color);
  602. }, lifetime());
  603. setupMoreChatsBar();
  604. setupDownloadBar();
  605. }
  606. setupSwipeBack();
  607. if (session().settings().dialogsFiltersEnabled()
  608. && (Core::App().settings().chatFiltersHorizontal()
  609. || !controller->enoughSpaceForFilters())) {
  610. toggleFiltersMenu(true);
  611. }
  612. }
  613. void Widget::setupSwipeBack() {
  614. const auto isMainList = [=] {
  615. const auto current = controller()->activeChatsFilterCurrent();
  616. const auto &chatsFilters = session().data().chatsFilters();
  617. if (chatsFilters.has()) {
  618. return chatsFilters.defaultId() == current;
  619. }
  620. return !current;
  621. };
  622. Ui::Controls::SetupSwipeHandler(_inner, _scroll.data(), [=](
  623. Ui::Controls::SwipeContextData data) {
  624. if (data.translation != 0) {
  625. if (data.translation < 0
  626. && _inner
  627. && (Core::App().settings().quickDialogAction()
  628. != Ui::QuickDialogAction::Disabled)) {
  629. _inner->setSwipeContextData(data.msgBareId, std::move(data));
  630. } else {
  631. if (!_swipeBackData.callback) {
  632. _swipeBackData = Ui::Controls::SetupSwipeBack(
  633. this,
  634. []() -> std::pair<QColor, QColor> {
  635. return {
  636. st::historyForwardChooseBg->c,
  637. st::historyForwardChooseFg->c,
  638. };
  639. },
  640. _swipeBackMirrored,
  641. _swipeBackIconMirrored);
  642. }
  643. _swipeBackData.callback(data);
  644. }
  645. return;
  646. } else {
  647. if (_swipeBackData.lifetime) {
  648. _swipeBackData = {};
  649. }
  650. if (_inner) {
  651. _inner->setSwipeContextData(data.msgBareId, std::nullopt);
  652. _inner->update();
  653. }
  654. }
  655. }, [=](int top, Qt::LayoutDirection direction) {
  656. _swipeBackIconMirrored = false;
  657. _swipeBackMirrored = false;
  658. if (_childListShown.current()) {
  659. return Ui::Controls::SwipeHandlerFinishData();
  660. }
  661. const auto isRightToLeft = direction == Qt::RightToLeft;
  662. const auto action = Core::App().settings().quickDialogAction();
  663. const auto isDisabled = action == Ui::QuickDialogAction::Disabled;
  664. if (!isRightToLeft && _inner) {
  665. if (const auto key = _inner->calcSwipeKey(top);
  666. key && !isDisabled) {
  667. _inner->prepareQuickAction(key, action);
  668. return Ui::Controls::SwipeHandlerFinishData{
  669. .callback = [=, session = &session()] {
  670. auto callback = [=, peerId = PeerId(key)] {
  671. const auto peer = session->data().peer(peerId);
  672. PerformQuickDialogAction(
  673. controller(),
  674. peer,
  675. action,
  676. _inner->filterId());
  677. };
  678. base::call_delayed(
  679. st::slideWrapDuration,
  680. session,
  681. std::move(callback));
  682. },
  683. .msgBareId = key,
  684. .speedRatio = 1.,
  685. .reachRatioDuration = crl::time(st::slideWrapDuration),
  686. .provideReachOutRatio = true,
  687. };
  688. }
  689. }
  690. if (controller()->openedFolder().current()) {
  691. if (!isRightToLeft) {
  692. return Ui::Controls::SwipeHandlerFinishData();
  693. }
  694. return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
  695. _swipeBackData = {};
  696. if (controller()->openedFolder().current()) {
  697. if (!controller()->windowId().folder()) {
  698. controller()->closeFolder();
  699. }
  700. }
  701. });
  702. }
  703. if (controller()->shownForum().current()) {
  704. if (!isRightToLeft) {
  705. return Ui::Controls::SwipeHandlerFinishData();
  706. }
  707. const auto id = controller()->windowId();
  708. const auto initial = id.forum();
  709. if (initial) {
  710. return Ui::Controls::SwipeHandlerFinishData();
  711. }
  712. return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
  713. _swipeBackData = {};
  714. if (const auto forum = controller()->shownForum().current()) {
  715. controller()->closeForum();
  716. }
  717. });
  718. }
  719. if (isRightToLeft && isMainList()) {
  720. _swipeBackIconMirrored = true;
  721. return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
  722. _swipeBackIconMirrored = false;
  723. _swipeBackData = {};
  724. if (isMainList()) {
  725. showMainMenu();
  726. }
  727. });
  728. }
  729. if (session().data().chatsFilters().has() && isDisabled) {
  730. _swipeBackMirrored = !isRightToLeft;
  731. using namespace Window;
  732. const auto next = !isRightToLeft;
  733. if (CheckAndJumpToNearChatsFilter(controller(), next, false)) {
  734. return Ui::Controls::DefaultSwipeBackHandlerFinishData([=] {
  735. _swipeBackData = {};
  736. CheckAndJumpToNearChatsFilter(controller(), next, true);
  737. });
  738. }
  739. }
  740. return Ui::Controls::SwipeHandlerFinishData();
  741. });
  742. }
  743. void Widget::chosenRow(const ChosenRow &row) {
  744. storiesToggleExplicitExpand(false);
  745. if (!_searchState.query.isEmpty()) {
  746. if (const auto history = row.key.history()) {
  747. session().recentPeers().bump(history->peer);
  748. }
  749. }
  750. const auto history = row.key.history();
  751. const auto topicJump = history
  752. ? history->peer->forumTopicFor(row.message.fullId.msg)
  753. : nullptr;
  754. if (topicJump) {
  755. if (controller()->shownForum().current() == topicJump->forum()) {
  756. controller()->closeForum();
  757. } else if (row.newWindow) {
  758. controller()->showInNewWindow(Window::SeparateId(topicJump));
  759. } else {
  760. if (!controller()->adaptive().isOneColumn()) {
  761. controller()->showForum(
  762. topicJump->forum(),
  763. Window::SectionShow().withChildColumn());
  764. }
  765. controller()->showThread(
  766. topicJump,
  767. ShowAtUnreadMsgId,
  768. Window::SectionShow::Way::ClearStack);
  769. }
  770. return;
  771. } else if (const auto topic = row.key.topic()) {
  772. auto params = Window::SectionShow(
  773. Window::SectionShow::Way::ClearStack);
  774. params.highlightPart.text = _searchState.query;
  775. if (!params.highlightPart.empty()) {
  776. params.highlightPartOffsetHint = kSearchQueryOffsetHint;
  777. }
  778. if (row.newWindow) {
  779. controller()->showInNewWindow(
  780. Window::SeparateId(topic),
  781. row.message.fullId.msg);
  782. } else {
  783. session().data().saveViewAsMessages(topic->forum(), false);
  784. controller()->showThread(topic, row.message.fullId.msg, params);
  785. }
  786. } else if (history
  787. && row.userpicClick
  788. && (row.message.fullId.msg == ShowAtUnreadMsgId)
  789. && history->peer->hasActiveStories()
  790. && !history->peer->isSelf()) {
  791. controller()->openPeerStories(history->peer->id);
  792. return;
  793. } else if (history
  794. && history->isForum()
  795. && !row.message.fullId
  796. && (!controller()->adaptive().isOneColumn()
  797. || !history->peer->forum()->channel()->viewForumAsMessages())) {
  798. const auto forum = history->peer->forum();
  799. if (controller()->shownForum().current() == forum) {
  800. controller()->closeForum();
  801. } else if (row.newWindow) {
  802. controller()->showInNewWindow(
  803. Window::SeparateId(Window::SeparateType::Forum, history));
  804. } else {
  805. controller()->showForum(
  806. forum,
  807. Window::SectionShow().withChildColumn());
  808. if (forum->channel()->viewForumAsMessages()) {
  809. controller()->showThread(
  810. history,
  811. ShowAtUnreadMsgId,
  812. Window::SectionShow::Way::ClearStack);
  813. }
  814. }
  815. return;
  816. } else if (history) {
  817. const auto peer = history->peer;
  818. const auto showAtMsgId = controller()->uniqueChatsInSearchResults()
  819. ? ShowAtUnreadMsgId
  820. : row.message.fullId.msg;
  821. auto params = Window::SectionShow(
  822. Window::SectionShow::Way::ClearStack);
  823. params.highlightPart.text = _searchState.query;
  824. if (!params.highlightPart.empty()) {
  825. params.highlightPartOffsetHint = kSearchQueryOffsetHint;
  826. }
  827. if (row.newWindow) {
  828. controller()->showInNewWindow(peer, showAtMsgId);
  829. } else {
  830. controller()->showThread(history, showAtMsgId, params);
  831. hideChildList();
  832. }
  833. } else if (const auto folder = row.key.folder()) {
  834. if (row.userpicClick) {
  835. const auto list = Data::StorySourcesList::Hidden;
  836. const auto &sources = session().data().stories().sources(list);
  837. if (!sources.empty()) {
  838. controller()->openPeerStories(sources.front().id, list);
  839. return;
  840. }
  841. }
  842. if (row.newWindow) {
  843. controller()->showInNewWindow(Window::SeparateId(
  844. Window::SeparateType::Archive,
  845. &session()));
  846. return;
  847. }
  848. controller()->openFolder(folder);
  849. hideChildList();
  850. }
  851. if (row.filteredRow && !session().supportMode()) {
  852. if (_subsectionTopBar) {
  853. _subsectionTopBar->toggleSearch(false, anim::type::instant);
  854. } else {
  855. escape();
  856. }
  857. }
  858. updateForceDisplayWide();
  859. }
  860. void Widget::setGeometryWithTopMoved(
  861. const QRect &newGeometry,
  862. int topDelta) {
  863. _topDelta = topDelta;
  864. bool willBeResized = (size() != newGeometry.size());
  865. if (geometry() != newGeometry) {
  866. auto weak = Ui::MakeWeak(this);
  867. setGeometry(newGeometry);
  868. if (!weak) {
  869. return;
  870. }
  871. }
  872. if (!willBeResized) {
  873. resizeEvent(nullptr);
  874. }
  875. _topDelta = 0;
  876. }
  877. void Widget::scrollToDefaultChecked(bool verytop) {
  878. if (_scrollToAnimation.animating()) {
  879. return;
  880. }
  881. scrollToDefault(verytop);
  882. }
  883. void Widget::setupScrollUpButton() {
  884. _scrollToTop->setClickedCallback([=] { scrollToDefaultChecked(); });
  885. trackScroll(_scrollToTop);
  886. trackScroll(this);
  887. updateScrollUpVisibility();
  888. }
  889. void Widget::setupTouchChatPreview() {
  890. _scroll->setCustomTouchProcess([=](not_null<QTouchEvent*> e) {
  891. return _inner->processTouchEvent(e);
  892. });
  893. _inner->touchCancelRequests() | rpl::start_with_next([=] {
  894. QTouchEvent ev(QEvent::TouchCancel);
  895. ev.setTimestamp(crl::now());
  896. QGuiApplication::sendEvent(_scroll, &ev);
  897. }, _inner->lifetime());
  898. }
  899. void Widget::setupMoreChatsBar() {
  900. if (_layout == Layout::Child) {
  901. return;
  902. }
  903. controller()->activeChatsFilter(
  904. ) | rpl::start_with_next([=](FilterId id) {
  905. storiesToggleExplicitExpand(false);
  906. const auto cancelled = cancelSearch({ .forceFullCancel = true });
  907. const auto guard = gsl::finally([&] {
  908. if (cancelled) {
  909. controller()->content()->dialogsCancelled();
  910. }
  911. });
  912. if (!id) {
  913. _moreChatsBar = nullptr;
  914. updateControlsGeometry();
  915. return;
  916. }
  917. const auto filters = &session().data().chatsFilters();
  918. _moreChatsBar = std::make_unique<Ui::MoreChatsBar>(
  919. this,
  920. filters->moreChatsContent(id));
  921. trackScroll(_moreChatsBar->wrap());
  922. _moreChatsBar->barClicks(
  923. ) | rpl::start_with_next([=] {
  924. if (const auto missing = filters->moreChats(id)
  925. ; !missing.empty()) {
  926. Api::ProcessFilterUpdate(controller(), id, missing);
  927. }
  928. }, _moreChatsBar->lifetime());
  929. _moreChatsBar->closeClicks(
  930. ) | rpl::start_with_next([=] {
  931. Api::ProcessFilterUpdate(controller(), id, {});
  932. }, _moreChatsBar->lifetime());
  933. if (_showAnimation) {
  934. _moreChatsBar->hide();
  935. } else {
  936. _moreChatsBar->show();
  937. _moreChatsBar->finishAnimating();
  938. }
  939. _moreChatsBar->heightValue(
  940. ) | rpl::start_with_next([=] {
  941. updateControlsGeometry();
  942. }, _moreChatsBar->lifetime());
  943. }, lifetime());
  944. }
  945. void Widget::setupDownloadBar() {
  946. if (_layout == Layout::Child) {
  947. return;
  948. }
  949. Data::MakeDownloadBarContent(
  950. ) | rpl::start_with_next([=](Ui::DownloadBarContent &&content) {
  951. const auto create = (content.count && !_downloadBar);
  952. if (create) {
  953. _downloadBar = std::make_unique<Ui::DownloadBar>(
  954. this,
  955. Data::MakeDownloadBarProgress());
  956. }
  957. if (_downloadBar) {
  958. _downloadBar->show(std::move(content));
  959. }
  960. if (create) {
  961. _downloadBar->heightValue(
  962. ) | rpl::start_with_next([=] {
  963. updateControlsGeometry();
  964. }, _downloadBar->lifetime());
  965. _downloadBar->shownValue(
  966. ) | rpl::filter(
  967. !rpl::mappers::_1
  968. ) | rpl::start_with_next([=] {
  969. _downloadBar = nullptr;
  970. updateControlsGeometry();
  971. }, _downloadBar->lifetime());
  972. _downloadBar->clicks(
  973. ) | rpl::start_with_next([=] {
  974. auto &&list = Core::App().downloadManager().loadingList();
  975. const auto guard = gsl::finally([] {
  976. Core::App().downloadManager().clearIfFinished();
  977. });
  978. auto first = (HistoryItem*)nullptr;
  979. for (const auto id : list) {
  980. if (!first) {
  981. first = id->object.item;
  982. } else {
  983. controller()->showSection(
  984. Info::Downloads::Make(
  985. controller()->session().user()));
  986. return;
  987. }
  988. }
  989. if (first) {
  990. controller()->showMessage(first);
  991. }
  992. }, _downloadBar->lifetime());
  993. if (_connecting) {
  994. _connecting->raise();
  995. }
  996. }
  997. }, lifetime());
  998. }
  999. void Widget::updateScrollUpVisibility() {
  1000. if (_scrollToAnimation.animating()) {
  1001. return;
  1002. }
  1003. startScrollUpButtonAnimation(
  1004. (_scroll->scrollTop() > (st::historyToDownShownAfter / 2))
  1005. && (_scroll->scrollTop() < _scroll->scrollTopMax()));
  1006. }
  1007. void Widget::startScrollUpButtonAnimation(bool shown) {
  1008. const auto smallColumn = (width() < st::columnMinimalWidthLeft)
  1009. || _childList;
  1010. shown &= !smallColumn;
  1011. if (_scrollToTopIsShown == shown) {
  1012. return;
  1013. }
  1014. _scrollToTopIsShown = shown;
  1015. _scrollToTopShown.start(
  1016. [=] { updateScrollUpPosition(); },
  1017. _scrollToTopIsShown ? 0. : 1.,
  1018. _scrollToTopIsShown ? 1. : 0.,
  1019. smallColumn ? 0 : st::historyToDownDuration);
  1020. }
  1021. void Widget::updateScrollUpPosition() {
  1022. // _scrollToTop is a child widget of _scroll, not me.
  1023. auto top = anim::interpolate(
  1024. 0,
  1025. _scrollToTop->height() + st::connectingMargin.top(),
  1026. _scrollToTopShown.value(_scrollToTopIsShown ? 1. : 0.));
  1027. _scrollToTop->moveToRight(
  1028. st::historyToDownPosition.x(),
  1029. _scroll->height() - top);
  1030. const auto shouldBeHidden
  1031. = !_scrollToTopIsShown && !_scrollToTopShown.animating();
  1032. if (shouldBeHidden != _scrollToTop->isHidden()) {
  1033. _scrollToTop->setVisible(!shouldBeHidden);
  1034. }
  1035. }
  1036. void Widget::setupConnectingWidget() {
  1037. _connecting = std::make_unique<Window::ConnectionState>(
  1038. this,
  1039. &session().account(),
  1040. controller()->adaptive().oneColumnValue());
  1041. }
  1042. void Widget::setupSupportMode() {
  1043. if (!session().supportMode()) {
  1044. return;
  1045. }
  1046. fullSearchRefreshOn(session().settings().supportAllSearchResultsValue(
  1047. ) | rpl::to_empty);
  1048. }
  1049. void Widget::setupMainMenuToggle() {
  1050. _mainMenu.under->setClickedCallback([=] {
  1051. _mainMenu.toggle->clicked({}, Qt::LeftButton);
  1052. });
  1053. _mainMenu.under->stackUnder(_mainMenu.toggle);
  1054. _mainMenu.toggle->setClickedCallback([=] { showMainMenu(); });
  1055. rpl::single(rpl::empty) | rpl::then(
  1056. controller()->filtersMenuChanged()
  1057. ) | rpl::start_with_next([=] {
  1058. const auto filtersHidden = !controller()->filtersWidth();
  1059. _mainMenu.toggle->setVisible(filtersHidden);
  1060. _mainMenu.under->setVisible(filtersHidden);
  1061. _searchForNarrowLayout->setVisible(!filtersHidden);
  1062. updateControlsGeometry();
  1063. }, lifetime());
  1064. Window::OtherAccountsUnreadState(
  1065. &controller()->session().account()
  1066. ) | rpl::start_with_next([=](const Window::OthersUnreadState &state) {
  1067. const auto icon = !state.count
  1068. ? nullptr
  1069. : !state.allMuted
  1070. ? &st::dialogsMenuToggleUnread
  1071. : &st::dialogsMenuToggleUnreadMuted;
  1072. _mainMenu.toggle->setIconOverride(icon, icon);
  1073. }, _mainMenu.toggle->lifetime());
  1074. }
  1075. void Widget::setupStories() {
  1076. _stories->verticalScrollEvents(
  1077. ) | rpl::start_with_next([=](not_null<QWheelEvent*> e) {
  1078. _scroll->viewportEvent(e);
  1079. }, _stories->lifetime());
  1080. if (!Core::App().settings().storiesClickTooltipHidden()) {
  1081. // Don't create tooltip
  1082. // until storiesClickTooltipHidden can be returned to false.
  1083. const auto hideTooltip = [=] {
  1084. Core::App().settings().setStoriesClickTooltipHidden(true);
  1085. Core::App().saveSettingsDelayed();
  1086. };
  1087. InvokeQueued(_stories.get(), [=] {
  1088. _stories->setShowTooltip(
  1089. controller()->content(),
  1090. rpl::combine(
  1091. Core::App().settings().storiesClickTooltipHiddenValue(),
  1092. shownValue(),
  1093. !rpl::mappers::_1 && rpl::mappers::_2),
  1094. hideTooltip);
  1095. });
  1096. }
  1097. _storiesContents.fire(Stories::ContentForSession(
  1098. &controller()->session(),
  1099. Data::StorySourcesList::NotHidden));
  1100. const auto currentSource = [=] {
  1101. using List = Data::StorySourcesList;
  1102. return _openedFolder ? List::Hidden : List::NotHidden;
  1103. };
  1104. rpl::combine(
  1105. _scroll->positionValue(),
  1106. _scroll->movementValue(),
  1107. _storiesExplicitExpandValue.value()
  1108. ) | rpl::start_with_next([=](
  1109. Ui::ElasticScrollPosition position,
  1110. Ui::ElasticScrollMovement movement,
  1111. int explicitlyExpanded) {
  1112. if (_stories->isHidden()) {
  1113. return;
  1114. }
  1115. const auto overscrollTop = std::max(-position.overscroll, 0);
  1116. if (overscrollTop > 0 && _storiesExplicitExpand) {
  1117. _scroll->setOverscrollDefaults(
  1118. -st::dialogsStoriesFull.height,
  1119. 0,
  1120. true);
  1121. }
  1122. if (explicitlyExpanded > 0 && explicitlyExpanded < overscrollTop) {
  1123. _storiesExplicitExpandAnimation.stop();
  1124. _storiesExplicitExpand = false;
  1125. _storiesExplicitExpandValue = 0;
  1126. return;
  1127. }
  1128. const auto above = std::max(explicitlyExpanded, overscrollTop);
  1129. if (_aboveScrollAdded != above) {
  1130. _aboveScrollAdded = above;
  1131. if (_updateScrollGeometryCached) {
  1132. _updateScrollGeometryCached();
  1133. }
  1134. }
  1135. using Phase = Ui::ElasticScrollMovement;
  1136. _stories->setExpandedHeight(
  1137. _aboveScrollAdded,
  1138. ((movement == Phase::Momentum || movement == Phase::Returning)
  1139. && (explicitlyExpanded < above)));
  1140. if (position.overscroll > 0
  1141. || (position.value
  1142. > (_storiesExplicitExpandScrollTop
  1143. + st::dialogsRowHeight))) {
  1144. storiesToggleExplicitExpand(false);
  1145. }
  1146. updateLockUnlockPosition();
  1147. }, lifetime());
  1148. _stories->collapsedGeometryChanged(
  1149. ) | rpl::start_with_next([=] {
  1150. updateLockUnlockPosition();
  1151. }, lifetime());
  1152. _stories->clicks(
  1153. ) | rpl::start_with_next([=](uint64 id) {
  1154. controller()->openPeerStories(PeerId(int64(id)), currentSource());
  1155. }, lifetime());
  1156. _stories->showMenuRequests(
  1157. ) | rpl::start_with_next([=](const Stories::ShowMenuRequest &request) {
  1158. FillSourceMenu(controller(), request);
  1159. }, lifetime());
  1160. _stories->loadMoreRequests(
  1161. ) | rpl::start_with_next([=] {
  1162. session().data().stories().loadMore(currentSource());
  1163. }, lifetime());
  1164. _stories->toggleExpandedRequests(
  1165. ) | rpl::start_with_next([=](bool expanded) {
  1166. const auto position = _scroll->position();
  1167. if (!expanded) {
  1168. _scroll->setOverscrollDefaults(0, 0);
  1169. } else if (position.value > 0 || position.overscroll >= 0) {
  1170. storiesToggleExplicitExpand(true);
  1171. _scroll->setOverscrollDefaults(0, 0);
  1172. } else {
  1173. _scroll->setOverscrollDefaults(
  1174. -st::dialogsStoriesFull.height,
  1175. 0);
  1176. }
  1177. }, lifetime());
  1178. _stories->emptyValue() | rpl::skip(1) | rpl::start_with_next([=] {
  1179. updateStoriesVisibility();
  1180. }, lifetime());
  1181. _stories->widthValue() | rpl::start_with_next([=] {
  1182. updateLockUnlockPosition();
  1183. }, lifetime());
  1184. }
  1185. void Widget::storiesToggleExplicitExpand(bool expand) {
  1186. if (_storiesExplicitExpand == expand) {
  1187. return;
  1188. }
  1189. _storiesExplicitExpand = expand;
  1190. if (!expand) {
  1191. _scroll->setOverscrollDefaults(0, 0, true);
  1192. }
  1193. const auto height = st::dialogsStoriesFull.height;
  1194. const auto duration = kStoriesExpandDuration;
  1195. _storiesExplicitExpandScrollTop = _scroll->position().value;
  1196. _storiesExplicitExpandAnimation.start([=](float64 value) {
  1197. _storiesExplicitExpandValue = int(base::SafeRound(value));
  1198. }, expand ? 0 : height, expand ? height : 0, duration, anim::sineInOut);
  1199. }
  1200. void Widget::trackScroll(not_null<Ui::RpWidget*> widget) {
  1201. widget->events(
  1202. ) | rpl::start_with_next([=](not_null<QEvent*> e) {
  1203. const auto type = e->type();
  1204. if (type == QEvent::TouchBegin
  1205. || type == QEvent::TouchUpdate
  1206. || type == QEvent::TouchEnd
  1207. || type == QEvent::TouchCancel
  1208. || type == QEvent::Wheel) {
  1209. _scroll->viewportEvent(e);
  1210. }
  1211. }, widget->lifetime());
  1212. }
  1213. void Widget::setupShortcuts() {
  1214. Shortcuts::Requests(
  1215. ) | rpl::filter([=] {
  1216. return isActiveWindow()
  1217. && Ui::InFocusChain(this)
  1218. && !_childList
  1219. && !controller()->isLayerShown()
  1220. && !controller()->window().locked();
  1221. }) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
  1222. using Command = Shortcuts::Command;
  1223. if (!controller()->activeChatCurrent()) {
  1224. request->check(Command::Search) && request->handle([=] {
  1225. if (const auto forum = _openedForum) {
  1226. const auto history = forum->history();
  1227. controller()->searchInChat(history);
  1228. return true;
  1229. } else if (!_openedFolder
  1230. && !_childList
  1231. && _search->isVisible()) {
  1232. _search->setFocus();
  1233. return true;
  1234. }
  1235. return false;
  1236. });
  1237. request->check(Command::ShowChatMenu, 1) && request->handle([=] {
  1238. if (_inner) {
  1239. Window::ActivateWindow(controller());
  1240. _inner->showPeerMenu();
  1241. }
  1242. return true;
  1243. });
  1244. request->check(Command::ShowChatPreview, 1)
  1245. && request->handle([=] {
  1246. if (_inner) {
  1247. Window::ActivateWindow(controller());
  1248. return _inner->showChatPreview();
  1249. }
  1250. return true;
  1251. });
  1252. }
  1253. }, lifetime());
  1254. }
  1255. void Widget::fullSearchRefreshOn(rpl::producer<> events) {
  1256. std::move(
  1257. events
  1258. ) | rpl::filter([=] {
  1259. return !_searchQuery.isEmpty();
  1260. }) | rpl::start_with_next([=] {
  1261. _searchTimer.cancel();
  1262. _searchProcess.cache.clear();
  1263. const auto queries = base::take(_searchProcess.queries);
  1264. for (const auto &[requestId, query] : queries) {
  1265. session().api().request(requestId).cancel();
  1266. }
  1267. _singleMessageSearch.clear();
  1268. _searchQuery = QString();
  1269. _scroll->scrollToY(0);
  1270. cancelSearchRequest();
  1271. search();
  1272. }, lifetime());
  1273. }
  1274. void Widget::updateControlsVisibility(bool fast) {
  1275. updateLoadMoreChatsVisibility();
  1276. _scroll->setVisible(!_suggestions && _hidingSuggestions.empty());
  1277. updateStoriesVisibility();
  1278. if ((_openedFolder || _openedForum) && _searchHasFocus) {
  1279. setInnerFocus();
  1280. }
  1281. if (_updateTelegram) {
  1282. _updateTelegram->show();
  1283. }
  1284. _searchControls->setVisible(!_openedFolder && !_openedForum);
  1285. if (_moreChatsBar) {
  1286. _moreChatsBar->show();
  1287. }
  1288. if (_chatFilters) {
  1289. _chatFilters->show();
  1290. }
  1291. if (_openedFolder || _openedForum) {
  1292. _subsectionTopBar->show();
  1293. if (_forumTopShadow) {
  1294. _forumTopShadow->show();
  1295. }
  1296. if (_forumGroupCallBar) {
  1297. _forumGroupCallBar->show();
  1298. }
  1299. if (_forumRequestsBar) {
  1300. _forumRequestsBar->show();
  1301. }
  1302. if (_forumReportBar) {
  1303. _forumReportBar->show();
  1304. }
  1305. } else {
  1306. updateLockUnlockVisibility();
  1307. updateJumpToDateVisibility(fast);
  1308. updateSearchFromVisibility(fast);
  1309. }
  1310. if (_connecting) {
  1311. _connecting->setForceHidden(false);
  1312. }
  1313. if (_childList) {
  1314. _childList->show();
  1315. _childListShadow->show();
  1316. }
  1317. if (_hideChildListCanvas) {
  1318. _hideChildListCanvas->show();
  1319. }
  1320. if (_childList && _searchHasFocus) {
  1321. setInnerFocus();
  1322. }
  1323. updateLockUnlockPosition();
  1324. }
  1325. void Widget::updateLockUnlockPosition() {
  1326. if (_lockUnlock->isHidden()) {
  1327. return;
  1328. }
  1329. const auto stories = (_stories && !_stories->isHidden())
  1330. ? _stories->collapsedGeometryCurrent()
  1331. : Stories::List::CollapsedGeometry();
  1332. const auto simple = _search->x() + _search->width();
  1333. const auto right = stories.geometry.isEmpty()
  1334. ? simple
  1335. : anim::interpolate(stories.geometry.x(), simple, stories.expanded);
  1336. _lockUnlock->move(
  1337. right - _lockUnlock->width(),
  1338. st::dialogsFilterPadding.y());
  1339. }
  1340. void Widget::updateHasFocus(not_null<QWidget*> focused) {
  1341. const auto has = (focused == _search.data())
  1342. || (focused == _search->rawTextEdit());
  1343. if (_searchHasFocus != has) {
  1344. _searchHasFocus = has;
  1345. if (_postponeProcessSearchFocusChange) {
  1346. return;
  1347. } else if (has) {
  1348. processSearchFocusChange();
  1349. } else {
  1350. // Search field may loose focus from the destructor of some
  1351. // widget, in that case we don't want to destroy _suggestions
  1352. // synchronously, because it may lead to a crash.
  1353. crl::on_main(this, [=] { processSearchFocusChange(); });
  1354. }
  1355. }
  1356. }
  1357. void Widget::toggleFiltersMenu(bool enabled) {
  1358. if (_layout == Layout::Child) {
  1359. enabled = false;
  1360. }
  1361. if (const auto id = controller()->windowId(); id.forum() || id.folder()) {
  1362. enabled = false;
  1363. }
  1364. if (!enabled == !_chatFilters) {
  1365. return;
  1366. } else if (enabled) {
  1367. class NoScrollPropagationWidget final : public Ui::RpWidget {
  1368. public:
  1369. using Ui::RpWidget::RpWidget;
  1370. protected:
  1371. void touchEvent(QTouchEvent *e) {
  1372. e->accept();
  1373. }
  1374. void wheelEvent(QWheelEvent *e) override final {
  1375. e->accept();
  1376. }
  1377. };
  1378. _chatFilters = base::make_unique_q<NoScrollPropagationWidget>(this);
  1379. const auto raw = _chatFilters.get();
  1380. const auto inner = Ui::AddChatFiltersTabsStrip(
  1381. _chatFilters.get(),
  1382. &session(),
  1383. [this](FilterId id) {
  1384. _scroll->scrollToY(0);
  1385. if (controller()->activeChatsFilterCurrent() != id) {
  1386. controller()->setActiveChatsFilter(id);
  1387. }
  1388. },
  1389. Window::GifPauseReason::Any,
  1390. controller(),
  1391. true);
  1392. raw->show();
  1393. raw->stackUnder(_scroll);
  1394. raw->resizeToWidth(width());
  1395. const auto shadow = Ui::CreateChild<Ui::PlainShadow>(raw);
  1396. shadow->show();
  1397. inner->sizeValue() | rpl::start_with_next([=, this](const QSize &s) {
  1398. raw->resize(s);
  1399. shadow->setGeometry(
  1400. 0,
  1401. s.height() - shadow->height(),
  1402. s.width(),
  1403. shadow->height());
  1404. updateControlsGeometry();
  1405. }, _chatFilters->lifetime());
  1406. updateControlsGeometry();
  1407. } else {
  1408. _chatFilters = nullptr;
  1409. }
  1410. }
  1411. bool Widget::cancelSearchByMouseBack() {
  1412. return _searchHasFocus
  1413. && !_searchSuggestionsLocked
  1414. && !_searchState.inChat
  1415. && cancelSearch({ .jumpBackToSearchedChat = true });
  1416. }
  1417. void Widget::processSearchFocusChange() {
  1418. _searchSuggestionsLocked = _suggestions && _suggestions->persist();
  1419. updateCancelSearch();
  1420. updateForceDisplayWide();
  1421. updateSuggestions(anim::type::normal);
  1422. }
  1423. void Widget::updateSuggestions(anim::type animated) {
  1424. const auto suggest = (_searchHasFocus || _searchSuggestionsLocked)
  1425. && !_searchState.inChat
  1426. && (_inner->state() == WidgetState::Default);
  1427. if (anim::Disabled() || !session().data().chatsListLoaded()) {
  1428. animated = anim::type::instant;
  1429. }
  1430. if (!suggest && _suggestions) {
  1431. if (animated == anim::type::normal) {
  1432. auto taken = base::take(_suggestions);
  1433. taken->setVisible(false);
  1434. storiesExplicitCollapse();
  1435. updateStoriesVisibility();
  1436. startWidthAnimation();
  1437. taken->setVisible(true);
  1438. _suggestions = base::take(taken);
  1439. _suggestions->hide(animated, [=, raw = _suggestions.get()] {
  1440. stopWidthAnimation();
  1441. _hidingSuggestions.erase(
  1442. ranges::remove(
  1443. _hidingSuggestions,
  1444. raw,
  1445. &std::unique_ptr<Suggestions>::get),
  1446. end(_hidingSuggestions));
  1447. updateControlsVisibility();
  1448. });
  1449. _hidingSuggestions.push_back(std::move(_suggestions));
  1450. } else {
  1451. _suggestions = nullptr;
  1452. _hidingSuggestions.clear();
  1453. storiesExplicitCollapse();
  1454. updateStoriesVisibility();
  1455. _scroll->show();
  1456. }
  1457. } else if (suggest && !_suggestions) {
  1458. if (animated == anim::type::normal) {
  1459. startWidthAnimation();
  1460. }
  1461. // Hides stories and passcode lock.
  1462. updateStoriesVisibility();
  1463. _suggestions = std::make_unique<Suggestions>(
  1464. this,
  1465. controller(),
  1466. TopPeersContent(&session()),
  1467. RecentPeersContent(&session()));
  1468. _suggestions->clearSearchQueryRequests() | rpl::start_with_next([=] {
  1469. setSearchQuery(QString());
  1470. }, _suggestions->lifetime());
  1471. _searchSuggestionsLocked = false;
  1472. rpl::merge(
  1473. _suggestions->topPeerChosen(),
  1474. _suggestions->recentPeerChosen(),
  1475. _suggestions->myChannelChosen(),
  1476. _suggestions->recommendationChosen()
  1477. ) | rpl::start_with_next([=](not_null<PeerData*> peer) {
  1478. if (_searchSuggestionsLocked
  1479. && (!_suggestions || !_suggestions->persist())) {
  1480. processSearchFocusChange();
  1481. }
  1482. chosenRow({
  1483. .key = peer->owner().history(peer),
  1484. .newWindow = base::IsCtrlPressed(),
  1485. });
  1486. if (!_searchSuggestionsLocked && _searchHasFocus) {
  1487. setFocus();
  1488. controller()->widget()->setInnerFocus();
  1489. }
  1490. }, _suggestions->lifetime());
  1491. rpl::merge(
  1492. _suggestions->openBotMainAppRequests(),
  1493. _suggestions->recentAppChosen()
  1494. ) | rpl::start_with_next([=](not_null<PeerData*> peer) {
  1495. if (const auto user = peer->asUser()) {
  1496. if (const auto info = user->botInfo.get()) {
  1497. if (info->hasMainApp) {
  1498. openBotMainApp(user);
  1499. return;
  1500. }
  1501. }
  1502. }
  1503. chosenRow({
  1504. .key = peer->owner().history(peer),
  1505. .newWindow = base::IsCtrlPressed(),
  1506. });
  1507. }, _suggestions->lifetime());
  1508. _suggestions->popularAppChosen(
  1509. ) | rpl::start_with_next([=](not_null<PeerData*> peer) {
  1510. controller()->showPeerInfo(peer);
  1511. }, _suggestions->lifetime());
  1512. updateControlsGeometry();
  1513. _suggestions->show(animated, [=] {
  1514. stopWidthAnimation();
  1515. });
  1516. _scroll->hide();
  1517. } else {
  1518. updateStoriesVisibility();
  1519. }
  1520. }
  1521. void Widget::openBotMainApp(not_null<UserData*> bot) {
  1522. session().attachWebView().open({
  1523. .bot = bot,
  1524. .context = {
  1525. .controller = controller(),
  1526. .maySkipConfirmation = true,
  1527. },
  1528. .source = InlineBots::WebViewSourceBotProfile(),
  1529. });
  1530. }
  1531. void Widget::changeOpenedSubsection(
  1532. FnMut<void()> change,
  1533. bool fromRight,
  1534. anim::type animated) {
  1535. if (isHidden()) {
  1536. animated = anim::type::instant;
  1537. }
  1538. auto oldContentCache = QPixmap();
  1539. const auto showDirection = fromRight
  1540. ? Window::SlideDirection::FromRight
  1541. : Window::SlideDirection::FromLeft;
  1542. if (animated == anim::type::normal) {
  1543. if (_connecting) {
  1544. _connecting->setForceHidden(true);
  1545. }
  1546. oldContentCache = grabForFolderSlideAnimation();
  1547. }
  1548. //_scroll->verticalScrollBar()->setMinimum(0);
  1549. _showAnimation = nullptr;
  1550. destroyChildListCanvas();
  1551. change();
  1552. refreshTopBars();
  1553. updateControlsVisibility(true);
  1554. _peerSearchRequest = 0;
  1555. _api.request(base::take(_topicSearchRequest)).cancel();
  1556. if (animated == anim::type::normal) {
  1557. if (_connecting) {
  1558. _connecting->setForceHidden(true);
  1559. }
  1560. auto newContentCache = grabForFolderSlideAnimation();
  1561. if (_connecting) {
  1562. _connecting->setForceHidden(false);
  1563. }
  1564. startSlideAnimation(
  1565. std::move(oldContentCache),
  1566. std::move(newContentCache),
  1567. showDirection);
  1568. }
  1569. }
  1570. void Widget::destroyChildListCanvas() {
  1571. _childListShown = 0.;
  1572. _hideChildListCanvas = nullptr;
  1573. }
  1574. void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) {
  1575. if (_openedFolder == folder) {
  1576. return;
  1577. }
  1578. changeOpenedSubsection([&] {
  1579. cancelSearch({ .forceFullCancel = true });
  1580. closeChildList(anim::type::instant);
  1581. controller()->closeForum();
  1582. _openedFolder = folder;
  1583. _inner->changeOpenedFolder(folder);
  1584. if (_stories) {
  1585. storiesExplicitCollapse();
  1586. }
  1587. }, (folder != nullptr), animated);
  1588. }
  1589. void Widget::storiesExplicitCollapse() {
  1590. if (_storiesExplicitExpand) {
  1591. storiesToggleExplicitExpand(false);
  1592. } else if (_stories) {
  1593. using Type = Ui::ElasticScroll::OverscrollType;
  1594. _scroll->setOverscrollDefaults(0, 0);
  1595. _scroll->setOverscrollTypes(Type::None, Type::Real);
  1596. _scroll->setOverscrollTypes(
  1597. _stories->isHidden() ? Type::Real : Type::Virtual,
  1598. Type::Real);
  1599. }
  1600. _storiesExplicitExpandAnimation.stop();
  1601. _storiesExplicitExpandValue = 0;
  1602. using List = Data::StorySourcesList;
  1603. collectStoriesUserpicsViews(_openedFolder
  1604. ? List::NotHidden
  1605. : List::Hidden);
  1606. _storiesContents.fire(Stories::ContentForSession(
  1607. &session(),
  1608. _openedFolder ? List::Hidden : List::NotHidden));
  1609. }
  1610. void Widget::collectStoriesUserpicsViews(Data::StorySourcesList list) {
  1611. auto &map = (list == Data::StorySourcesList::Hidden)
  1612. ? _storiesUserpicsViewsHidden
  1613. : _storiesUserpicsViewsShown;
  1614. map.clear();
  1615. auto &owner = session().data();
  1616. for (const auto &source : owner.stories().sources(list)) {
  1617. if (const auto peer = owner.peerLoaded(source.id)) {
  1618. if (auto view = peer->activeUserpicView(); view.cloud) {
  1619. map.emplace(source.id, std::move(view));
  1620. }
  1621. }
  1622. }
  1623. }
  1624. void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) {
  1625. if (_openedForum == forum) {
  1626. return;
  1627. }
  1628. changeOpenedSubsection([&] {
  1629. cancelSearch({ .forceFullCancel = true });
  1630. closeChildList(anim::type::instant);
  1631. _openedForum = forum;
  1632. _searchState.tab = forum
  1633. ? ChatSearchTab::ThisPeer
  1634. : ChatSearchTab::MyMessages;
  1635. _searchWithPostsPreview = computeSearchWithPostsPreview();
  1636. _api.request(base::take(_topicSearchRequest)).cancel();
  1637. _inner->changeOpenedForum(forum);
  1638. storiesToggleExplicitExpand(false);
  1639. updateStoriesVisibility();
  1640. }, (forum != nullptr), animated);
  1641. }
  1642. void Widget::hideChildList() {
  1643. if (_childList) {
  1644. controller()->closeForum();
  1645. }
  1646. }
  1647. void Widget::refreshTopBars() {
  1648. if (_openedFolder || _openedForum) {
  1649. if (!_subsectionTopBar) {
  1650. _subsectionTopBar.create(this, controller());
  1651. if (_stories) {
  1652. _stories->raise();
  1653. }
  1654. _subsectionTopBar->searchCancelled(
  1655. ) | rpl::start_with_next([=] {
  1656. escape();
  1657. }, _subsectionTopBar->lifetime());
  1658. _subsectionTopBar->searchSubmitted(
  1659. ) | rpl::start_with_next([=] {
  1660. submit();
  1661. }, _subsectionTopBar->lifetime());
  1662. _subsectionTopBar->searchQuery(
  1663. ) | rpl::start_with_next([=](QString query) {
  1664. applySearchUpdate();
  1665. }, _subsectionTopBar->lifetime());
  1666. _subsectionTopBar->jumpToDateRequest(
  1667. ) | rpl::start_with_next([=] {
  1668. showCalendar();
  1669. }, _subsectionTopBar->lifetime());
  1670. _subsectionTopBar->chooseFromUserRequest(
  1671. ) | rpl::start_with_next([=] {
  1672. showSearchFrom();
  1673. }, _subsectionTopBar->lifetime());
  1674. updateControlsGeometry();
  1675. }
  1676. const auto history = _openedForum
  1677. ? _openedForum->history().get()
  1678. : nullptr;
  1679. _subsectionTopBar->setActiveChat(
  1680. HistoryView::TopBarWidget::ActiveChat{
  1681. .key = (_openedForum
  1682. ? Dialogs::Key(history)
  1683. : Dialogs::Key(_openedFolder)),
  1684. .section = Dialogs::EntryState::Section::ChatsList,
  1685. }, history ? history->sendActionPainter().get() : nullptr);
  1686. if (_forumSearchRequested) {
  1687. showSearchInTopBar(anim::type::instant);
  1688. }
  1689. } else if (_subsectionTopBar) {
  1690. if (_subsectionTopBar->searchHasFocus()) {
  1691. setFocus();
  1692. }
  1693. _subsectionTopBar.destroy();
  1694. updateSearchFromVisibility(true);
  1695. }
  1696. _forumSearchRequested = false;
  1697. if (_openedForum) {
  1698. const auto channel = _openedForum->channel();
  1699. channel->updateFull();
  1700. _forumReportBar = std::make_unique<HistoryView::ContactStatus>(
  1701. controller(),
  1702. this,
  1703. channel,
  1704. true);
  1705. _forumRequestsBar = std::make_unique<Ui::RequestsBar>(
  1706. this,
  1707. HistoryView::RequestsBarContentByPeer(
  1708. channel,
  1709. st::historyRequestsUserpics.size,
  1710. true));
  1711. _forumGroupCallBar = std::make_unique<Ui::GroupCallBar>(
  1712. this,
  1713. HistoryView::GroupCallBarContentByPeer(
  1714. channel,
  1715. st::historyGroupCallUserpics.size,
  1716. true),
  1717. Core::App().appDeactivatedValue());
  1718. _forumTopShadow = std::make_unique<Ui::PlainShadow>(this);
  1719. _forumRequestsBar->barClicks(
  1720. ) | rpl::start_with_next([=] {
  1721. RequestsBoxController::Start(controller(), channel);
  1722. }, _forumRequestsBar->lifetime());
  1723. rpl::merge(
  1724. _forumGroupCallBar->barClicks(),
  1725. _forumGroupCallBar->joinClicks()
  1726. ) | rpl::start_with_next([=] {
  1727. if (channel->groupCall()) {
  1728. controller()->startOrJoinGroupCall(channel);
  1729. }
  1730. }, _forumGroupCallBar->lifetime());
  1731. if (_showAnimation) {
  1732. _forumTopShadow->hide();
  1733. _forumGroupCallBar->hide();
  1734. _forumRequestsBar->hide();
  1735. _forumReportBar->bar().hide();
  1736. } else {
  1737. _forumTopShadow->show();
  1738. _forumGroupCallBar->show();
  1739. _forumRequestsBar->show();
  1740. _forumReportBar->show();
  1741. _forumGroupCallBar->finishAnimating();
  1742. _forumRequestsBar->finishAnimating();
  1743. }
  1744. rpl::combine(
  1745. _forumGroupCallBar->heightValue(),
  1746. _forumRequestsBar->heightValue(),
  1747. _forumReportBar->bar().heightValue()
  1748. ) | rpl::start_with_next([=] {
  1749. updateControlsGeometry();
  1750. }, _forumRequestsBar->lifetime());
  1751. } else {
  1752. _forumTopShadow = nullptr;
  1753. _forumGroupCallBar = nullptr;
  1754. _forumRequestsBar = nullptr;
  1755. _forumReportBar = nullptr;
  1756. updateControlsGeometry();
  1757. }
  1758. }
  1759. void Widget::showSearchInTopBar(anim::type animated) {
  1760. Expects(_subsectionTopBar != nullptr);
  1761. _subsectionTopBar->toggleSearch(true, animated);
  1762. updateForceDisplayWide();
  1763. }
  1764. QPixmap Widget::grabForFolderSlideAnimation() {
  1765. const auto hidden = _scrollToTop->isHidden();
  1766. if (!hidden) {
  1767. _scrollToTop->hide();
  1768. }
  1769. const auto rect = QRect(0, 0, width(), rect::bottom(_scroll));
  1770. auto result = Ui::GrabWidget(this, rect);
  1771. if (!hidden) {
  1772. _scrollToTop->show();
  1773. }
  1774. return result;
  1775. }
  1776. void Widget::checkUpdateStatus() {
  1777. Expects(!Core::UpdaterDisabled());
  1778. if (_layout == Layout::Child) {
  1779. return;
  1780. }
  1781. using Checker = Core::UpdateChecker;
  1782. if (Checker().state() == Checker::State::Ready) {
  1783. if (_updateTelegram) {
  1784. return;
  1785. }
  1786. _updateTelegram.create(
  1787. this,
  1788. tr::lng_update_telegram(tr::now),
  1789. st::dialogsUpdateButton,
  1790. st::dialogsInstallUpdate,
  1791. st::dialogsInstallUpdateOver,
  1792. true);
  1793. _updateTelegram->show();
  1794. _updateTelegram->setClickedCallback([] {
  1795. Core::checkReadyUpdate();
  1796. Core::Restart();
  1797. });
  1798. if (_connecting) {
  1799. _connecting->raise();
  1800. }
  1801. } else {
  1802. if (!_updateTelegram) {
  1803. return;
  1804. }
  1805. _updateTelegram.destroy();
  1806. }
  1807. updateControlsGeometry();
  1808. }
  1809. void Widget::setInnerFocus(bool unfocusSearch) {
  1810. if (_childList) {
  1811. _childList->setInnerFocus();
  1812. } else if (_subsectionTopBar && _subsectionTopBar->searchSetFocus()) {
  1813. return;
  1814. } else if (!unfocusSearch
  1815. && (!_search->getLastText().isEmpty()
  1816. || _searchState.inChat
  1817. || _searchHasFocus
  1818. || _searchSuggestionsLocked)) {
  1819. _search->setFocus();
  1820. } else {
  1821. setFocus();
  1822. }
  1823. }
  1824. bool Widget::searchHasFocus() const {
  1825. return _searchHasFocus;
  1826. }
  1827. void Widget::jumpToTop(bool belowPinned) {
  1828. if (session().supportMode()) {
  1829. return;
  1830. }
  1831. if ((_searchState.query.trimmed().isEmpty() && !_searchState.inChat)) {
  1832. auto to = 0;
  1833. if (belowPinned) {
  1834. const auto list = _openedForum
  1835. ? _openedForum->topicsList()
  1836. : controller()->activeChatsFilterCurrent()
  1837. ? session().data().chatsFilters().chatsList(
  1838. controller()->activeChatsFilterCurrent())
  1839. : session().data().chatsList(_openedFolder);
  1840. const auto count = int(list->pinned()->order().size());
  1841. const auto row = _inner->st()->height;
  1842. const auto min = (row * (count * 2 + 1) - _scroll->height()) / 2;
  1843. if (_scroll->scrollTop() <= min) {
  1844. return;
  1845. }
  1846. // Don't jump too high up, below the pinned chats.
  1847. to = std::max(min, to);
  1848. }
  1849. _scrollToAnimation.stop();
  1850. _scroll->scrollToY(to);
  1851. }
  1852. }
  1853. void Widget::raiseWithTooltip() {
  1854. raise();
  1855. if (_stories) {
  1856. Ui::PostponeCall(this, [=] {
  1857. _stories->raiseTooltip();
  1858. });
  1859. }
  1860. }
  1861. void Widget::scrollToDefault(bool verytop) {
  1862. if (verytop) {
  1863. //_scroll->verticalScrollBar()->setMinimum(0);
  1864. }
  1865. _scrollToAnimation.stop();
  1866. auto scrollTop = _scroll->scrollTop();
  1867. const auto scrollTo = 0;
  1868. if (scrollTop == scrollTo) {
  1869. return;
  1870. }
  1871. const auto maxAnimatedDelta = _scroll->height();
  1872. if (scrollTo + maxAnimatedDelta < scrollTop) {
  1873. scrollTop = scrollTo + maxAnimatedDelta;
  1874. _scroll->scrollToY(scrollTop);
  1875. }
  1876. startScrollUpButtonAnimation(false);
  1877. const auto scroll = [=] {
  1878. const auto animated = qRound(_scrollToAnimation.value(scrollTo));
  1879. const auto animatedDelta = animated - scrollTo;
  1880. const auto realDelta = _scroll->scrollTop() - scrollTo;
  1881. if (base::OppositeSigns(realDelta, animatedDelta)) {
  1882. // We scrolled manually to the other side of target 'scrollTo'.
  1883. _scrollToAnimation.stop();
  1884. } else if (std::abs(realDelta) > std::abs(animatedDelta)) {
  1885. // We scroll by animation only if it gets us closer to target.
  1886. _scroll->scrollToY(animated);
  1887. }
  1888. };
  1889. _scrollAnimationTo = scrollTo;
  1890. _scrollToAnimation.start(
  1891. scroll,
  1892. scrollTop,
  1893. scrollTo,
  1894. st::slideDuration,
  1895. anim::sineInOut);
  1896. }
  1897. [[nodiscard]] QPixmap Widget::grabNonNarrowScrollFrame() {
  1898. auto scrollGeometry = _scroll->geometry();
  1899. const auto top = _searchControls->y() + _searchControls->height();
  1900. const auto skip = scrollGeometry.y() - top;
  1901. auto wideGeometry = QRect(
  1902. scrollGeometry.x(),
  1903. scrollGeometry.y(),
  1904. std::max(scrollGeometry.width(), st::columnMinimalWidthLeft),
  1905. scrollGeometry.height());
  1906. _scroll->setGeometry(wideGeometry);
  1907. _inner->resize(wideGeometry.width(), _inner->height());
  1908. _inner->setNarrowRatio(0.);
  1909. Ui::SendPendingMoveResizeEvents(_scroll);
  1910. const auto grabSize = QSize(
  1911. wideGeometry.width(),
  1912. skip + wideGeometry.height());
  1913. auto image = QImage(
  1914. grabSize * style::DevicePixelRatio(),
  1915. QImage::Format_ARGB32_Premultiplied);
  1916. image.setDevicePixelRatio(style::DevicePixelRatio());
  1917. image.fill(Qt::transparent);
  1918. {
  1919. QPainter p(&image);
  1920. Ui::RenderWidget(
  1921. p,
  1922. this,
  1923. QPoint(),
  1924. QRect(0, top, wideGeometry.width(), skip));
  1925. if (_chatFilters) {
  1926. Ui::RenderWidget(
  1927. p,
  1928. _chatFilters,
  1929. QPoint(0, skip - _chatFilters->height()));
  1930. }
  1931. Ui::RenderWidget(p, _scroll, QPoint(0, skip));
  1932. }
  1933. if (scrollGeometry != wideGeometry) {
  1934. _scroll->setGeometry(scrollGeometry);
  1935. updateControlsGeometry();
  1936. }
  1937. return Ui::PixmapFromImage(std::move(image));
  1938. }
  1939. void Widget::startWidthAnimation() {
  1940. if (!_widthAnimationCache.isNull()) {
  1941. return;
  1942. }
  1943. _widthAnimationCache = grabNonNarrowScrollFrame();
  1944. _scroll->hide();
  1945. if (_chatFilters) {
  1946. _chatFilters->hide();
  1947. }
  1948. updateStoriesVisibility();
  1949. }
  1950. void Widget::stopWidthAnimation() {
  1951. _widthAnimationCache = QPixmap();
  1952. if (!_showAnimation) {
  1953. _scroll->setVisible(!_suggestions);
  1954. if (_chatFilters) {
  1955. _chatFilters->setVisible(!_suggestions);
  1956. }
  1957. }
  1958. updateStoriesVisibility();
  1959. update();
  1960. }
  1961. void Widget::updateStoriesVisibility() {
  1962. updateLockUnlockVisibility();
  1963. if (!_stories) {
  1964. return;
  1965. }
  1966. const auto hidden = (_showAnimation != nullptr)
  1967. || _openedForum
  1968. || !_widthAnimationCache.isNull()
  1969. || _childList
  1970. || _searchHasFocus
  1971. || _searchSuggestionsLocked
  1972. || !_searchState.query.isEmpty()
  1973. || _searchState.inChat
  1974. || _stories->empty();
  1975. if (_stories->isHidden() != hidden) {
  1976. _stories->setVisible(!hidden);
  1977. using Type = Ui::ElasticScroll::OverscrollType;
  1978. if (hidden) {
  1979. _scroll->setOverscrollDefaults(0, 0);
  1980. _scroll->setOverscrollTypes(Type::Real, Type::Real);
  1981. if (_scroll->position().overscroll < 0) {
  1982. _scroll->scrollToY(0);
  1983. }
  1984. _scroll->update();
  1985. } else {
  1986. _scroll->setOverscrollDefaults(0, 0);
  1987. _scroll->setOverscrollTypes(Type::Virtual, Type::Real);
  1988. _storiesExplicitExpandValue.force_assign(
  1989. _storiesExplicitExpandValue.current());
  1990. }
  1991. if (_aboveScrollAdded > 0 && _updateScrollGeometryCached) {
  1992. _updateScrollGeometryCached();
  1993. }
  1994. updateLockUnlockPosition();
  1995. }
  1996. }
  1997. void Widget::showFast() {
  1998. if (isHidden()) {
  1999. _inner->clearSelection();
  2000. }
  2001. show();
  2002. }
  2003. rpl::producer<float64> Widget::shownProgressValue() const {
  2004. return _shownProgressValue.value();
  2005. }
  2006. void Widget::showAnimated(
  2007. Window::SlideDirection direction,
  2008. const Window::SectionSlideParams &params) {
  2009. _showAnimation = nullptr;
  2010. auto oldContentCache = params.oldContentCache;
  2011. showFast();
  2012. auto newContentCache = Ui::GrabWidget(this);
  2013. if (_updateTelegram) {
  2014. _updateTelegram->hide();
  2015. }
  2016. if (_connecting) {
  2017. _connecting->setForceHidden(true);
  2018. }
  2019. if (_childList) {
  2020. _childList->hide();
  2021. _childListShadow->hide();
  2022. }
  2023. _shownProgressValue = 0.;
  2024. startSlideAnimation(
  2025. std::move(oldContentCache),
  2026. std::move(newContentCache),
  2027. direction);
  2028. }
  2029. void Widget::startSlideAnimation(
  2030. QPixmap oldContentCache,
  2031. QPixmap newContentCache,
  2032. Window::SlideDirection direction) {
  2033. _scroll->hide();
  2034. if (_stories) {
  2035. _stories->hide();
  2036. }
  2037. _searchControls->hide();
  2038. if (_subsectionTopBar) {
  2039. _subsectionTopBar->hide();
  2040. }
  2041. if (_moreChatsBar) {
  2042. _moreChatsBar->hide();
  2043. }
  2044. if (_chatFilters) {
  2045. _chatFilters->hide();
  2046. }
  2047. if (_forumTopShadow) {
  2048. _forumTopShadow->hide();
  2049. }
  2050. if (_forumGroupCallBar) {
  2051. _forumGroupCallBar->hide();
  2052. }
  2053. if (_forumRequestsBar) {
  2054. _forumRequestsBar->hide();
  2055. }
  2056. if (_forumReportBar) {
  2057. _forumReportBar->bar().hide();
  2058. }
  2059. _showAnimation = std::make_unique<Window::SlideAnimation>();
  2060. _showAnimation->setDirection(direction);
  2061. _showAnimation->setRepaintCallback([=] {
  2062. if (_shownProgressValue.current() < 1.) {
  2063. _shownProgressValue = _showAnimation->progress();
  2064. }
  2065. update();
  2066. });
  2067. _showAnimation->setFinishedCallback([=] { slideFinished(); });
  2068. _showAnimation->setPixmaps(oldContentCache, newContentCache);
  2069. _showAnimation->start();
  2070. }
  2071. bool Widget::floatPlayerHandleWheelEvent(QEvent *e) {
  2072. return _scroll->viewportEvent(e);
  2073. }
  2074. QRect Widget::floatPlayerAvailableRect() {
  2075. return mapToGlobal(_scroll->geometry());
  2076. }
  2077. void Widget::slideFinished() {
  2078. _showAnimation = nullptr;
  2079. _shownProgressValue = 1.;
  2080. updateControlsVisibility(true);
  2081. if ((!_subsectionTopBar || !_subsectionTopBar->searchHasFocus())
  2082. && !_searchHasFocus) {
  2083. controller()->widget()->setInnerFocus();
  2084. }
  2085. }
  2086. void Widget::escape() {
  2087. if (!cancelSearch({ .jumpBackToSearchedChat = true })) {
  2088. if (const auto forum = controller()->shownForum().current()) {
  2089. const auto id = controller()->windowId();
  2090. const auto initial = id.forum();
  2091. if (!initial) {
  2092. controller()->closeForum();
  2093. } else if (initial != forum) {
  2094. controller()->showForum(initial);
  2095. }
  2096. } else if (controller()->openedFolder().current()) {
  2097. if (!controller()->windowId().folder()) {
  2098. controller()->closeFolder();
  2099. }
  2100. } else if (controller()->activeChatEntryCurrent().key) {
  2101. controller()->content()->dialogsCancelled();
  2102. } else if (controller()->isPrimary()) {
  2103. const auto filters = &session().data().chatsFilters();
  2104. const auto &list = filters->list();
  2105. const auto first = list.empty() ? FilterId() : list.front().id();
  2106. if (controller()->activeChatsFilterCurrent() != first) {
  2107. controller()->setActiveChatsFilter(first);
  2108. }
  2109. }
  2110. } else if (!_searchState.inChat
  2111. && controller()->activeChatEntryCurrent().key) {
  2112. controller()->content()->dialogsCancelled();
  2113. }
  2114. }
  2115. void Widget::submit() {
  2116. if (_suggestions) {
  2117. _suggestions->chooseRow();
  2118. return;
  2119. } else if (_inner->chooseRow()) {
  2120. return;
  2121. }
  2122. const auto state = _inner->state();
  2123. if (state == WidgetState::Default
  2124. || (state == WidgetState::Filtered
  2125. && _inner->hasFilteredResults())) {
  2126. _inner->selectSkip(1);
  2127. _inner->chooseRow();
  2128. } else {
  2129. search();
  2130. }
  2131. }
  2132. void Widget::refreshLoadMoreButton(bool mayBlock, bool isBlocked) {
  2133. if (_layout == Layout::Child) {
  2134. return;
  2135. }
  2136. if (!mayBlock) {
  2137. if (_loadMoreChats) {
  2138. _loadMoreChats.destroy();
  2139. updateControlsGeometry();
  2140. }
  2141. return;
  2142. }
  2143. if (!_loadMoreChats) {
  2144. _loadMoreChats.create(
  2145. this,
  2146. "Load more",
  2147. st::dialogsLoadMoreButton,
  2148. st::dialogsLoadMore,
  2149. st::dialogsLoadMore,
  2150. false);
  2151. _loadMoreChats->show();
  2152. _loadMoreChats->addClickHandler([=] {
  2153. loadMoreBlockedByDate();
  2154. });
  2155. updateControlsGeometry();
  2156. }
  2157. const auto loading = !isBlocked;
  2158. _loadMoreChats->setDisabled(loading);
  2159. _loadMoreChats->setText(loading ? "Loading..." : "Load more");
  2160. }
  2161. void Widget::loadMoreBlockedByDate() {
  2162. if (!_loadMoreChats
  2163. || _loadMoreChats->isDisabled()
  2164. || _loadMoreChats->isHidden()) {
  2165. return;
  2166. }
  2167. session().api().requestMoreBlockedByDateDialogs();
  2168. }
  2169. bool Widget::search(bool inCache, SearchRequestDelay delay) {
  2170. _processingSearch = true;
  2171. const auto guard = gsl::finally([&] {
  2172. _processingSearch = false;
  2173. listScrollUpdated();
  2174. });
  2175. auto result = false;
  2176. const auto query = _searchState.query.trimmed();
  2177. const auto trimmed = (query.isEmpty() || query[0] != '#')
  2178. ? query
  2179. : query.mid(1).trimmed();
  2180. const auto inPeer = searchInPeer();
  2181. const auto fromPeer = searchFromPeer();
  2182. const auto &inTags = searchInTags();
  2183. const auto tab = _searchState.tab;
  2184. const auto filter = _searchState.filter;
  2185. const auto fromStartType = SearchRequestType{
  2186. .start = true,
  2187. .peer = (inPeer != nullptr),
  2188. };
  2189. if (trimmed.isEmpty() && !fromPeer && inTags.empty()) {
  2190. cancelSearchRequest();
  2191. searchApplyEmpty(fromStartType, currentSearchProcess());
  2192. if (_searchWithPostsPreview) {
  2193. searchApplyEmpty(
  2194. { .posts = true, .start = true },
  2195. &_postsProcess);
  2196. }
  2197. _api.request(base::take(_peerSearchRequest)).cancel();
  2198. _peerSearchQuery = QString();
  2199. peerSearchApplyEmpty(0);
  2200. _api.request(base::take(_topicSearchRequest)).cancel();
  2201. return true;
  2202. } else if (inCache) {
  2203. const auto success = _singleMessageSearch.lookup(query, [=] {
  2204. searchRequested(delay);
  2205. });
  2206. if (!success) {
  2207. return false;
  2208. }
  2209. const auto process = currentSearchProcess();
  2210. const auto i = process->cache.find(query);
  2211. if (i != process->cache.end()) {
  2212. _searchQuery = query;
  2213. _searchQueryFrom = fromPeer;
  2214. _searchQueryTags = inTags;
  2215. _searchQueryTab = tab;
  2216. _searchQueryFilter = filter;
  2217. process->nextRate = 0;
  2218. process->full = false;
  2219. _migratedProcess.full = false;
  2220. cancelSearchRequest();
  2221. searchReceived(fromStartType, i->second, process, true);
  2222. result = true;
  2223. }
  2224. } else if (_searchQuery != query
  2225. || _searchQueryFrom != fromPeer
  2226. || _searchQueryTags != inTags
  2227. || _searchQueryTab != tab
  2228. || _searchQueryFilter != filter) {
  2229. const auto process = currentSearchProcess();
  2230. _searchQuery = query;
  2231. _searchQueryFrom = fromPeer;
  2232. _searchQueryTags = inTags;
  2233. _searchQueryTab = tab;
  2234. _searchQueryFilter = filter;
  2235. process->nextRate = 0;
  2236. process->full = false;
  2237. _migratedProcess.full = false;
  2238. cancelSearchRequest();
  2239. if (inPeer) {
  2240. const auto topic = searchInTopic();
  2241. auto &histories = session().data().histories();
  2242. const auto type = Data::Histories::RequestType::History;
  2243. const auto history = session().data().history(inPeer);
  2244. const auto sublist = _openedForum
  2245. ? nullptr
  2246. : _searchState.inChat.sublist();
  2247. const auto fromPeer = sublist ? nullptr : _searchQueryFrom;
  2248. const auto savedPeer = sublist
  2249. ? sublist->peer().get()
  2250. : nullptr;
  2251. _historiesRequest = histories.sendRequest(history, type, [=](
  2252. Fn<void()> finish) {
  2253. const auto type = SearchRequestType{
  2254. .start = true,
  2255. .peer = true,
  2256. };
  2257. using Flag = MTPmessages_Search::Flag;
  2258. process->requestId = session().api().request(
  2259. MTPmessages_Search(
  2260. MTP_flags((topic ? Flag::f_top_msg_id : Flag())
  2261. | (fromPeer ? Flag::f_from_id : Flag())
  2262. | (savedPeer ? Flag::f_saved_peer_id : Flag())
  2263. | (_searchQueryTags.empty()
  2264. ? Flag()
  2265. : Flag::f_saved_reaction)),
  2266. inPeer->input,
  2267. MTP_string(_searchQuery),
  2268. (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
  2269. (savedPeer ? savedPeer->input : MTP_inputPeerEmpty()),
  2270. MTP_vector_from_range(
  2271. _searchQueryTags | ranges::views::transform(
  2272. Data::ReactionToMTP
  2273. )),
  2274. MTP_int(topic ? topic->rootId() : 0),
  2275. MTP_inputMessagesFilterEmpty(),
  2276. MTP_int(0), // min_date
  2277. MTP_int(0), // max_date
  2278. MTP_int(0), // offset_id
  2279. MTP_int(0), // add_offset
  2280. MTP_int(kSearchPerPage),
  2281. MTP_int(0), // max_id
  2282. MTP_int(0), // min_id
  2283. MTP_long(0)) // hash
  2284. ).done([=](const MTPmessages_Messages &result) {
  2285. _historiesRequest = 0;
  2286. searchReceived(type, result, process);
  2287. finish();
  2288. }).fail([=](const MTP::Error &error) {
  2289. _historiesRequest = 0;
  2290. searchFailed(type, error, process);
  2291. finish();
  2292. }).send();
  2293. process->queries.emplace(process->requestId, _searchQuery);
  2294. return process->requestId;
  2295. });
  2296. } else if (_searchState.tab == ChatSearchTab::PublicPosts) {
  2297. requestPublicPosts(true);
  2298. } else {
  2299. requestMessages(true);
  2300. }
  2301. _inner->searchRequested(true);
  2302. } else {
  2303. _inner->searchRequested(false);
  2304. }
  2305. const auto peerQuery = Api::ConvertPeerSearchQuery(query);
  2306. if (searchForPeersRequired(peerQuery)) {
  2307. if (inCache) {
  2308. auto i = _peerSearchCache.find(peerQuery);
  2309. if (i != _peerSearchCache.end()) {
  2310. _peerSearchQuery = peerQuery;
  2311. _peerSearchRequest = 0;
  2312. peerSearchReceived(i->second, 0);
  2313. }
  2314. } else if (_peerSearchQuery != peerQuery) {
  2315. _peerSearchQuery = peerQuery;
  2316. _peerSearchFull = false;
  2317. _peerSearchRequest = _api.request(MTPcontacts_Search(
  2318. MTP_string(_peerSearchQuery),
  2319. MTP_int(SearchPeopleLimit)
  2320. )).done([=](
  2321. const MTPcontacts_Found &result,
  2322. mtpRequestId requestId) {
  2323. peerSearchReceived(result, requestId);
  2324. }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
  2325. peerSearchFailed(error, requestId);
  2326. }).send();
  2327. _peerSearchQueries.emplace(_peerSearchRequest, _peerSearchQuery);
  2328. }
  2329. } else {
  2330. _api.request(base::take(_peerSearchRequest)).cancel();
  2331. _peerSearchQuery = peerQuery;
  2332. peerSearchApplyEmpty(0);
  2333. }
  2334. if (searchForTopicsRequired(peerQuery)) {
  2335. if (inCache) {
  2336. if (_topicSearchQuery != peerQuery) {
  2337. result = false;
  2338. }
  2339. } else if (_topicSearchQuery != peerQuery) {
  2340. _topicSearchQuery = peerQuery;
  2341. _topicSearchFull = false;
  2342. searchTopics();
  2343. }
  2344. } else {
  2345. _api.request(base::take(_topicSearchRequest)).cancel();
  2346. _topicSearchQuery = peerQuery;
  2347. _topicSearchFull = true;
  2348. }
  2349. return result;
  2350. }
  2351. bool Widget::searchForPeersRequired(const QString &query) const {
  2352. return _searchState.filterChatsList()
  2353. && !_openedForum
  2354. && !query.isEmpty()
  2355. && (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None);
  2356. }
  2357. bool Widget::searchForTopicsRequired(const QString &query) const {
  2358. return _searchState.filterChatsList()
  2359. && _openedForum
  2360. && !query.isEmpty()
  2361. && (IsHashOrCashtagSearchQuery(query) == HashOrCashtag::None)
  2362. && !_openedForum->topicsList()->loaded();
  2363. }
  2364. void Widget::searchRequested(SearchRequestDelay delay) {
  2365. if (search(true, delay)) {
  2366. return;
  2367. } else if (delay == SearchRequestDelay::Instant) {
  2368. _searchTimer.cancel();
  2369. search();
  2370. } else {
  2371. _searchTimer.callOnce(kSearchRequestDelay);
  2372. }
  2373. }
  2374. void Widget::showMainMenu() {
  2375. controller()->widget()->showMainMenu();
  2376. }
  2377. void Widget::searchMessages(SearchState state) {
  2378. applySearchState(std::move(state));
  2379. session().local().saveRecentSearchHashtags(_searchState.query);
  2380. }
  2381. void Widget::searchTopics() {
  2382. if (_topicSearchRequest || _topicSearchFull) {
  2383. return;
  2384. }
  2385. _api.request(base::take(_topicSearchRequest)).cancel();
  2386. _topicSearchRequest = _api.request(MTPchannels_GetForumTopics(
  2387. MTP_flags(MTPchannels_GetForumTopics::Flag::f_q),
  2388. _openedForum->channel()->inputChannel,
  2389. MTP_string(_topicSearchQuery),
  2390. MTP_int(_topicSearchOffsetDate),
  2391. MTP_int(_topicSearchOffsetId),
  2392. MTP_int(_topicSearchOffsetTopicId),
  2393. MTP_int(kSearchPerPage)
  2394. )).done([=](const MTPmessages_ForumTopics &result) {
  2395. _topicSearchRequest = 0;
  2396. const auto savedTopicId = _topicSearchOffsetTopicId;
  2397. const auto byCreation = result.data().is_order_by_create_date();
  2398. _openedForum->applyReceivedTopics(result, [&](
  2399. not_null<Data::ForumTopic*> topic) {
  2400. _topicSearchOffsetTopicId = topic->rootId();
  2401. if (byCreation) {
  2402. _topicSearchOffsetDate = topic->creationDate();
  2403. if (const auto last = topic->lastServerMessage()) {
  2404. _topicSearchOffsetId = last->id;
  2405. }
  2406. } else if (const auto last = topic->lastServerMessage()) {
  2407. _topicSearchOffsetId = last->id;
  2408. _topicSearchOffsetDate = last->date();
  2409. }
  2410. _inner->appendToFiltered(topic);
  2411. });
  2412. if (_topicSearchOffsetTopicId != savedTopicId) {
  2413. _inner->refresh();
  2414. } else {
  2415. _topicSearchFull = true;
  2416. }
  2417. }).fail([=] {
  2418. _topicSearchFull = true;
  2419. }).send();
  2420. }
  2421. void Widget::searchMore() {
  2422. const auto process = currentSearchProcess();
  2423. if (process->requestId
  2424. || _historiesRequest
  2425. || _searchTimer.isActive()) {
  2426. return;
  2427. } else if (!process->full) {
  2428. if (const auto peer = searchInPeer()) {
  2429. auto &histories = session().data().histories();
  2430. const auto topic = searchInTopic();
  2431. const auto type = Data::Histories::RequestType::History;
  2432. const auto history = session().data().history(peer);
  2433. const auto sublist = _openedForum
  2434. ? nullptr
  2435. : _searchState.inChat.sublist();
  2436. const auto fromPeer = sublist ? nullptr : _searchQueryFrom;
  2437. const auto savedPeer = sublist
  2438. ? sublist->peer().get()
  2439. : nullptr;
  2440. _historiesRequest = histories.sendRequest(history, type, [=](
  2441. Fn<void()> finish) {
  2442. const auto type = SearchRequestType{
  2443. .start = !process->lastId,
  2444. .peer = true,
  2445. };
  2446. using Flag = MTPmessages_Search::Flag;
  2447. process->requestId = session().api().request(
  2448. MTPmessages_Search(
  2449. MTP_flags((topic ? Flag::f_top_msg_id : Flag())
  2450. | (fromPeer ? Flag::f_from_id : Flag())
  2451. | (savedPeer ? Flag::f_saved_peer_id : Flag())
  2452. | (_searchQueryTags.empty()
  2453. ? Flag()
  2454. : Flag::f_saved_reaction)),
  2455. peer->input,
  2456. MTP_string(_searchQuery),
  2457. (fromPeer ? fromPeer->input : MTP_inputPeerEmpty()),
  2458. (savedPeer
  2459. ? savedPeer->input
  2460. : MTP_inputPeerEmpty()),
  2461. MTP_vector_from_range(
  2462. _searchQueryTags | ranges::views::transform(
  2463. Data::ReactionToMTP
  2464. )),
  2465. MTP_int(topic ? topic->rootId() : 0),
  2466. MTP_inputMessagesFilterEmpty(),
  2467. MTP_int(0), // min_date
  2468. MTP_int(0), // max_date
  2469. MTP_int(process->lastId),
  2470. MTP_int(0), // add_offset
  2471. MTP_int(kSearchPerPage),
  2472. MTP_int(0), // max_id
  2473. MTP_int(0), // min_id
  2474. MTP_long(0)) // hash
  2475. ).done([=](const MTPmessages_Messages &result) {
  2476. searchReceived(type, result, process);
  2477. _historiesRequest = 0;
  2478. finish();
  2479. }).fail([=](const MTP::Error &error) {
  2480. searchFailed(type, error, process);
  2481. _historiesRequest = 0;
  2482. finish();
  2483. }).send();
  2484. if (!process->lastId) {
  2485. process->queries.emplace(
  2486. process->requestId,
  2487. _searchQuery);
  2488. }
  2489. return process->requestId;
  2490. });
  2491. } else if (_searchState.tab == ChatSearchTab::PublicPosts) {
  2492. requestPublicPosts(false);
  2493. } else {
  2494. requestMessages(false);
  2495. }
  2496. } else if (_searchInMigrated && !_migratedProcess.full) {
  2497. auto &histories = session().data().histories();
  2498. const auto type = Data::Histories::RequestType::History;
  2499. const auto history = _searchInMigrated;
  2500. _historiesRequest = histories.sendRequest(history, type, [=](
  2501. Fn<void()> finish) {
  2502. const auto type = SearchRequestType{
  2503. .migrated = true,
  2504. .start = !_migratedProcess.lastId,
  2505. };
  2506. const auto flags = _searchQueryFrom
  2507. ? MTP_flags(MTPmessages_Search::Flag::f_from_id)
  2508. : MTP_flags(0);
  2509. _migratedProcess.requestId = session().api().request(
  2510. MTPmessages_Search(
  2511. flags,
  2512. _searchInMigrated->peer->input,
  2513. MTP_string(_searchQuery),
  2514. (_searchQueryFrom
  2515. ? _searchQueryFrom->input
  2516. : MTP_inputPeerEmpty()),
  2517. MTPInputPeer(), // saved_peer_id
  2518. MTPVector<MTPReaction>(), // saved_reaction
  2519. MTPint(), // top_msg_id
  2520. MTP_inputMessagesFilterEmpty(),
  2521. MTP_int(0), // min_date
  2522. MTP_int(0), // max_date
  2523. MTP_int(_migratedProcess.lastId),
  2524. MTP_int(0), // add_offset
  2525. MTP_int(kSearchPerPage),
  2526. MTP_int(0), // max_id
  2527. MTP_int(0), // min_id
  2528. MTP_long(0)) // hash
  2529. ).done([=](const MTPmessages_Messages &result) {
  2530. searchReceived(type, result, &_migratedProcess);
  2531. _historiesRequest = 0;
  2532. finish();
  2533. }).fail([=](const MTP::Error &error) {
  2534. searchFailed(type, error, &_migratedProcess);
  2535. _historiesRequest = 0;
  2536. finish();
  2537. }).send();
  2538. return _migratedProcess.requestId;
  2539. });
  2540. }
  2541. }
  2542. void Widget::requestPublicPosts(bool fromStart) {
  2543. if (!_postsProcess.lastId || !_postsProcess.lastPeer) {
  2544. fromStart = true;
  2545. }
  2546. const auto type = SearchRequestType{
  2547. .posts = true,
  2548. .start = fromStart,
  2549. };
  2550. _postsProcess.requestId = session().api().request(
  2551. MTPchannels_SearchPosts(
  2552. MTP_string(_searchState.query.trimmed().mid(1)),
  2553. MTP_int(fromStart ? 0 : _postsProcess.nextRate),
  2554. (fromStart
  2555. ? MTP_inputPeerEmpty()
  2556. : _postsProcess.lastPeer->input),
  2557. MTP_int(fromStart ? 0 : _postsProcess.lastId),
  2558. MTP_int(kSearchPerPage))
  2559. ).done([=](const MTPmessages_Messages &result) {
  2560. searchReceived(type, result, &_postsProcess);
  2561. }).fail([=](const MTP::Error &error) {
  2562. searchFailed(type, error, &_postsProcess);
  2563. }).send();
  2564. if (fromStart) {
  2565. _postsProcess.queries.emplace(_postsProcess.requestId, _searchQuery);
  2566. }
  2567. }
  2568. void Widget::requestMessages(bool fromStart) {
  2569. if (!_searchProcess.lastId || !_searchProcess.lastPeer) {
  2570. fromStart = true;
  2571. }
  2572. const auto type = SearchRequestType{
  2573. .start = fromStart,
  2574. };
  2575. using Flag = MTPmessages_SearchGlobal::Flag;
  2576. const auto flags = Flag()
  2577. | (session().settings().skipArchiveInSearch()
  2578. ? Flag::f_folder_id
  2579. : Flag())
  2580. | (_searchQueryFilter == ChatTypeFilter::Private
  2581. ? Flag::f_users_only
  2582. : _searchQueryFilter == ChatTypeFilter::Groups
  2583. ? Flag::f_groups_only
  2584. : _searchQueryFilter == ChatTypeFilter::Channels
  2585. ? Flag::f_broadcasts_only
  2586. : Flag());
  2587. const auto folderId = 0;
  2588. _searchProcess.requestId = session().api().request(
  2589. MTPmessages_SearchGlobal(
  2590. MTP_flags(flags),
  2591. MTP_int(folderId),
  2592. MTP_string(_searchQuery),
  2593. MTP_inputMessagesFilterEmpty(),
  2594. MTP_int(0), // min_date
  2595. MTP_int(0), // max_date
  2596. MTP_int(fromStart ? 0 : _searchProcess.nextRate),
  2597. (fromStart
  2598. ? MTP_inputPeerEmpty()
  2599. : _searchProcess.lastPeer->input),
  2600. MTP_int(fromStart ? 0 : _searchProcess.lastId),
  2601. MTP_int(kSearchPerPage))
  2602. ).done([=](const MTPmessages_Messages &result) {
  2603. searchReceived(type, result, &_searchProcess);
  2604. }).fail([=](const MTP::Error &error) {
  2605. searchFailed(type, error, &_searchProcess);
  2606. }).send();
  2607. if (!_searchProcess.lastId) {
  2608. _searchProcess.queries.emplace(
  2609. _searchProcess.requestId,
  2610. _searchQuery);
  2611. }
  2612. if (fromStart && _searchWithPostsPreview) {
  2613. requestPublicPosts(true);
  2614. }
  2615. }
  2616. auto Widget::currentSearchProcess() -> not_null<SearchProcessState*> {
  2617. return (_searchState.tab == ChatSearchTab::PublicPosts)
  2618. ? &_postsProcess
  2619. : &_searchProcess;
  2620. }
  2621. bool Widget::computeSearchWithPostsPreview() const {
  2622. return (_searchHashOrCashtag != HashOrCashtag::None)
  2623. && (_searchState.tab == ChatSearchTab::MyMessages);
  2624. }
  2625. void Widget::searchReceived(
  2626. SearchRequestType type,
  2627. const MTPmessages_Messages &result,
  2628. not_null<SearchProcessState*> process,
  2629. bool cacheResults) {
  2630. const auto state = _inner->state();
  2631. if (!cacheResults
  2632. && (state == WidgetState::Filtered)
  2633. && type.start) {
  2634. const auto i = process->queries.find(process->requestId);
  2635. if (i != process->queries.end()) {
  2636. process->cache[i->second] = result;
  2637. process->queries.erase(i);
  2638. }
  2639. }
  2640. const auto inject = (type.start && !type.posts)
  2641. ? *_singleMessageSearch.lookup(_searchQuery)
  2642. : nullptr;
  2643. if (cacheResults && process->requestId) {
  2644. return;
  2645. }
  2646. if (type.start) {
  2647. process->lastPeer = nullptr;
  2648. process->lastId = 0;
  2649. }
  2650. const auto processList = [&](const MTPVector<MTPMessage> &messages) {
  2651. auto result = std::vector<not_null<HistoryItem*>>();
  2652. for (const auto &message : messages.v) {
  2653. const auto msgId = IdFromMessage(message);
  2654. const auto peerId = PeerFromMessage(message);
  2655. const auto lastDate = DateFromMessage(message);
  2656. if (const auto peer = session().data().peerLoaded(peerId)) {
  2657. if (lastDate) {
  2658. const auto item = session().data().addNewMessage(
  2659. message,
  2660. MessageFlags(),
  2661. NewMessageType::Existing);
  2662. result.push_back(item);
  2663. }
  2664. process->lastPeer = peer;
  2665. } else {
  2666. LOG(("API Error: a search results with not loaded peer %1"
  2667. ).arg(peerId.value));
  2668. }
  2669. process->lastId = msgId;
  2670. }
  2671. return result;
  2672. };
  2673. auto fullCount = 0;
  2674. auto messages = result.match([&](const MTPDmessages_messages &data) {
  2675. if (!cacheResults) {
  2676. // Don't apply cached data!
  2677. session().data().processUsers(data.vusers());
  2678. session().data().processChats(data.vchats());
  2679. }
  2680. process->full = true;
  2681. auto list = processList(data.vmessages());
  2682. fullCount = list.size();
  2683. return list;
  2684. }, [&](const MTPDmessages_messagesSlice &data) {
  2685. if (!cacheResults) {
  2686. // Don't apply cached data!
  2687. session().data().processUsers(data.vusers());
  2688. session().data().processChats(data.vchats());
  2689. }
  2690. auto list = processList(data.vmessages());
  2691. const auto nextRate = data.vnext_rate();
  2692. const auto rateUpdated = nextRate
  2693. && (nextRate->v != process->nextRate);
  2694. const auto finished = (type.peer || type.migrated || type.posts)
  2695. ? list.empty()
  2696. : !rateUpdated;
  2697. if (rateUpdated) {
  2698. process->nextRate = nextRate->v;
  2699. }
  2700. if (finished) {
  2701. process->full = true;
  2702. }
  2703. fullCount = data.vcount().v;
  2704. return list;
  2705. }, [&](const MTPDmessages_channelMessages &data) {
  2706. if (const auto peer = searchInPeer()) {
  2707. if (const auto channel = peer->asChannel()) {
  2708. channel->ptsReceived(data.vpts().v);
  2709. channel->processTopics(data.vtopics());
  2710. } else {
  2711. LOG(("API Error: "
  2712. "received messages.channelMessages when no channel "
  2713. "was passed! (Widget::searchReceived)"));
  2714. }
  2715. } else {
  2716. LOG(("API Error: "
  2717. "received messages.channelMessages when no channel "
  2718. "was passed! (Widget::searchReceived)"));
  2719. }
  2720. if (!cacheResults) {
  2721. // Don't apply cached data!
  2722. session().data().processUsers(data.vusers());
  2723. session().data().processChats(data.vchats());
  2724. }
  2725. auto list = processList(data.vmessages());
  2726. if (list.empty()) {
  2727. process->full = true;
  2728. }
  2729. fullCount = data.vcount().v;
  2730. return list;
  2731. }, [&](const MTPDmessages_messagesNotModified &) {
  2732. LOG(("API Error: received messages.messagesNotModified! "
  2733. "(Widget::searchReceived)"));
  2734. process->full = true;
  2735. return std::vector<not_null<HistoryItem*>>();
  2736. });
  2737. _inner->searchReceived(messages, inject, type, fullCount);
  2738. process->requestId = 0;
  2739. listScrollUpdated();
  2740. update();
  2741. }
  2742. void Widget::peerSearchReceived(
  2743. const MTPcontacts_Found &result,
  2744. mtpRequestId requestId) {
  2745. const auto state = _inner->state();
  2746. auto q = _peerSearchQuery;
  2747. if (state == WidgetState::Filtered) {
  2748. auto i = _peerSearchQueries.find(requestId);
  2749. if (i != _peerSearchQueries.end()) {
  2750. _peerSearchCache[i->second] = result;
  2751. _peerSearchQueries.erase(i);
  2752. }
  2753. }
  2754. if (_peerSearchRequest == requestId) {
  2755. switch (result.type()) {
  2756. case mtpc_contacts_found: {
  2757. auto &d = result.c_contacts_found();
  2758. session().data().processUsers(d.vusers());
  2759. session().data().processChats(d.vchats());
  2760. _inner->peerSearchReceived(q, d.vmy_results().v, d.vresults().v);
  2761. } break;
  2762. }
  2763. _peerSearchRequest = 0;
  2764. listScrollUpdated();
  2765. }
  2766. }
  2767. void Widget::searchApplyEmpty(
  2768. SearchRequestType type,
  2769. not_null<SearchProcessState*> process) {
  2770. process->full = true;
  2771. searchReceived(
  2772. type,
  2773. MTP_messages_messages(
  2774. MTP_vector<MTPMessage>(),
  2775. MTP_vector<MTPChat>(),
  2776. MTP_vector<MTPUser>()),
  2777. process);
  2778. }
  2779. void Widget::peerSearchApplyEmpty(mtpRequestId id) {
  2780. _peerSearchFull = true;
  2781. peerSearchReceived(
  2782. MTP_contacts_found(
  2783. MTP_vector<MTPPeer>(0),
  2784. MTP_vector<MTPPeer>(0),
  2785. MTP_vector<MTPChat>(0),
  2786. MTP_vector<MTPUser>(0)),
  2787. id);
  2788. }
  2789. void Widget::searchFailed(
  2790. SearchRequestType type,
  2791. const MTP::Error &error,
  2792. not_null<SearchProcessState*> process) {
  2793. if (error.type() == u"SEARCH_QUERY_EMPTY"_q) {
  2794. searchApplyEmpty(type, process);
  2795. } else {
  2796. process->requestId = 0;
  2797. process->full = true;
  2798. }
  2799. }
  2800. void Widget::peerSearchFailed(const MTP::Error &error, mtpRequestId id) {
  2801. if (_peerSearchRequest == id) {
  2802. _peerSearchRequest = 0;
  2803. _peerSearchFull = true;
  2804. }
  2805. }
  2806. void Widget::dragEnterEvent(QDragEnterEvent *e) {
  2807. using namespace Storage;
  2808. const auto data = e->mimeData();
  2809. _dragInScroll = false;
  2810. _dragForward = !controller()->adaptive().isOneColumn()
  2811. && data->hasFormat(u"application/x-td-forward"_q);
  2812. if (_dragForward) {
  2813. e->setDropAction(Qt::CopyAction);
  2814. e->accept();
  2815. updateDragInScroll(_scroll->geometry().contains(e->pos()));
  2816. } else if (ComputeMimeDataState(data) != MimeDataState::None) {
  2817. e->setDropAction(Qt::CopyAction);
  2818. e->accept();
  2819. }
  2820. _chooseByDragTimer.cancel();
  2821. }
  2822. void Widget::dragMoveEvent(QDragMoveEvent *e) {
  2823. if (_scroll->geometry().contains(e->pos())) {
  2824. if (_dragForward) {
  2825. updateDragInScroll(true);
  2826. } else {
  2827. _chooseByDragTimer.callOnce(ChoosePeerByDragTimeout);
  2828. }
  2829. const auto global = mapToGlobal(e->pos());
  2830. const auto thread = _suggestions
  2831. ? _suggestions->updateFromParentDrag(global)
  2832. : _inner->updateFromParentDrag(global);
  2833. e->setDropAction(thread ? Qt::CopyAction : Qt::IgnoreAction);
  2834. } else {
  2835. if (_dragForward) {
  2836. updateDragInScroll(false);
  2837. }
  2838. if (_suggestions) {
  2839. _suggestions->dragLeft();
  2840. }
  2841. _inner->dragLeft();
  2842. e->setDropAction(Qt::IgnoreAction);
  2843. }
  2844. e->accept();
  2845. }
  2846. void Widget::dragLeaveEvent(QDragLeaveEvent *e) {
  2847. if (_dragForward) {
  2848. updateDragInScroll(false);
  2849. } else {
  2850. _chooseByDragTimer.cancel();
  2851. }
  2852. if (_suggestions) {
  2853. _suggestions->dragLeft();
  2854. }
  2855. _inner->dragLeft();
  2856. e->accept();
  2857. }
  2858. void Widget::updateDragInScroll(bool inScroll) {
  2859. if (_dragInScroll != inScroll) {
  2860. _dragInScroll = inScroll;
  2861. if (_dragInScroll) {
  2862. controller()->content()->showDragForwardInfo();
  2863. } else {
  2864. controller()->content()->dialogsCancelled();
  2865. }
  2866. }
  2867. }
  2868. void Widget::dropEvent(QDropEvent *e) {
  2869. _chooseByDragTimer.cancel();
  2870. if (_scroll->geometry().contains(e->pos())) {
  2871. const auto globalPosition = mapToGlobal(e->pos());
  2872. const auto thread = _suggestions
  2873. ? _suggestions->updateFromParentDrag(globalPosition)
  2874. : _inner->updateFromParentDrag(globalPosition);
  2875. if (thread) {
  2876. e->setDropAction(Qt::CopyAction);
  2877. e->accept();
  2878. controller()->content()->filesOrForwardDrop(
  2879. thread,
  2880. e->mimeData());
  2881. if (!thread->owningHistory()->isForum()) {
  2882. hideChildList();
  2883. }
  2884. controller()->widget()->raise();
  2885. controller()->widget()->activateWindow();
  2886. }
  2887. }
  2888. }
  2889. void Widget::listScrollUpdated() {
  2890. const auto scrollTop = _scroll->scrollTop();
  2891. _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
  2892. updateScrollUpVisibility();
  2893. // Fix button rendering glitch, Qt bug with WA_OpaquePaintEvent widgets.
  2894. _scrollToTop->update();
  2895. }
  2896. void Widget::updateCancelSearch() {
  2897. const auto shown = !_searchState.query.isEmpty()
  2898. || (!_searchState.inChat
  2899. && (_searchHasFocus || _searchSuggestionsLocked));
  2900. _cancelSearch->toggle(shown, anim::type::normal);
  2901. }
  2902. QString Widget::validateSearchQuery() {
  2903. const auto query = currentSearchQuery();
  2904. if (!_subsectionTopBar
  2905. && _suggestions
  2906. && _suggestions->consumeSearchQuery(query)) {
  2907. return QString();
  2908. } else if (_searchState.tab == ChatSearchTab::PublicPosts) {
  2909. if (_searchHashOrCashtag == HashOrCashtag::None) {
  2910. _searchHashOrCashtag = HashOrCashtag::Hashtag;
  2911. }
  2912. const auto fixed = FixHashtagSearchQuery(
  2913. query,
  2914. currentSearchQueryCursorPosition(),
  2915. _searchHashOrCashtag);
  2916. if (fixed.text != query) {
  2917. setSearchQuery(fixed.text, fixed.cursorPosition);
  2918. }
  2919. return fixed.text;
  2920. } else {
  2921. _searchHashOrCashtag = IsHashOrCashtagSearchQuery(query);
  2922. }
  2923. _searchWithPostsPreview = computeSearchWithPostsPreview();
  2924. return query;
  2925. }
  2926. void Widget::applySearchUpdate() {
  2927. auto copy = _searchState;
  2928. copy.query = validateSearchQuery();
  2929. applySearchState(std::move(copy));
  2930. if (_chooseFromUser->toggled()
  2931. || _searchState.fromPeer
  2932. || !_searchState.tags.empty()) {
  2933. auto switchToChooseFrom = HistoryView::SwitchToChooseFromQuery();
  2934. if (_lastSearchText != switchToChooseFrom
  2935. && switchToChooseFrom.startsWith(_lastSearchText)
  2936. && _searchState.query == switchToChooseFrom) {
  2937. showSearchFrom();
  2938. }
  2939. }
  2940. _lastSearchText = _searchState.query;
  2941. }
  2942. void Widget::updateForceDisplayWide() {
  2943. if (_childList) {
  2944. _childList->updateForceDisplayWide();
  2945. return;
  2946. }
  2947. controller()->setChatsForceDisplayWide(_searchHasFocus
  2948. || (_subsectionTopBar && _subsectionTopBar->searchHasFocus())
  2949. || _searchSuggestionsLocked
  2950. || !_searchState.query.isEmpty()
  2951. || _searchState.inChat);
  2952. }
  2953. void Widget::showForum(
  2954. not_null<Data::Forum*> forum,
  2955. const Window::SectionShow &params) {
  2956. if (_openedForum == forum) {
  2957. return;
  2958. }
  2959. const auto nochat = !controller()->mainSectionShown();
  2960. if (!params.childColumn
  2961. || (Core::App().settings().dialogsWidthRatio(nochat) == 0.)
  2962. || (_layout != Layout::Main)
  2963. || OptionForumHideChatsList.value()) {
  2964. changeOpenedForum(forum, params.animated);
  2965. return;
  2966. }
  2967. cancelSearch({ .forceFullCancel = true });
  2968. openChildList(forum, params);
  2969. }
  2970. void Widget::openChildList(
  2971. not_null<Data::Forum*> forum,
  2972. const Window::SectionShow &params) {
  2973. auto slide = Window::SectionSlideParams();
  2974. const auto animated = !_childList
  2975. && (params.animated == anim::type::normal);
  2976. if (animated) {
  2977. destroyChildListCanvas();
  2978. slide.oldContentCache = Ui::GrabWidget(
  2979. this,
  2980. QRect(_narrowWidth, 0, width() - _narrowWidth, height()));
  2981. }
  2982. auto copy = params;
  2983. copy.childColumn = false;
  2984. copy.animated = anim::type::instant;
  2985. {
  2986. if (_childList && InFocusChain(_childList.get())) {
  2987. setFocus();
  2988. }
  2989. _childList = std::make_unique<Widget>(
  2990. this,
  2991. controller(),
  2992. Layout::Child);
  2993. _childList->showForum(forum, copy);
  2994. _childListPeerId = forum->channel()->id;
  2995. }
  2996. _childListShadow = std::make_unique<Ui::RpWidget>(this);
  2997. const auto shadow = _childListShadow.get();
  2998. const auto opacity = shadow->lifetime().make_state<float64>(0.);
  2999. shadow->setAttribute(Qt::WA_TransparentForMouseEvents);
  3000. shadow->paintRequest(
  3001. ) | rpl::start_with_next([=](QRect clip) {
  3002. auto p = QPainter(shadow);
  3003. p.setOpacity(*opacity);
  3004. p.fillRect(clip, st::shadowFg);
  3005. }, shadow->lifetime());
  3006. _childListShown.value() | rpl::start_with_next([=](float64 value) {
  3007. *opacity = value;
  3008. update();
  3009. _inner->update();
  3010. _search->setVisible(value < 1.);
  3011. if (!value && _childListShadow.get() != shadow) {
  3012. delete shadow;
  3013. }
  3014. }, shadow->lifetime());
  3015. updateControlsGeometry();
  3016. updateControlsVisibility(true);
  3017. if (animated) {
  3018. _childList->showAnimated(Window::SlideDirection::FromRight, slide);
  3019. _childListShown = _childList->shownProgressValue();
  3020. } else {
  3021. _childListShown = 1.;
  3022. }
  3023. if (hasFocus()) {
  3024. setInnerFocus();
  3025. }
  3026. updateForceDisplayWide();
  3027. }
  3028. void Widget::closeChildList(anim::type animated) {
  3029. if (!_childList) {
  3030. return;
  3031. }
  3032. const auto geometry = _childList->geometry();
  3033. const auto shown = _childListShown.current();
  3034. auto oldContentCache = QPixmap();
  3035. auto animation = (Window::SlideAnimation*)nullptr;
  3036. if (animated == anim::type::normal) {
  3037. oldContentCache = Ui::GrabWidget(_childList.get());
  3038. _hideChildListCanvas = std::make_unique<Ui::RpWidget>(this);
  3039. _hideChildListCanvas->setAttribute(Qt::WA_TransparentForMouseEvents);
  3040. _hideChildListCanvas->setGeometry(geometry);
  3041. animation = _hideChildListCanvas->lifetime().make_state<
  3042. Window::SlideAnimation
  3043. >();
  3044. _hideChildListCanvas->paintRequest(
  3045. ) | rpl::start_with_next([=] {
  3046. QPainter p(_hideChildListCanvas.get());
  3047. animation->paintContents(p);
  3048. }, _hideChildListCanvas->lifetime());
  3049. }
  3050. if (InFocusChain(_childList.get())) {
  3051. setFocus();
  3052. }
  3053. _childList = nullptr;
  3054. _childListShown = 0.;
  3055. if (hasFocus()) {
  3056. setInnerFocus();
  3057. _search->finishAnimating();
  3058. }
  3059. if (animated == anim::type::normal) {
  3060. _hideChildListCanvas->hide();
  3061. auto newContentCache = Ui::GrabWidget(this, geometry);
  3062. _hideChildListCanvas->show();
  3063. _childListShown = shown;
  3064. _childListShadow.release();
  3065. animation->setDirection(Window::SlideDirection::FromLeft);
  3066. animation->setRepaintCallback([=] {
  3067. _childListShown = (1. - animation->progress()) * shown;
  3068. _hideChildListCanvas->update();
  3069. });
  3070. animation->setFinishedCallback([=] {
  3071. destroyChildListCanvas();
  3072. });
  3073. animation->setPixmaps(oldContentCache, newContentCache);
  3074. animation->start();
  3075. } else {
  3076. _childListShadow = nullptr;
  3077. }
  3078. updateStoriesVisibility();
  3079. updateForceDisplayWide();
  3080. }
  3081. bool Widget::applySearchState(SearchState state) {
  3082. if (_searchState == state) {
  3083. return true;
  3084. } else if (_childList) {
  3085. if (_childList->applySearchState(state)) {
  3086. return true;
  3087. }
  3088. hideChildList();
  3089. }
  3090. if (state.inChat && _layout == Layout::Main) {
  3091. controller()->closeFolder();
  3092. }
  3093. // Adjust state to be consistent.
  3094. if (const auto peer = state.inChat.peer()) {
  3095. if (const auto to = peer->migrateTo()) {
  3096. state.inChat = peer->owner().history(to);
  3097. }
  3098. }
  3099. const auto peer = state.inChat.peer();
  3100. const auto topic = state.inChat.topic();
  3101. const auto forum = peer ? peer->forum() : nullptr;
  3102. if (state.inChat.folder() || (forum && !topic)) {
  3103. state.inChat = {};
  3104. }
  3105. if (!state.inChat && !forum && !_openedForum) {
  3106. state.fromPeer = nullptr;
  3107. }
  3108. if (state.tab == ChatSearchTab::PublicPosts
  3109. && IsHashOrCashtagSearchQuery(state.query) == HashOrCashtag::None) {
  3110. state.tab = (_openedForum && !state.inChat)
  3111. ? ChatSearchTab::ThisPeer
  3112. : ChatSearchTab::MyMessages;
  3113. } else if (!state.inChat
  3114. && _searchHashOrCashtag == HashOrCashtag::None) {
  3115. state.tab = (forum || _openedForum)
  3116. ? ChatSearchTab::ThisPeer
  3117. : ChatSearchTab::MyMessages;
  3118. }
  3119. if (!state.tags.empty()) {
  3120. state.inChat = session().data().history(session().user());
  3121. }
  3122. const auto clearQuery = state.fromPeer
  3123. && (_lastSearchText == HistoryView::SwitchToChooseFromQuery());
  3124. if (clearQuery) {
  3125. state.query = _lastSearchText = QString();
  3126. }
  3127. const auto inChatChanged = (_searchState.inChat != state.inChat);
  3128. const auto fromPeerChanged = (_searchState.fromPeer != state.fromPeer);
  3129. const auto tagsChanged = (_searchState.tags != state.tags);
  3130. const auto queryChanged = (_searchState.query != state.query);
  3131. const auto tabChanged = (_searchState.tab != state.tab);
  3132. const auto queryEmptyChanged = queryChanged
  3133. ? (_searchState.query.isEmpty() != state.query.isEmpty())
  3134. : false;
  3135. if (queryEmptyChanged || tabChanged) {
  3136. state.filter = ChatTypeFilter::All;
  3137. }
  3138. const auto filterChanged = (_searchState.filter != state.filter);
  3139. if (forum) {
  3140. if (_openedForum == forum) {
  3141. showSearchInTopBar(anim::type::normal);
  3142. } else if (_layout == Layout::Main) {
  3143. _forumSearchRequested = true;
  3144. controller()->showForum(forum);
  3145. } else {
  3146. return false;
  3147. }
  3148. } else if (peer && (_layout != Layout::Main)) {
  3149. return false;
  3150. }
  3151. if ((state.tab == ChatSearchTab::ThisTopic
  3152. && !state.inChat.topic())
  3153. || (state.tab == ChatSearchTab::ThisPeer
  3154. && !state.inChat
  3155. && !_openedForum)
  3156. || (state.tab == ChatSearchTab::PublicPosts
  3157. && _searchHashOrCashtag == HashOrCashtag::None)) {
  3158. state.tab = state.inChat.topic()
  3159. ? ChatSearchTab::ThisTopic
  3160. : (state.inChat.owningHistory() || state.inChat.sublist())
  3161. ? ChatSearchTab::ThisPeer
  3162. : ChatSearchTab::MyMessages;
  3163. }
  3164. const auto migrateFrom = (peer && !topic)
  3165. ? peer->migrateFrom()
  3166. : nullptr;
  3167. _searchInMigrated = migrateFrom
  3168. ? peer->owner().history(migrateFrom).get()
  3169. : nullptr;
  3170. _searchState = state;
  3171. if (_chatFilters && queryEmptyChanged) {
  3172. _chatFilters->setVisible(_searchState.query.isEmpty());
  3173. updateControlsGeometry();
  3174. }
  3175. _searchWithPostsPreview = computeSearchWithPostsPreview();
  3176. if (queryChanged) {
  3177. updateLockUnlockVisibility(anim::type::normal);
  3178. updateLoadMoreChatsVisibility();
  3179. }
  3180. if (inChatChanged) {
  3181. controller()->setSearchInChat(_searchState.inChat);
  3182. }
  3183. if (queryChanged || inChatChanged) {
  3184. updateCancelSearch();
  3185. updateStoriesVisibility();
  3186. }
  3187. updateJumpToDateVisibility();
  3188. updateSearchFromVisibility();
  3189. updateLockUnlockPosition();
  3190. const auto searchCleared = state.query.isEmpty()
  3191. && !state.fromPeer
  3192. && state.tags.empty();
  3193. if (searchCleared
  3194. || inChatChanged
  3195. || fromPeerChanged
  3196. || filterChanged
  3197. || tagsChanged
  3198. || tabChanged) {
  3199. clearSearchCache(searchCleared);
  3200. }
  3201. if (state.query.isEmpty()) {
  3202. _peerSearchCache.clear();
  3203. const auto queries = base::take(_peerSearchQueries);
  3204. for (const auto &[requestId, query] : queries) {
  3205. _api.request(requestId).cancel();
  3206. }
  3207. _peerSearchQuery = QString();
  3208. }
  3209. if (_searchState.query != currentSearchQuery()) {
  3210. setSearchQuery(_searchState.query);
  3211. }
  3212. _inner->applySearchState(_searchState);
  3213. if (!_postponeProcessSearchFocusChange) {
  3214. // Suggestions depend on _inner->state(), not on _searchState.
  3215. updateSuggestions(anim::type::instant);
  3216. }
  3217. _searchTagsLifetime = _inner->searchTagsChanges(
  3218. ) | rpl::start_with_next([=](std::vector<Data::ReactionId> &&list) {
  3219. auto copy = _searchState;
  3220. copy.tags = std::move(list);
  3221. applySearchState(std::move(copy));
  3222. });
  3223. if (_subsectionTopBar) {
  3224. _subsectionTopBar->searchEnableJumpToDate(
  3225. _openedForum && _searchState.inChat);
  3226. }
  3227. if (!_searchState.inChat && _searchState.query.isEmpty()) {
  3228. if (!_widthAnimationCache.isNull()) {
  3229. stopWidthAnimation();
  3230. }
  3231. setInnerFocus();
  3232. } else if (!_subsectionTopBar) {
  3233. _search->setFocus();
  3234. } else if (_openedForum && !_subsectionTopBar->searchSetFocus()) {
  3235. _subsectionTopBar->toggleSearch(true, anim::type::normal);
  3236. }
  3237. updateForceDisplayWide();
  3238. applySearchUpdate();
  3239. return true;
  3240. }
  3241. void Widget::clearSearchCache(bool clearPosts) {
  3242. _searchProcess.cache.clear();
  3243. _singleMessageSearch.clear();
  3244. const auto queries = base::take(_searchProcess.queries);
  3245. for (const auto &[requestId, query] : queries) {
  3246. session().api().request(requestId).cancel();
  3247. }
  3248. _searchQuery = QString();
  3249. _searchQueryFrom = nullptr;
  3250. _searchQueryTags.clear();
  3251. if (clearPosts) {
  3252. _postsProcess.cache.clear();
  3253. const auto queries = base::take(_postsProcess.queries);
  3254. for (const auto &[requestId, query] : queries) {
  3255. session().api().request(requestId).cancel();
  3256. }
  3257. }
  3258. _topicSearchQuery = QString();
  3259. _topicSearchOffsetDate = 0;
  3260. _topicSearchOffsetId = _topicSearchOffsetTopicId = 0;
  3261. _api.request(base::take(_peerSearchRequest)).cancel();
  3262. _api.request(base::take(_topicSearchRequest)).cancel();
  3263. cancelSearchRequest();
  3264. }
  3265. void Widget::showCalendar() {
  3266. if (_searchState.inChat) {
  3267. controller()->showCalendar(_searchState.inChat, QDate());
  3268. }
  3269. }
  3270. void Widget::showSearchFrom() {
  3271. if (const auto peer = searchInPeer()) {
  3272. const auto weak = base::make_weak(_searchState.inChat.topic());
  3273. const auto chat = (!_searchState.inChat && _openedForum)
  3274. ? Key(_openedForum->history())
  3275. : _searchState.inChat;
  3276. auto box = SearchFromBox(
  3277. peer,
  3278. crl::guard(this, [=](not_null<PeerData*> from) {
  3279. controller()->hideLayer();
  3280. auto copy = _searchState;
  3281. if (!chat.topic()) {
  3282. copy.inChat = chat;
  3283. copy.fromPeer = from;
  3284. applySearchState(std::move(copy));
  3285. } else if (const auto strong = weak.get()) {
  3286. copy.inChat = strong;
  3287. copy.fromPeer = from;
  3288. applySearchState(std::move(copy));
  3289. }
  3290. }),
  3291. crl::guard(this, [=] { _search->setFocus(); }));
  3292. if (box) {
  3293. controller()->show(std::move(box));
  3294. }
  3295. }
  3296. }
  3297. void Widget::searchCursorMoved() {
  3298. const auto to = _search->textCursor().position();
  3299. const auto text = _search->getLastText();
  3300. auto hashtag = QStringView();
  3301. for (int start = to; start > 0;) {
  3302. --start;
  3303. if (text.size() <= start) {
  3304. break;
  3305. }
  3306. const auto ch = text[start];
  3307. if (ch == '#') {
  3308. hashtag = base::StringViewMid(text, start, to - start);
  3309. break;
  3310. } else if (!ch.isLetterOrNumber() && ch != '_') {
  3311. break;
  3312. }
  3313. }
  3314. _inner->onHashtagFilterUpdate(hashtag);
  3315. }
  3316. void Widget::completeHashtag(QString tag) {
  3317. const auto t = _search->getLastText();
  3318. auto cur = _search->textCursor().position();
  3319. auto hashtag = QString();
  3320. for (int start = cur; start > 0;) {
  3321. --start;
  3322. if (t.size() <= start) {
  3323. break;
  3324. } else if (t.at(start) == '#') {
  3325. if (cur == start + 1
  3326. || base::StringViewMid(t, start + 1, cur - start - 1)
  3327. == base::StringViewMid(tag, 0, cur - start - 1)) {
  3328. while (cur < t.size() && cur - start - 1 < tag.size()) {
  3329. if (t.at(cur) != tag.at(cur - start - 1)) {
  3330. break;
  3331. }
  3332. ++cur;
  3333. }
  3334. if (cur - start - 1 == tag.size()
  3335. && cur < t.size()
  3336. && t.at(cur) == ' ') {
  3337. ++cur;
  3338. }
  3339. hashtag = t.mid(0, start + 1) + tag + ' ' + t.mid(cur);
  3340. setSearchQuery(hashtag, start + 1 + tag.size() + 1);
  3341. applySearchUpdate();
  3342. return;
  3343. }
  3344. break;
  3345. } else if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') {
  3346. break;
  3347. }
  3348. }
  3349. setSearchQuery(
  3350. t.mid(0, cur) + '#' + tag + ' ' + t.mid(cur),
  3351. cur + 1 + tag.size() + 1);
  3352. applySearchUpdate();
  3353. }
  3354. void Widget::resizeEvent(QResizeEvent *e) {
  3355. updateControlsGeometry();
  3356. }
  3357. void Widget::updateLockUnlockVisibility(anim::type animated) {
  3358. if (_showAnimation) {
  3359. return;
  3360. }
  3361. const auto hidden = !session().domain().local().hasLocalPasscode()
  3362. || _showAnimation
  3363. || _openedForum
  3364. || !_widthAnimationCache.isNull()
  3365. || _childList
  3366. || _searchHasFocus
  3367. || _searchSuggestionsLocked
  3368. || _searchState.inChat
  3369. || !_searchState.query.isEmpty();
  3370. if (_lockUnlock->toggled() == hidden) {
  3371. const auto stories = _stories && !_stories->empty();
  3372. _lockUnlock->toggle(
  3373. !hidden,
  3374. stories ? anim::type::instant : animated);
  3375. if (!hidden) {
  3376. updateLockUnlockPosition();
  3377. }
  3378. updateControlsGeometry();
  3379. }
  3380. }
  3381. void Widget::updateLoadMoreChatsVisibility() {
  3382. if (_showAnimation || !_loadMoreChats) {
  3383. return;
  3384. }
  3385. const auto hidden = (_openedFolder != nullptr)
  3386. || (_openedForum != nullptr)
  3387. || !_searchState.query.isEmpty();
  3388. if (_loadMoreChats->isHidden() != hidden) {
  3389. _loadMoreChats->setVisible(!hidden);
  3390. updateControlsGeometry();
  3391. }
  3392. }
  3393. void Widget::updateJumpToDateVisibility(bool fast) {
  3394. if (_showAnimation) {
  3395. return;
  3396. }
  3397. _jumpToDate->toggle(
  3398. (searchInPeer() && _searchState.query.isEmpty()),
  3399. fast ? anim::type::instant : anim::type::normal);
  3400. }
  3401. void Widget::updateSearchFromVisibility(bool fast) {
  3402. auto visible = [&] {
  3403. if (const auto peer = searchInPeer()) {
  3404. if (peer->isChat() || peer->isMegagroup()) {
  3405. return !_searchState.fromPeer;
  3406. }
  3407. }
  3408. return false;
  3409. }();
  3410. const auto changed = (visible == !_chooseFromUser->toggled());
  3411. _chooseFromUser->toggle(
  3412. visible,
  3413. fast ? anim::type::instant : anim::type::normal);
  3414. if (_subsectionTopBar) {
  3415. _subsectionTopBar->searchEnableChooseFromUser(true, visible);
  3416. } else if (changed) {
  3417. auto additional = QMargins();
  3418. if (visible) {
  3419. additional.setRight(_chooseFromUser->width());
  3420. }
  3421. _search->setAdditionalMargins(additional);
  3422. }
  3423. }
  3424. void Widget::updateControlsGeometry() {
  3425. if (width() < _narrowWidth) {
  3426. return;
  3427. }
  3428. auto filterAreaTop = 0;
  3429. const auto ratiow = anim::interpolate(
  3430. width(),
  3431. _narrowWidth,
  3432. _childListShown.current());
  3433. const auto smallw = st::columnMinimalWidthLeft - _narrowWidth;
  3434. const auto narrowRatio = (ratiow < smallw)
  3435. ? ((smallw - ratiow) / float64(smallw - _narrowWidth))
  3436. : 0.;
  3437. auto filterLeft = (controller()->filtersWidth()
  3438. ? st::dialogsFilterSkip
  3439. : (st::dialogsFilterPadding.x() + _mainMenu.toggle->width()))
  3440. + st::dialogsFilterPadding.x();
  3441. const auto filterRight = st::dialogsFilterSkip
  3442. + st::dialogsFilterPadding.x();
  3443. const auto filterWidth = qMax(ratiow, smallw) - filterLeft - filterRight;
  3444. const auto filterAreaHeight = st::topBarHeight;
  3445. _searchControls->setGeometry(0, filterAreaTop, ratiow, filterAreaHeight);
  3446. if (_subsectionTopBar) {
  3447. _subsectionTopBar->setGeometryWithNarrowRatio(
  3448. _searchControls->geometry(),
  3449. _narrowWidth,
  3450. narrowRatio);
  3451. }
  3452. auto filterTop = (filterAreaHeight - _search->height()) / 2;
  3453. filterLeft = anim::interpolate(filterLeft, _narrowWidth, narrowRatio);
  3454. _search->setGeometryToLeft(
  3455. filterLeft,
  3456. filterTop,
  3457. filterWidth,
  3458. _search->height());
  3459. auto mainMenuLeft = anim::interpolate(
  3460. st::dialogsFilterPadding.x(),
  3461. (_narrowWidth - _mainMenu.toggle->width()) / 2,
  3462. narrowRatio);
  3463. _mainMenu.toggle->moveToLeft(mainMenuLeft, st::dialogsFilterPadding.y());
  3464. _mainMenu.under->setGeometry(
  3465. 0,
  3466. 0,
  3467. filterLeft,
  3468. _mainMenu.toggle->y()
  3469. + _mainMenu.toggle->height()
  3470. + st::dialogsFilterPadding.y());
  3471. const auto searchLeft = anim::interpolate(
  3472. -_searchForNarrowLayout->width(),
  3473. (_narrowWidth - _searchForNarrowLayout->width()) / 2,
  3474. narrowRatio);
  3475. _searchForNarrowLayout->moveToLeft(
  3476. searchLeft,
  3477. st::dialogsFilterPadding.y());
  3478. auto right = filterLeft + filterWidth;
  3479. _cancelSearch->moveToLeft(right - _cancelSearch->width(), _search->y());
  3480. right -= _jumpToDate->width();
  3481. _jumpToDate->moveToLeft(right, _search->y());
  3482. right -= _chooseFromUser->width();
  3483. _chooseFromUser->moveToLeft(right, _search->y());
  3484. const auto barw = width();
  3485. const auto expandedStoriesTop = filterAreaTop + filterAreaHeight;
  3486. const auto storiesHeight = 2 * st::dialogsStories.photoTop
  3487. + st::dialogsStories.photo;
  3488. const auto added = (st::dialogsFilter.heightMin - storiesHeight) / 2;
  3489. if (_stories) {
  3490. _stories->setLayoutConstraints(
  3491. { filterLeft + filterWidth, filterTop + added },
  3492. style::al_right,
  3493. { 0, expandedStoriesTop, barw, st::dialogsStoriesFull.height });
  3494. }
  3495. if (_forumTopShadow) {
  3496. _forumTopShadow->setGeometry(
  3497. 0,
  3498. expandedStoriesTop,
  3499. barw,
  3500. st::lineWidth);
  3501. }
  3502. updateLockUnlockPosition();
  3503. auto bottomSkip = 0;
  3504. const auto putBottomButton = [&](auto &button) {
  3505. if (button && !button->isHidden()) {
  3506. const auto buttonHeight = button->height();
  3507. bottomSkip += buttonHeight;
  3508. button->setGeometry(
  3509. 0,
  3510. height() - bottomSkip,
  3511. barw,
  3512. buttonHeight);
  3513. }
  3514. };
  3515. putBottomButton(_updateTelegram);
  3516. putBottomButton(_downloadBar);
  3517. putBottomButton(_loadMoreChats);
  3518. if (_connecting) {
  3519. _connecting->setBottomSkip(bottomSkip);
  3520. }
  3521. if (_layout != Layout::Child) {
  3522. controller()->setConnectingBottomSkip(bottomSkip);
  3523. }
  3524. const auto wasScrollTop = _scroll->scrollTop();
  3525. const auto newScrollTop = (wasScrollTop == 0)
  3526. ? wasScrollTop
  3527. : (_topDelta < 0 && wasScrollTop <= 0)
  3528. ? wasScrollTop
  3529. : (wasScrollTop + _topDelta);
  3530. const auto scrollWidth = _childList ? _narrowWidth : barw;
  3531. if (_moreChatsBar) {
  3532. _moreChatsBar->resizeToWidth(barw);
  3533. }
  3534. if (_forumGroupCallBar) {
  3535. _forumGroupCallBar->resizeToWidth(barw);
  3536. }
  3537. if (_forumRequestsBar) {
  3538. _forumRequestsBar->resizeToWidth(barw);
  3539. }
  3540. if (_chatFilters) {
  3541. _chatFilters->resizeToWidth(barw);
  3542. }
  3543. _updateScrollGeometryCached = [=] {
  3544. const auto moreChatsBarTop = expandedStoriesTop
  3545. + ((!_stories || _stories->isHidden()) ? 0 : _aboveScrollAdded);
  3546. if (_moreChatsBar) {
  3547. _moreChatsBar->move(0, moreChatsBarTop);
  3548. }
  3549. const auto forumGroupCallTop = moreChatsBarTop
  3550. + (_moreChatsBar ? _moreChatsBar->height() : 0);
  3551. if (_forumGroupCallBar) {
  3552. _forumGroupCallBar->move(0, forumGroupCallTop);
  3553. }
  3554. const auto forumRequestsTop = forumGroupCallTop
  3555. + (_forumGroupCallBar ? _forumGroupCallBar->height() : 0);
  3556. if (_forumRequestsBar) {
  3557. _forumRequestsBar->move(0, forumRequestsTop);
  3558. }
  3559. const auto forumReportTop = forumRequestsTop
  3560. + (_forumRequestsBar ? _forumRequestsBar->height() : 0);
  3561. if (_forumReportBar) {
  3562. _forumReportBar->bar().move(0, forumReportTop);
  3563. }
  3564. const auto chatFiltersTop = forumReportTop
  3565. + (_forumReportBar ? _forumReportBar->bar().height() : 0);
  3566. if (_chatFilters) {
  3567. _chatFilters->move(0, chatFiltersTop);
  3568. }
  3569. const auto scrollTop = chatFiltersTop
  3570. + ((_chatFilters && _searchState.query.isEmpty())
  3571. ? (_chatFilters->height() * (1. - narrowRatio))
  3572. : 0);
  3573. const auto scrollHeight = height() - scrollTop - bottomSkip;
  3574. const auto wasScrollHeight = _scroll->height();
  3575. _scroll->setGeometry(0, scrollTop, scrollWidth, scrollHeight);
  3576. if (scrollHeight != wasScrollHeight) {
  3577. controller()->floatPlayerAreaUpdated();
  3578. }
  3579. };
  3580. _updateScrollGeometryCached();
  3581. if (_suggestions) {
  3582. _suggestions->setGeometry(
  3583. 0,
  3584. expandedStoriesTop,
  3585. scrollWidth,
  3586. height() - expandedStoriesTop - bottomSkip);
  3587. }
  3588. _inner->resize(scrollWidth, _inner->height());
  3589. _inner->setNarrowRatio(narrowRatio);
  3590. if (newScrollTop != wasScrollTop) {
  3591. _scroll->scrollToY(newScrollTop);
  3592. } else {
  3593. listScrollUpdated();
  3594. }
  3595. if (_scrollToTopIsShown) {
  3596. updateScrollUpPosition();
  3597. }
  3598. if (_childList) {
  3599. const auto childw = std::max(_narrowWidth, width() - scrollWidth);
  3600. const auto childh = _scroll->y() + _scroll->height();
  3601. const auto childx = width() - childw;
  3602. _childList->setGeometryWithTopMoved(
  3603. { childx, 0, childw, childh },
  3604. _topDelta);
  3605. const auto line = st::lineWidth;
  3606. _childListShadow->setGeometry(childx - line, 0, line, childh);
  3607. }
  3608. }
  3609. RowDescriptor Widget::resolveChatNext(RowDescriptor from) const {
  3610. return _inner->resolveChatNext(from);
  3611. }
  3612. RowDescriptor Widget::resolveChatPrevious(RowDescriptor from) const {
  3613. return _inner->resolveChatPrevious(from);
  3614. }
  3615. void Widget::keyPressEvent(QKeyEvent *e) {
  3616. if (e->key() == Qt::Key_Escape) {
  3617. escape();
  3618. //if (_openedForum) {
  3619. // controller()->closeForum();
  3620. //} else if (_openedFolder) {
  3621. // controller()->closeFolder();
  3622. //} else {
  3623. // e->ignore();
  3624. //}
  3625. } else if ((e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Tab)
  3626. && _searchHasFocus
  3627. && !_searchState.inChat
  3628. && _searchState.query.isEmpty()) {
  3629. escape();
  3630. } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
  3631. submit();
  3632. } else if (_suggestions
  3633. && (e->key() == Qt::Key_Down
  3634. || e->key() == Qt::Key_Up
  3635. || e->key() == Qt::Key_Left
  3636. || e->key() == Qt::Key_Right)) {
  3637. _suggestions->selectJump(Qt::Key(e->key()));
  3638. } else if (e->key() == Qt::Key_Down) {
  3639. _inner->selectSkip(1);
  3640. } else if (e->key() == Qt::Key_Up) {
  3641. _inner->selectSkip(-1);
  3642. } else if (e->key() == Qt::Key_PageDown) {
  3643. if (_suggestions) {
  3644. _suggestions->selectJump(Qt::Key_Down, _scroll->height());
  3645. } else {
  3646. _inner->selectSkipPage(_scroll->height(), 1);
  3647. }
  3648. } else if (e->key() == Qt::Key_PageUp) {
  3649. if (_suggestions) {
  3650. _suggestions->selectJump(Qt::Key_Up, _scroll->height());
  3651. } else {
  3652. _inner->selectSkipPage(_scroll->height(), -1);
  3653. }
  3654. } else if (redirectKeyToSearch(e)) {
  3655. // This delay in search focus processing allows us not to create
  3656. // _suggestions in case the event inserts some non-whitespace search
  3657. // query while still show _suggestions animated, if it is a space.
  3658. _postponeProcessSearchFocusChange = true;
  3659. _search->setFocusFast();
  3660. if (e->key() != Qt::Key_Space) {
  3661. QCoreApplication::sendEvent(_search->rawTextEdit(), e);
  3662. }
  3663. _postponeProcessSearchFocusChange = false;
  3664. processSearchFocusChange();
  3665. } else {
  3666. e->ignore();
  3667. }
  3668. }
  3669. void Widget::inputMethodEvent(QInputMethodEvent *e) {
  3670. const auto cursor = _search->rawTextEdit()->textCursor();
  3671. bool isGettingInput = !e->commitString().isEmpty()
  3672. || e->preeditString() != cursor.block().layout()->preeditAreaText()
  3673. || e->replacementLength() > 0;
  3674. if (!isGettingInput || _postponeProcessSearchFocusChange) {
  3675. Window::AbstractSectionWidget::inputMethodEvent(e);
  3676. return;
  3677. }
  3678. // This delay in search focus processing allows us not to create
  3679. // _suggestions in case the event inserts some non-whitespace search
  3680. // query while still show _suggestions animated, if it is a space.
  3681. _postponeProcessSearchFocusChange = true;
  3682. _search->setFocusFast();
  3683. QCoreApplication::sendEvent(_search->rawTextEdit(), e);
  3684. _postponeProcessSearchFocusChange = false;
  3685. processSearchFocusChange();
  3686. }
  3687. QVariant Widget::inputMethodQuery(Qt::InputMethodQuery query) const {
  3688. return _search->rawTextEdit()->inputMethodQuery(query);
  3689. }
  3690. bool Widget::redirectToSearchPossible() const {
  3691. return !_openedFolder
  3692. && !_openedForum
  3693. && !_childList
  3694. && _search->isVisible()
  3695. && !_search->hasFocus()
  3696. && hasFocus();
  3697. }
  3698. bool Widget::redirectKeyToSearch(QKeyEvent *e) const {
  3699. if (!redirectToSearchPossible()) {
  3700. return false;
  3701. }
  3702. const auto character = !(e->modifiers() & ~Qt::ShiftModifier)
  3703. && (e->key() != Qt::Key_Shift)
  3704. && RedirectTextToSearch(e->text());
  3705. if (character) {
  3706. return true;
  3707. } else if (e != QKeySequence::Paste) {
  3708. return false;
  3709. }
  3710. const auto useSelectionMode = (e->key() == Qt::Key_Insert)
  3711. && (e->modifiers() == (Qt::CTRL | Qt::SHIFT))
  3712. && QGuiApplication::clipboard()->supportsSelection();
  3713. const auto pasteMode = useSelectionMode
  3714. ? QClipboard::Selection
  3715. : QClipboard::Clipboard;
  3716. const auto data = QGuiApplication::clipboard()->mimeData(pasteMode);
  3717. return data && data->hasText();
  3718. }
  3719. bool Widget::redirectImeToSearch() const {
  3720. return redirectToSearchPossible();
  3721. }
  3722. void Widget::paintEvent(QPaintEvent *e) {
  3723. if (controller()->contentOverlapped(this, e)) {
  3724. return;
  3725. }
  3726. Painter p(this);
  3727. QRect r(e->rect());
  3728. if (r != rect()) {
  3729. p.setClipRect(r);
  3730. }
  3731. if (_showAnimation) {
  3732. _showAnimation->paintContents(p);
  3733. return;
  3734. }
  3735. const auto bg = anim::brush(
  3736. st::dialogsBg,
  3737. st::dialogsBgOver,
  3738. _childListShown.current());
  3739. auto above = QRect(0, 0, width(), _scroll->y());
  3740. if (above.intersects(r)) {
  3741. p.fillRect(above.intersected(r), bg);
  3742. }
  3743. auto belowTop = _scroll->y() + _scroll->height();
  3744. if (!_widthAnimationCache.isNull()) {
  3745. const auto suggestionsShown = _suggestions
  3746. ? _suggestions->shownOpacity()
  3747. : !_hidingSuggestions.empty()
  3748. ? _hidingSuggestions.back()->shownOpacity()
  3749. : 0.;
  3750. const auto suggestionsSkip = suggestionsShown
  3751. * (st::topPeers.height + st::searchedBarHeight);
  3752. const auto top = _searchControls->y()
  3753. + _searchControls->height()
  3754. + suggestionsSkip;
  3755. p.drawPixmapLeft(0, top, width(), _widthAnimationCache);
  3756. belowTop = top
  3757. + (_widthAnimationCache.height() / style::DevicePixelRatio());
  3758. }
  3759. auto below = QRect(0, belowTop, width(), height() - belowTop);
  3760. if (below.intersects(r)) {
  3761. p.fillRect(below.intersected(r), bg);
  3762. }
  3763. }
  3764. void Widget::scrollToEntry(const RowDescriptor &entry) {
  3765. _inner->scrollToEntry(entry);
  3766. }
  3767. void Widget::cancelSearchRequest() {
  3768. session().api().request(base::take(_searchProcess.requestId)).cancel();
  3769. session().api().request(base::take(_migratedProcess.requestId)).cancel();
  3770. session().api().request(base::take(_postsProcess.requestId)).cancel();
  3771. session().data().histories().cancelRequest(
  3772. base::take(_historiesRequest));
  3773. }
  3774. PeerData *Widget::searchInPeer() const {
  3775. return (_searchState.tab == ChatSearchTab::MyMessages
  3776. || _searchState.tab == ChatSearchTab::PublicPosts)
  3777. ? nullptr
  3778. : _openedForum
  3779. ? _openedForum->channel().get()
  3780. : _searchState.inChat.sublist()
  3781. ? session().user().get()
  3782. : _searchState.inChat.peer();
  3783. }
  3784. Data::ForumTopic *Widget::searchInTopic() const {
  3785. return (_searchState.tab != ChatSearchTab::ThisTopic)
  3786. ? nullptr
  3787. : _searchState.inChat.topic();
  3788. }
  3789. PeerData *Widget::searchFromPeer() const {
  3790. if (const auto peer = searchInPeer()) {
  3791. if (peer->isChat() || peer->isMegagroup()) {
  3792. return _searchState.fromPeer;
  3793. }
  3794. }
  3795. return nullptr;
  3796. }
  3797. const std::vector<Data::ReactionId> &Widget::searchInTags() const {
  3798. if (const auto peer = searchInPeer()) {
  3799. if (peer->isSelf() && _searchState.tab == ChatSearchTab::ThisPeer) {
  3800. return _searchState.tags;
  3801. }
  3802. }
  3803. static const auto kEmpty = std::vector<Data::ReactionId>();
  3804. return kEmpty;
  3805. }
  3806. QString Widget::currentSearchQuery() const {
  3807. return _subsectionTopBar
  3808. ? _subsectionTopBar->searchQueryCurrent()
  3809. : _search->getLastText();
  3810. }
  3811. int Widget::currentSearchQueryCursorPosition() const {
  3812. return _subsectionTopBar
  3813. ? _subsectionTopBar->searchQueryCursorPosition()
  3814. : _search->textCursor().position();
  3815. }
  3816. void Widget::clearSearchField() {
  3817. if (_subsectionTopBar) {
  3818. _subsectionTopBar->searchClear();
  3819. } else {
  3820. _search->clear();
  3821. }
  3822. }
  3823. void Widget::setSearchQuery(const QString &query, int cursorPosition) {
  3824. if (query.isEmpty()) {
  3825. clearSearchField();
  3826. return;
  3827. }
  3828. if (cursorPosition < 0) {
  3829. cursorPosition = query.size();
  3830. }
  3831. if (_subsectionTopBar) {
  3832. _subsectionTopBar->searchSetText(query, cursorPosition);
  3833. } else {
  3834. _search->setText(query);
  3835. _search->setCursorPosition(cursorPosition);
  3836. }
  3837. }
  3838. bool Widget::cancelSearch(CancelSearchOptions options) {
  3839. const auto clearingSuggestionsQuery = _suggestions
  3840. && _suggestions->consumeSearchQuery(QString());
  3841. if (clearingSuggestionsQuery) {
  3842. setSearchQuery(QString());
  3843. if (!options.forceFullCancel) {
  3844. return true;
  3845. }
  3846. }
  3847. cancelSearchRequest();
  3848. auto updatedState = _searchState;
  3849. const auto clearingQuery = clearingSuggestionsQuery
  3850. || !updatedState.query.isEmpty();
  3851. const auto forceFullCancel = options.forceFullCancel;
  3852. auto clearingInChat = (forceFullCancel || !clearingQuery)
  3853. && (updatedState.inChat
  3854. || updatedState.fromPeer
  3855. || !updatedState.tags.empty());
  3856. if (clearingQuery) {
  3857. updatedState.query = QString();
  3858. }
  3859. if (clearingInChat) {
  3860. if (options.jumpBackToSearchedChat
  3861. && updatedState.inChat
  3862. && controller()->adaptive().isOneColumn()) {
  3863. if (const auto thread = updatedState.inChat.thread()) {
  3864. controller()->showThread(thread);
  3865. } else {
  3866. Unexpected("Empty key in cancelSearch().");
  3867. }
  3868. }
  3869. updatedState.inChat = {};
  3870. updatedState.fromPeer = nullptr;
  3871. updatedState.tags = {};
  3872. }
  3873. if (!clearingQuery
  3874. && _subsectionTopBar
  3875. && _subsectionTopBar->toggleSearch(false, anim::type::normal)) {
  3876. setInnerFocus(true);
  3877. clearingInChat = true;
  3878. }
  3879. const auto clearSearchFocus = (forceFullCancel || !updatedState.inChat)
  3880. && (_searchHasFocus || _searchSuggestionsLocked);
  3881. if (!updatedState.inChat && _suggestions) {
  3882. _suggestions->clearPersistance();
  3883. _searchSuggestionsLocked = false;
  3884. }
  3885. if (!_suggestions && clearSearchFocus) {
  3886. // Don't create suggestions in unfocus case.
  3887. setInnerFocus(true);
  3888. }
  3889. _searchProcess.lastPeer = nullptr;
  3890. _searchProcess.lastId = 0;
  3891. _migratedProcess.lastPeer = nullptr;
  3892. _migratedProcess.lastId = 0;
  3893. _postsProcess.lastPeer = nullptr;
  3894. _postsProcess.lastId = 0;
  3895. _inner->clearFilter();
  3896. applySearchState(std::move(updatedState));
  3897. if (_suggestions && clearSearchFocus) {
  3898. const auto clearLockedFocus = !_searchHasFocus;
  3899. setInnerFocus(true);
  3900. if (clearLockedFocus) {
  3901. processSearchFocusChange();
  3902. }
  3903. }
  3904. updateForceDisplayWide();
  3905. return clearingQuery || clearingInChat || clearSearchFocus;
  3906. }
  3907. Widget::~Widget() {
  3908. cancelSearchRequest();
  3909. // Destructor may hide the bar and attempt to double-destroy it.
  3910. base::take(_downloadBar);
  3911. }
  3912. } // namespace Dialogs