| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "history/history_widget.h"
- #include "api/api_editing.h"
- #include "api/api_bot.h"
- #include "api/api_chat_participants.h"
- #include "api/api_report.h"
- #include "api/api_sending.h"
- #include "api/api_send_progress.h"
- #include "api/api_unread_things.h"
- #include "ui/boxes/confirm_box.h"
- #include "boxes/delete_messages_box.h"
- #include "boxes/send_credits_box.h"
- #include "boxes/send_files_box.h"
- #include "boxes/share_box.h"
- #include "boxes/edit_caption_box.h"
- #include "boxes/moderate_messages_box.h"
- #include "boxes/premium_limits_box.h"
- #include "boxes/premium_preview_box.h"
- #include "boxes/star_gift_box.h"
- #include "boxes/peers/edit_peer_permissions_box.h" // ShowAboutGigagroup.
- #include "boxes/peers/edit_peer_requests_box.h"
- #include "core/file_utilities.h"
- #include "core/mime_type.h"
- #include "ui/emoji_config.h"
- #include "ui/chat/attach/attach_prepare.h"
- #include "ui/chat/choose_theme_controller.h"
- #include "ui/widgets/menu/menu_add_action_callback_factory.h"
- #include "ui/widgets/buttons.h"
- #include "ui/widgets/inner_dropdown.h"
- #include "ui/widgets/dropdown_menu.h"
- #include "ui/widgets/labels.h"
- #include "ui/effects/ripple_animation.h"
- #include "ui/effects/message_sending_animation_controller.h"
- #include "ui/text/text_utilities.h" // Ui::Text::ToUpper
- #include "ui/text/format_values.h"
- #include "ui/chat/message_bar.h"
- #include "ui/chat/attach/attach_send_files_way.h"
- #include "ui/chat/choose_send_as.h"
- #include "ui/effects/spoiler_mess.h"
- #include "ui/image/image.h"
- #include "ui/painter.h"
- #include "ui/rect.h"
- #include "ui/power_saving.h"
- #include "ui/controls/emoji_button.h"
- #include "ui/controls/send_button.h"
- #include "ui/controls/send_as_button.h"
- #include "ui/controls/silent_toggle.h"
- #include "ui/ui_utility.h"
- #include "inline_bots/inline_bot_result.h"
- #include "base/event_filter.h"
- #include "base/qt_signal_producer.h"
- #include "base/qt/qt_key_modifiers.h"
- #include "base/unixtime.h"
- #include "base/call_delayed.h"
- #include "data/business/data_shortcut_messages.h"
- #include "data/components/credits.h"
- #include "data/components/scheduled_messages.h"
- #include "data/components/sponsored_messages.h"
- #include "data/notify/data_notify_settings.h"
- #include "data/data_changes.h"
- #include "data/data_drafts.h"
- #include "data/data_session.h"
- #include "data/data_web_page.h"
- #include "data/data_document.h"
- #include "data/data_photo.h"
- #include "data/data_photo_media.h"
- #include "data/data_channel.h"
- #include "data/data_chat.h"
- #include "data/data_forum.h"
- #include "data/data_forum_topic.h"
- #include "data/data_user.h"
- #include "data/data_chat_filters.h"
- #include "data/data_file_origin.h"
- #include "data/data_histories.h"
- #include "data/data_group_call.h"
- #include "data/data_message_reactions.h"
- #include "data/data_peer_values.h" // Data::AmPremiumValue.
- #include "data/data_premium_limits.h" // Data::PremiumLimits.
- #include "data/stickers/data_stickers.h"
- #include "data/stickers/data_custom_emoji.h"
- #include "history/history.h"
- #include "history/history_item.h"
- #include "history/history_item_helpers.h" // GetErrorForSending.
- #include "history/history_drag_area.h"
- #include "history/history_inner_widget.h"
- #include "history/history_item_components.h"
- #include "history/history_unread_things.h"
- #include "history/view/controls/history_view_characters_limit.h"
- #include "history/view/controls/history_view_compose_search.h"
- #include "history/view/controls/history_view_forward_panel.h"
- #include "history/view/controls/history_view_draft_options.h"
- #include "history/view/controls/history_view_voice_record_bar.h"
- #include "history/view/controls/history_view_ttl_button.h"
- #include "history/view/controls/history_view_webpage_processor.h"
- #include "history/view/reactions/history_view_reactions_button.h"
- #include "history/view/history_view_cursor_state.h"
- #include "history/view/history_view_service_message.h"
- #include "history/view/history_view_element.h"
- #include "history/view/history_view_scheduled_section.h"
- #include "history/view/history_view_schedule_box.h"
- #include "history/view/history_view_top_bar_widget.h"
- #include "history/view/history_view_contact_status.h"
- #include "history/view/history_view_paid_reaction_toast.h"
- #include "history/view/history_view_pinned_tracker.h"
- #include "history/view/history_view_pinned_section.h"
- #include "history/view/history_view_pinned_bar.h"
- #include "history/view/history_view_group_call_bar.h"
- #include "history/view/history_view_item_preview.h"
- #include "history/view/history_view_reply.h"
- #include "history/view/history_view_requests_bar.h"
- #include "history/view/history_view_sticker_toast.h"
- #include "history/view/history_view_translate_bar.h"
- #include "history/view/media/history_view_media.h"
- #include "profile/profile_block_group_members.h"
- #include "core/click_handler_types.h"
- #include "chat_helpers/field_autocomplete.h"
- #include "chat_helpers/tabbed_panel.h"
- #include "chat_helpers/tabbed_selector.h"
- #include "chat_helpers/tabbed_section.h"
- #include "chat_helpers/bot_keyboard.h"
- #include "chat_helpers/message_field.h"
- #include "menu/menu_send.h"
- #include "mtproto/mtproto_config.h"
- #include "lang/lang_keys.h"
- #include "settings/business/settings_quick_replies.h"
- #include "settings/settings_credits_graphics.h"
- #include "storage/localimageloader.h"
- #include "storage/storage_account.h"
- #include "storage/file_upload.h"
- #include "storage/storage_media_prepare.h"
- #include "media/audio/media_audio.h"
- #include "media/audio/media_audio_capture.h"
- #include "media/player/media_player_instance.h"
- #include "core/application.h"
- #include "apiwrap.h"
- #include "base/qthelp_regex.h"
- #include "ui/boxes/report_box_graphics.h"
- #include "ui/chat/pinned_bar.h"
- #include "ui/chat/group_call_bar.h"
- #include "ui/chat/requests_bar.h"
- #include "ui/chat/chat_theme.h"
- #include "ui/chat/chat_style.h"
- #include "ui/chat/continuous_scroll.h"
- #include "ui/widgets/checkbox.h"
- #include "ui/widgets/elastic_scroll.h"
- #include "ui/widgets/popup_menu.h"
- #include "ui/item_text_options.h"
- #include "main/main_session.h"
- #include "main/main_session_settings.h"
- #include "main/session/send_as_peers.h"
- #include "webrtc/webrtc_environment.h"
- #include "window/notifications_manager.h"
- #include "window/window_adaptive.h"
- #include "window/window_controller.h"
- #include "window/window_session_controller.h"
- #include "window/window_slide_animation.h"
- #include "window/window_peer_menu.h"
- #include "inline_bots/inline_results_widget.h"
- #include "inline_bots/bot_attach_web_view.h"
- #include "info/profile/info_profile_values.h" // SharedMediaCountValue.
- #include "chat_helpers/emoji_suggestions_widget.h"
- #include "core/shortcuts.h"
- #include "core/ui_integration.h"
- #include "support/support_common.h"
- #include "support/support_autocomplete.h"
- #include "support/support_helper.h"
- #include "support/support_preload.h"
- #include "dialogs/dialogs_key.h"
- #include "calls/calls_instance.h"
- #include "styles/style_chat.h"
- #include "styles/style_window.h"
- #include "styles/style_chat_helpers.h"
- #include "styles/style_info.h"
- #include <QtGui/QWindow>
- #include <QtCore/QMimeData>
- namespace {
- constexpr auto kMessagesPerPageFirst = 30;
- constexpr auto kMessagesPerPage = 50;
- constexpr auto kPreloadHeightsCount = 3; // when 3 screens to scroll left make a preload request
- constexpr auto kScrollToVoiceAfterScrolledMs = 1000;
- constexpr auto kSkipRepaintWhileScrollMs = 100;
- constexpr auto kShowMembersDropdownTimeoutMs = 300;
- constexpr auto kDisplayEditTimeWarningMs = 300 * 1000;
- constexpr auto kFullDayInMs = 86400 * 1000;
- constexpr auto kSaveDraftTimeout = crl::time(1000);
- constexpr auto kSaveDraftAnywayTimeout = 5 * crl::time(1000);
- constexpr auto kSaveCloudDraftIdleTimeout = 14 * crl::time(1000);
- constexpr auto kRefreshSlowmodeLabelTimeout = crl::time(200);
- constexpr auto kCommonModifiers = 0
- | Qt::ShiftModifier
- | Qt::MetaModifier
- | Qt::ControlModifier;
- const auto kPsaAboutPrefix = "cloud_lng_about_psa_";
- [[nodiscard]] rpl::producer<PeerData*> ActivePeerValue(
- not_null<Window::SessionController*> controller) {
- return controller->activeChatValue(
- ) | rpl::map([](Dialogs::Key key) {
- const auto history = key.history();
- return history ? history->peer.get() : nullptr;
- });
- }
- [[nodiscard]] QString FirstEmoji(const QString &s) {
- const auto begin = s.data();
- const auto end = begin + s.size();
- for (auto ch = begin; ch != end; ch++) {
- auto length = 0;
- if (const auto e = Ui::Emoji::Find(ch, end, &length)) {
- return e->text();
- }
- }
- return QString();
- }
- } // namespace
- HistoryWidget::HistoryWidget(
- QWidget *parent,
- not_null<Window::SessionController*> controller)
- : Window::AbstractSectionWidget(
- parent,
- controller,
- ActivePeerValue(controller))
- , _api(&controller->session().mtp())
- , _updateEditTimeLeftDisplay([=] { updateField(); })
- , _fieldBarCancel(this, st::historyReplyCancel)
- , _topBar(this, controller)
- , _scroll(
- this,
- controller->chatStyle()->value(lifetime(), st::historyScroll),
- false)
- , _updateHistoryItems([=] { updateHistoryItemsByTimer(); })
- , _cornerButtons(
- _scroll.data(),
- controller->chatStyle(),
- static_cast<HistoryView::CornerButtonsDelegate*>(this))
- , _supportAutocomplete(session().supportMode()
- ? object_ptr<Support::Autocomplete>(this, &session())
- : nullptr)
- , _send(std::make_shared<Ui::SendButton>(this, st::historySend))
- , _unblock(
- this,
- tr::lng_unblock_button(tr::now).toUpper(),
- st::historyUnblock)
- , _botStart(
- this,
- tr::lng_bot_start(tr::now).toUpper(),
- st::historyComposeButton)
- , _joinChannel(
- this,
- tr::lng_profile_join_channel(tr::now).toUpper(),
- st::historyComposeButton)
- , _muteUnmute(
- this,
- tr::lng_channel_mute(tr::now).toUpper(),
- st::historyComposeButton)
- , _reportMessages(this, QString(), st::historyComposeButton)
- , _attachToggle(this, st::historyAttach)
- , _tabbedSelectorToggle(this, st::historyAttachEmoji)
- , _botKeyboardShow(this, st::historyBotKeyboardShow)
- , _botKeyboardHide(this, st::historyBotKeyboardHide)
- , _botCommandStart(this, st::historyBotCommandStart)
- , _voiceRecordBar(std::make_unique<VoiceRecordBar>(
- this,
- controller->uiShow(),
- _send,
- st::historySendSize.height()))
- , _forwardPanel(std::make_unique<ForwardPanel>([=] { updateField(); }))
- , _field(
- this,
- st::historyComposeField,
- Ui::InputField::Mode::MultiLine,
- tr::lng_message_ph())
- , _kbScroll(this, st::botKbScroll)
- , _keyboard(_kbScroll->setOwnedWidget(object_ptr<BotKeyboard>(
- controller,
- this)))
- , _membersDropdownShowTimer([=] { showMembersDropdown(); })
- , _highlighter(
- &session().data(),
- [=](const HistoryItem *item) { return item->mainView(); },
- [=](const HistoryView::Element *view) {
- session().data().requestViewRepaint(view);
- })
- , _saveDraftTimer([=] { saveDraft(); })
- , _saveCloudDraftTimer([=] { saveCloudDraft(); })
- , _paidReactionToast(std::make_unique<HistoryView::PaidReactionToast>(
- this,
- &session().data(),
- rpl::single(st::topBarHeight),
- [=](not_null<const HistoryView::Element*> view) {
- return _list && _list->itemTop(view) >= 0;
- }))
- , _topShadow(this) {
- setAcceptDrops(true);
- session().downloaderTaskFinished() | rpl::start_with_next([=] {
- update();
- }, lifetime());
- base::install_event_filter(_scroll.data(), [=](not_null<QEvent*> e) {
- const auto consumed = (e->type() == QEvent::Wheel)
- && _list
- && _list->consumeScrollAction(
- Ui::ScrollDelta(static_cast<QWheelEvent*>(e.get())));
- return consumed
- ? base::EventFilterResult::Cancel
- : base::EventFilterResult::Continue;
- });
- _scroll->scrolls() | rpl::start_with_next([=] {
- handleScroll();
- }, lifetime());
- _scroll->geometryChanged(
- ) | rpl::start_with_next(crl::guard(_list, [=] {
- _list->onParentGeometryChanged();
- }), lifetime());
- _scroll->addContentRequests(
- ) | rpl::start_with_next([=] {
- if (_history && _history->loadedAtBottom()) {
- using Result = Data::SponsoredMessages::AppendResult;
- const auto tryToAppend = [=] {
- const auto sponsored = &session().sponsoredMessages();
- const auto result = sponsored->append(_history);
- if (result == Result::Appended) {
- _scroll->contentAdded();
- }
- return result;
- };
- if (tryToAppend() == Result::MediaLoading
- && !_historySponsoredPreloading) {
- session().downloaderTaskFinished(
- ) | rpl::start_with_next([=] {
- if (tryToAppend() != Result::MediaLoading) {
- _historySponsoredPreloading.destroy();
- }
- }, _historySponsoredPreloading);
- }
- }
- }, lifetime());
- _fieldBarCancel->addClickHandler([=] { cancelFieldAreaState(); });
- _send->addClickHandler([=] { sendButtonClicked(); });
- _mediaEditManager.updateRequests() | rpl::start_with_next([this] {
- updateOverStates(mapFromGlobal(QCursor::pos()));
- }, lifetime());
- {
- using namespace SendMenu;
- const auto sendAction = [=](Action action, Details) {
- if (action.type == ActionType::CaptionUp
- || action.type == ActionType::CaptionDown
- || action.type == ActionType::SpoilerOn
- || action.type == ActionType::SpoilerOff) {
- _mediaEditManager.apply(action);
- } else if (action.type == ActionType::Send) {
- send(action.options);
- } else {
- sendScheduled(action.options);
- }
- };
- SetupMenuAndShortcuts(
- _send.get(),
- controller->uiShow(),
- [=] { return sendButtonMenuDetails(); },
- sendAction);
- }
- _unblock->addClickHandler([=] { unblockUser(); });
- _botStart->addClickHandler([=] { sendBotStartCommand(); });
- _joinChannel->addClickHandler([=] { joinChannel(); });
- _muteUnmute->addClickHandler([=] { toggleMuteUnmute(); });
- setupGiftToChannelButton();
- _reportMessages->addClickHandler([=] { reportSelectedMessages(); });
- _field->submits(
- ) | rpl::start_with_next([=](Qt::KeyboardModifiers modifiers) {
- sendWithModifiers(modifiers);
- }, _field->lifetime());
- _field->cancelled(
- ) | rpl::start_with_next([=] {
- escape();
- }, _field->lifetime());
- _field->tabbed(
- ) | rpl::start_with_next([=] {
- fieldTabbed();
- }, _field->lifetime());
- _field->heightChanges(
- ) | rpl::start_with_next([=] {
- fieldResized();
- }, _field->lifetime());
- _field->focusedChanges(
- ) | rpl::filter(rpl::mappers::_1) | rpl::start_with_next([=] {
- fieldFocused();
- }, _field->lifetime());
- _field->changes(
- ) | rpl::start_with_next([=] {
- fieldChanged();
- }, _field->lifetime());
- #ifdef Q_OS_MAC
- // Removed an ability to insert text from the menu bar
- // when the field is hidden.
- _field->shownValue(
- ) | rpl::start_with_next([=](bool shown) {
- _field->setEnabled(shown);
- }, _field->lifetime());
- #endif // Q_OS_MAC
- controller->widget()->shownValue(
- ) | rpl::skip(1) | rpl::start_with_next([=] {
- windowIsVisibleChanged();
- }, lifetime());
- initTabbedSelector();
- _attachToggle->setClickedCallback([=] {
- const auto toggle = _attachBotsMenu && _attachBotsMenu->isHidden();
- base::call_delayed(st::historyAttach.ripple.hideDuration, this, [=] {
- if (_attachBotsMenu && toggle) {
- _attachBotsMenu->showAnimated();
- } else {
- chooseAttach();
- if (_attachBotsMenu) {
- _attachBotsMenu->hideAnimated();
- }
- }
- });
- });
- const auto rawTextEdit = _field->rawTextEdit().get();
- rpl::merge(
- _field->scrollTop().changes() | rpl::to_empty,
- base::qt_signal_producer(
- rawTextEdit,
- &QTextEdit::cursorPositionChanged)
- ) | rpl::start_with_next([=] {
- saveDraftDelayed();
- }, _field->lifetime());
- _fieldBarCancel->hide();
- _topBar->hide();
- _scroll->hide();
- _kbScroll->hide();
- controller->chatStyle()->paletteChanged(
- ) | rpl::start_with_next([=] {
- _scroll->updateBars();
- }, lifetime());
- _forwardPanel->itemsUpdated(
- ) | rpl::start_with_next([=] {
- updateControlsVisibility();
- updateControlsGeometry();
- }, lifetime());
- InitMessageField(controller, _field, [=](
- not_null<DocumentData*> document) {
- if (_peer && Data::AllowEmojiWithoutPremium(_peer, document)) {
- return true;
- }
- showPremiumToast(document);
- return false;
- });
- InitMessageFieldFade(_field, st::historyComposeField.textBg);
- setupFastButtonMode();
- _fieldCharsCountManager.limitExceeds(
- ) | rpl::start_with_next([=] {
- const auto hide = _fieldCharsCountManager.isLimitExceeded();
- if (_silent) {
- _silent->setVisible(!hide);
- }
- if (_ttlInfo) {
- _ttlInfo->setVisible(!hide);
- }
- if (_scheduled) {
- _scheduled->setVisible(!hide);
- }
- updateFieldSize();
- moveFieldControls();
- }, lifetime());
- _send->widthValue() | rpl::skip(1) | rpl::start_with_next([=] {
- updateFieldSize();
- moveFieldControls();
- }, _send->lifetime());
- _keyboard->sendCommandRequests(
- ) | rpl::start_with_next([=](Bot::SendCommandRequest r) {
- sendBotCommand(r);
- }, lifetime());
- if (_supportAutocomplete) {
- supportInitAutocomplete();
- }
- _field->rawTextEdit()->installEventFilter(this);
- _field->setMimeDataHook([=](
- not_null<const QMimeData*> data,
- Ui::InputField::MimeAction action) {
- if (action == Ui::InputField::MimeAction::Check) {
- return canSendFiles(data);
- } else if (action == Ui::InputField::MimeAction::Insert) {
- return confirmSendingFiles(
- data,
- std::nullopt,
- Core::ReadMimeText(data));
- }
- Unexpected("action in MimeData hook.");
- });
- updateFieldSubmitSettings();
- _field->hide();
- _send->hide();
- _unblock->hide();
- _botStart->hide();
- _joinChannel->hide();
- _muteUnmute->hide();
- _reportMessages->hide();
- initVoiceRecordBar();
- _attachToggle->hide();
- _tabbedSelectorToggle->hide();
- _botKeyboardShow->hide();
- _botKeyboardHide->hide();
- _botCommandStart->hide();
- session().attachWebView().requestBots();
- rpl::merge(
- session().attachWebView().attachBotsUpdates(),
- session().changes().peerUpdates(
- Data::PeerUpdate::Flag::Rights
- | Data::PeerUpdate::Flag::StarsPerMessage
- ) | rpl::filter([=](const Data::PeerUpdate &update) {
- return update.peer == _peer;
- }) | rpl::to_empty
- ) | rpl::start_with_next([=] {
- refreshAttachBotsMenu();
- }, lifetime());
- _botKeyboardShow->addClickHandler([=] { toggleKeyboard(); });
- _botKeyboardHide->addClickHandler([=] { toggleKeyboard(); });
- _botCommandStart->addClickHandler([=] { startBotCommand(); });
- _topShadow->hide();
- _attachDragAreas = DragArea::SetupDragAreaToContainer(
- this,
- crl::guard(this, [=](not_null<const QMimeData*> d) {
- if (!_peer || isRecording()) {
- return false;
- }
- const auto topic = resolveReplyToTopic();
- return topic
- ? Data::CanSendAnyOf(topic, Data::FilesSendRestrictions())
- : Data::CanSendAnyOf(_peer, Data::FilesSendRestrictions());
- }),
- crl::guard(this, [=](bool f) { _field->setAcceptDrops(f); }),
- crl::guard(this, [=] { updateControlsGeometry(); }));
- _attachDragAreas.document->setDroppedCallback([=](const QMimeData *data) {
- confirmSendingFiles(data, false);
- Window::ActivateWindow(controller);
- });
- _attachDragAreas.photo->setDroppedCallback([=](const QMimeData *data) {
- confirmSendingFiles(data, true);
- Window::ActivateWindow(controller);
- });
- Core::App().mediaDevices().recordAvailabilityValue(
- ) | rpl::start_with_next([=](Webrtc::RecordAvailability value) {
- _recordAvailability = value;
- if (_list) {
- updateSendButtonType();
- }
- }, lifetime());
- session().data().newItemAdded(
- ) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
- newItemAdded(item);
- }, lifetime());
- session().data().historyChanged(
- ) | rpl::start_with_next([=](not_null<History*> history) {
- handleHistoryChange(history);
- }, lifetime());
- session().data().viewResizeRequest(
- ) | rpl::start_with_next([=](not_null<HistoryView::Element*> view) {
- const auto item = view->data();
- const auto history = item->history();
- if (item->mainView() == view
- && (history == _history || history == _migrated)) {
- updateHistoryGeometry();
- }
- }, lifetime());
- session().data().itemDataChanges(
- ) | rpl::filter([=](not_null<HistoryItem*> item) {
- return !_list && (item->mainView() != nullptr);
- }) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
- item->mainView()->itemDataChanged();
- }, lifetime());
- Core::App().settings().largeEmojiChanges(
- ) | rpl::start_with_next([=] {
- crl::on_main(this, [=] {
- updateHistoryGeometry();
- });
- }, lifetime());
- Core::App().settings().sendSubmitWayValue(
- ) | rpl::start_with_next([=] {
- crl::on_main(this, [=] {
- updateFieldSubmitSettings();
- });
- }, lifetime());
- session().data().channelDifferenceTooLong(
- ) | rpl::filter([=](not_null<ChannelData*> channel) {
- return _peer == channel.get();
- }) | rpl::start_with_next([=] {
- _cornerButtons.updateJumpDownVisibility();
- preloadHistoryIfNeeded();
- }, lifetime());
- session().data().userIsBotChanges(
- ) | rpl::filter([=](not_null<UserData*> user) {
- return (_peer == user.get());
- }) | rpl::start_with_next([=](not_null<UserData*> user) {
- _list->refreshAboutView();
- _list->updateBotInfo();
- updateControlsVisibility();
- updateControlsGeometry();
- }, lifetime());
- session().data().botCommandsChanges(
- ) | rpl::filter([=](not_null<PeerData*> peer) {
- return _peer && (_peer == peer);
- }) | rpl::start_with_next([=] {
- if (updateCmdStartShown()) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- }, lifetime());
- using EntryUpdateFlag = Data::EntryUpdate::Flag;
- session().changes().entryUpdates(
- EntryUpdateFlag::HasPinnedMessages
- | EntryUpdateFlag::ForwardDraft
- ) | rpl::start_with_next([=](const Data::EntryUpdate &update) {
- if (_pinnedTracker
- && (update.flags & EntryUpdateFlag::HasPinnedMessages)
- && ((update.entry.get() == _history)
- || (update.entry.get() == _migrated))) {
- checkPinnedBarState();
- }
- if (update.flags & EntryUpdateFlag::ForwardDraft) {
- updateForwarding();
- }
- }, lifetime());
- using HistoryUpdateFlag = Data::HistoryUpdate::Flag;
- session().changes().historyUpdates(
- HistoryUpdateFlag::MessageSent
- | HistoryUpdateFlag::BotKeyboard
- | HistoryUpdateFlag::CloudDraft
- | HistoryUpdateFlag::UnreadMentions
- | HistoryUpdateFlag::UnreadReactions
- | HistoryUpdateFlag::UnreadView
- | HistoryUpdateFlag::TopPromoted
- | HistoryUpdateFlag::ClientSideMessages
- ) | rpl::filter([=](const Data::HistoryUpdate &update) {
- return (_history == update.history.get());
- }) | rpl::start_with_next([=](const Data::HistoryUpdate &update) {
- const auto flags = update.flags;
- if (flags & HistoryUpdateFlag::MessageSent) {
- synteticScrollToY(_scroll->scrollTopMax());
- }
- if (flags & HistoryUpdateFlag::BotKeyboard) {
- updateBotKeyboard(update.history);
- }
- if (flags & HistoryUpdateFlag::CloudDraft) {
- applyCloudDraft(update.history);
- }
- if (flags & HistoryUpdateFlag::ClientSideMessages) {
- updateSendButtonType();
- }
- if ((flags & HistoryUpdateFlag::UnreadMentions)
- || (flags & HistoryUpdateFlag::UnreadReactions)) {
- _cornerButtons.updateUnreadThingsVisibility();
- }
- if (flags & HistoryUpdateFlag::UnreadView) {
- unreadCountUpdated();
- }
- if (flags & HistoryUpdateFlag::TopPromoted) {
- updateHistoryGeometry();
- updateControlsVisibility();
- updateControlsGeometry();
- this->update();
- }
- }, lifetime());
- using MessageUpdateFlag = Data::MessageUpdate::Flag;
- session().changes().messageUpdates(
- MessageUpdateFlag::Destroyed
- | MessageUpdateFlag::Edited
- | MessageUpdateFlag::ReplyMarkup
- | MessageUpdateFlag::BotCallbackSent
- ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
- const auto flags = update.flags;
- if (flags & MessageUpdateFlag::Destroyed) {
- itemRemoved(update.item);
- return;
- }
- if (flags & MessageUpdateFlag::Edited) {
- itemEdited(update.item);
- }
- if (flags & MessageUpdateFlag::ReplyMarkup) {
- if (_keyboard->forMsgId() == update.item->fullId()) {
- updateBotKeyboard(update.item->history(), true);
- }
- }
- if (flags & MessageUpdateFlag::BotCallbackSent) {
- botCallbackSent(update.item);
- }
- }, lifetime());
- session().changes().realtimeMessageUpdates(
- MessageUpdateFlag::NewUnreadReaction
- ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
- maybeMarkReactionsRead(update.item);
- }, lifetime());
- session().data().sentToScheduled(
- ) | rpl::start_with_next([=](const Data::SentToScheduled &value) {
- const auto history = value.history;
- if (history == _history) {
- const auto id = value.scheduledId;
- crl::on_main(this, [=] {
- if (history == _history) {
- controller->showSection(
- std::make_shared<HistoryView::ScheduledMemento>(
- history,
- id));
- }
- });
- return;
- }
- }, lifetime());
- session().data().sentFromScheduled(
- ) | rpl::start_with_next([=](const Data::SentFromScheduled &value) {
- if (value.item->awaitingVideoProcessing()
- && !_sentFromScheduledTip
- && HistoryView::ShowScheduledVideoPublished(
- controller,
- value,
- crl::guard(this, [=] { _sentFromScheduledTip = false; }))) {
- _sentFromScheduledTip = true;
- }
- }, lifetime());
- using MediaSwitch = Media::Player::Instance::Switch;
- Media::Player::instance()->switchToNextEvents(
- ) | rpl::filter([=](const MediaSwitch &pair) {
- return (pair.from.type() == AudioMsgId::Type::Voice);
- }) | rpl::start_with_next([=](const MediaSwitch &pair) {
- scrollToCurrentVoiceMessage(pair.from.contextId(), pair.to);
- }, lifetime());
- session().user()->flagsValue(
- ) | rpl::start_with_next([=](UserData::Flags::Change change) {
- if (change.diff & UserData::Flag::Premium) {
- if (const auto user = _peer ? _peer->asUser() : nullptr) {
- if (user->requiresPremiumToWrite()) {
- handlePeerUpdate();
- }
- }
- }
- }, lifetime());
- using PeerUpdateFlag = Data::PeerUpdate::Flag;
- session().changes().peerUpdates(
- PeerUpdateFlag::Rights
- | PeerUpdateFlag::Migration
- | PeerUpdateFlag::UnavailableReason
- | PeerUpdateFlag::IsBlocked
- | PeerUpdateFlag::Admins
- | PeerUpdateFlag::Members
- | PeerUpdateFlag::OnlineStatus
- | PeerUpdateFlag::Notifications
- | PeerUpdateFlag::ChannelAmIn
- | PeerUpdateFlag::ChannelLinkedChat
- | PeerUpdateFlag::Slowmode
- | PeerUpdateFlag::BotStartToken
- | PeerUpdateFlag::MessagesTTL
- | PeerUpdateFlag::ChatThemeEmoji
- | PeerUpdateFlag::FullInfo
- | PeerUpdateFlag::StarsPerMessage
- ) | rpl::filter([=](const Data::PeerUpdate &update) {
- return (update.peer.get() == _peer);
- }) | rpl::map([](const Data::PeerUpdate &update) {
- return update.flags;
- }) | rpl::start_with_next([=](Data::PeerUpdate::Flags flags) {
- if (flags & PeerUpdateFlag::Rights) {
- updateFieldPlaceholder();
- updateSendButtonType();
- _preview->checkNow(false);
- const auto was = (_sendAs != nullptr);
- refreshSendAsToggle();
- if (was != (_sendAs != nullptr)) {
- updateControlsVisibility();
- updateControlsGeometry();
- orderWidgets();
- }
- }
- if (flags & PeerUpdateFlag::Migration) {
- handlePeerMigration();
- }
- if (flags & PeerUpdateFlag::Notifications) {
- updateNotifyControls();
- }
- if (flags & PeerUpdateFlag::UnavailableReason) {
- const auto unavailable = _peer->computeUnavailableReason();
- if (!unavailable.isEmpty()) {
- const auto account = not_null(&_peer->account());
- closeCurrent();
- if (const auto primary = Core::App().windowFor(account)) {
- primary->showToast(unavailable);
- }
- return;
- }
- }
- if (flags & PeerUpdateFlag::StarsPerMessage) {
- updateFieldPlaceholder();
- updateSendButtonType();
- }
- if (flags & PeerUpdateFlag::BotStartToken) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- if (flags & PeerUpdateFlag::Slowmode) {
- updateSendButtonType();
- }
- if (flags & (PeerUpdateFlag::IsBlocked
- | PeerUpdateFlag::Admins
- | PeerUpdateFlag::Members
- | PeerUpdateFlag::OnlineStatus
- | PeerUpdateFlag::Rights
- | PeerUpdateFlag::ChannelAmIn
- | PeerUpdateFlag::ChannelLinkedChat)) {
- handlePeerUpdate();
- }
- if (flags & PeerUpdateFlag::MessagesTTL) {
- checkMessagesTTL();
- }
- if ((flags & PeerUpdateFlag::ChatThemeEmoji) && _list) {
- const auto emoji = _peer->themeEmoji();
- if (Data::CloudThemes::TestingColors() && !emoji.isEmpty()) {
- _peer->owner().cloudThemes().themeForEmojiValue(
- emoji
- ) | rpl::filter_optional(
- ) | rpl::take(
- 1
- ) | rpl::start_with_next([=](const Data::CloudTheme &theme) {
- const auto &themes = _peer->owner().cloudThemes();
- const auto text = themes.prepareTestingLink(theme);
- if (!text.isEmpty()) {
- _field->setText(text);
- }
- }, _list->lifetime());
- }
- }
- if (flags & PeerUpdateFlag::FullInfo) {
- fullInfoUpdated();
- if (_peer->starsPerMessageChecked()) {
- session().credits().load();
- } else if (const auto channel = _peer->asChannel()) {
- if (channel->allowedReactions().paidEnabled) {
- session().credits().load();
- }
- }
- }
- }, lifetime());
- using Type = Data::DefaultNotify;
- rpl::merge(
- session().data().notifySettings().defaultUpdates(Type::User),
- session().data().notifySettings().defaultUpdates(Type::Group),
- session().data().notifySettings().defaultUpdates(Type::Broadcast)
- ) | rpl::start_with_next([=] {
- updateNotifyControls();
- }, lifetime());
- session().data().itemVisibilityQueries(
- ) | rpl::filter([=](
- const Data::Session::ItemVisibilityQuery &query) {
- return !_showAnimation
- && (_history == query.item->history())
- && (query.item->mainView() != nullptr)
- && isVisible();
- }) | rpl::start_with_next([=](
- const Data::Session::ItemVisibilityQuery &query) {
- if (const auto view = query.item->mainView()) {
- auto top = _list->itemTop(view);
- if (top >= 0) {
- auto scrollTop = _scroll->scrollTop();
- if (top + view->height() > scrollTop
- && top < scrollTop + _scroll->height()) {
- *query.isVisible = true;
- }
- }
- }
- }, lifetime());
- _topBar->membersShowAreaActive(
- ) | rpl::start_with_next([=](bool active) {
- setMembersShowAreaActive(active);
- }, _topBar->lifetime());
- _topBar->forwardSelectionRequest(
- ) | rpl::start_with_next([=] {
- forwardSelected();
- }, _topBar->lifetime());
- _topBar->deleteSelectionRequest(
- ) | rpl::start_with_next([=] {
- confirmDeleteSelected();
- }, _topBar->lifetime());
- _topBar->clearSelectionRequest(
- ) | rpl::start_with_next([=] {
- clearSelected();
- }, _topBar->lifetime());
- _topBar->cancelChooseForReportRequest(
- ) | rpl::start_with_next([=] {
- setChooseReportMessagesDetails({}, nullptr);
- }, _topBar->lifetime());
- _topBar->searchRequest(
- ) | rpl::start_with_next([=] {
- if (_history) {
- controller->searchInChat(_history);
- }
- }, _topBar->lifetime());
- session().api().sendActions(
- ) | rpl::filter([=](const Api::SendAction &action) {
- return (action.history == _history);
- }) | rpl::start_with_next([=](const Api::SendAction &action) {
- const auto lastKeyboardUsed = lastForceReplyReplied(
- action.replyTo.messageId);
- if (action.replaceMediaOf) {
- } else if (action.options.scheduled) {
- cancelReply(lastKeyboardUsed);
- crl::on_main(this, [=, history = action.history] {
- controller->showSection(
- std::make_shared<HistoryView::ScheduledMemento>(history));
- });
- } else {
- fastShowAtEnd(action.history);
- if (!_justMarkingAsRead
- && cancelReply(lastKeyboardUsed)
- && !action.clearDraft) {
- saveCloudDraft();
- }
- }
- if (action.options.handleSupportSwitch) {
- handleSupportSwitch(action.history);
- }
- }, lifetime());
- if (session().supportMode()) {
- session().data().chatListEntryRefreshes(
- ) | rpl::start_with_next([=] {
- crl::on_main(this, [=] { checkSupportPreload(true); });
- }, lifetime());
- }
- Core::App().materializeLocalDraftsRequests(
- ) | rpl::start_with_next([=] {
- saveFieldToHistoryLocalDraft();
- }, lifetime());
- setupScheduledToggle();
- setupSendAsToggle();
- orderWidgets();
- setupShortcuts();
- }
- void HistoryWidget::setGeometryWithTopMoved(
- const QRect &newGeometry,
- int topDelta) {
- _topDelta = topDelta;
- bool willBeResized = (size() != newGeometry.size());
- if (geometry() != newGeometry) {
- auto weak = Ui::MakeWeak(this);
- setGeometry(newGeometry);
- if (!weak) {
- return;
- }
- }
- if (!willBeResized) {
- resizeEvent(nullptr);
- }
- _topDelta = 0;
- }
- Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const {
- return Dialogs::EntryState{
- .key = _history,
- .section = Dialogs::EntryState::Section::History,
- .currentReplyTo = replyTo(),
- };
- }
- void HistoryWidget::refreshJoinChannelText() {
- if (const auto channel = _peer ? _peer->asChannel() : nullptr) {
- _joinChannel->setText((channel->isBroadcast()
- ? tr::lng_profile_join_channel(tr::now)
- : (channel->requestToJoin() && !channel->amCreator())
- ? tr::lng_profile_apply_to_join_group(tr::now)
- : tr::lng_profile_join_group(tr::now)).toUpper());
- }
- }
- void HistoryWidget::refreshGiftToChannelShown() {
- if (!_giftToChannelIn || !_giftToChannelOut) {
- return;
- }
- const auto channel = _peer->asChannel();
- const auto shown = channel
- && channel->isBroadcast()
- && channel->stargiftsAvailable();
- _giftToChannelIn->setVisible(shown);
- _giftToChannelOut->setVisible(shown);
- }
- void HistoryWidget::refreshTopBarActiveChat() {
- const auto state = computeDialogsEntryState();
- _topBar->setActiveChat(state, _history->sendActionPainter());
- if (state.key) {
- controller()->setDialogsEntryState(state);
- }
- }
- void HistoryWidget::refreshTabbedPanel() {
- if (_peer && controller()->hasTabbedSelectorOwnership()) {
- createTabbedPanel();
- } else {
- setTabbedPanel(nullptr);
- }
- }
- void HistoryWidget::initVoiceRecordBar() {
- _voiceRecordBar->setStartRecordingFilter([=] {
- const auto error = [&]() -> Data::SendError {
- if (_peer) {
- if (const auto error = Data::RestrictionError(
- _peer,
- ChatRestriction::SendVoiceMessages)) {
- return error;
- }
- }
- return {};
- }();
- if (error) {
- Data::ShowSendErrorToast(controller(), _peer, error);
- return true;
- } else if (showSlowmodeError()) {
- return true;
- }
- return false;
- });
- _voiceRecordBar->setTTLFilter([=] {
- if (const auto peer = _history ? _history->peer.get() : nullptr) {
- if (const auto user = peer->asUser()) {
- if (!user->isSelf() && !user->isBot()) {
- return true;
- }
- }
- }
- return false;
- });
- const auto applyLocalDraft = [=] {
- if (_history && _history->localDraft({})) {
- applyDraft();
- }
- };
- _voiceRecordBar->sendActionUpdates(
- ) | rpl::start_with_next([=](const auto &data) {
- if (!_history) {
- return;
- }
- session().sendProgressManager().update(
- _history,
- data.type,
- data.progress);
- }, lifetime());
- _voiceRecordBar->sendVoiceRequests(
- ) | rpl::start_with_next([=](const VoiceToSend &data) {
- sendVoice(data);
- }, lifetime());
- _voiceRecordBar->cancelRequests(
- ) | rpl::start_with_next(applyLocalDraft, lifetime());
- _voiceRecordBar->lockShowStarts(
- ) | rpl::start_with_next([=] {
- _cornerButtons.updateJumpDownVisibility();
- _cornerButtons.updateUnreadThingsVisibility();
- }, lifetime());
- _voiceRecordBar->errors(
- ) | rpl::start_with_next([=](::Media::Capture::Error error) {
- using Error = ::Media::Capture::Error;
- switch (error) {
- case Error::AudioInit:
- case Error::AudioTimeout:
- controller()->showToast(tr::lng_record_audio_problem(tr::now));
- break;
- case Error::VideoInit:
- case Error::VideoTimeout:
- controller()->showToast(tr::lng_record_video_problem(tr::now));
- break;
- default:
- controller()->showToast(u"Unknown error."_q);
- break;
- }
- }, lifetime());
- _voiceRecordBar->updateSendButtonTypeRequests(
- ) | rpl::start_with_next([=] {
- updateSendButtonType();
- }, lifetime());
- _voiceRecordBar->lockViewportEvents(
- ) | rpl::start_with_next([=](not_null<QEvent*> e) {
- _scroll->viewportEvent(e);
- }, lifetime());
- _voiceRecordBar->recordingTipRequests(
- ) | rpl::start_with_next([=] {
- Core::App().settings().setRecordVideoMessages(
- !Core::App().settings().recordVideoMessages());
- updateSendButtonType();
- switch (_send->type()) {
- case Ui::SendButton::Type::Record: {
- const auto can = Webrtc::RecordAvailability::VideoAndAudio;
- controller()->showToast((_recordAvailability == can)
- ? tr::lng_record_voice_tip(tr::now)
- : tr::lng_record_hold_tip(tr::now));
- } break;
- case Ui::SendButton::Type::Round:
- controller()->showToast(tr::lng_record_video_tip(tr::now));
- break;
- }
- }, lifetime());
- _voiceRecordBar->recordingStateChanges(
- ) | rpl::start_with_next([=](bool active) {
- controller()->widget()->setInnerFocus();
- }, lifetime());
- _voiceRecordBar->hideFast();
- }
- void HistoryWidget::initTabbedSelector() {
- refreshTabbedPanel();
- _tabbedSelectorToggle->addClickHandler([=] {
- if (_tabbedPanel && _tabbedPanel->isHidden()) {
- _tabbedPanel->showAnimated();
- } else {
- toggleTabbedSelectorMode();
- }
- });
- const auto selector = controller()->tabbedSelector();
- base::install_event_filter(this, selector, [=](not_null<QEvent*> e) {
- if (_tabbedPanel && e->type() == QEvent::ParentChange) {
- setTabbedPanel(nullptr);
- }
- return base::EventFilterResult::Continue;
- });
- auto filter = rpl::filter([=] {
- return !isHidden();
- });
- using Selector = TabbedSelector;
- selector->emojiChosen(
- ) | rpl::filter([=] {
- return !isHidden() && !_field->isHidden();
- }) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
- Ui::InsertEmojiAtCursor(_field->textCursor(), data.emoji);
- }, lifetime());
- rpl::merge(
- selector->fileChosen() | filter,
- selector->customEmojiChosen() | filter,
- controller()->stickerOrEmojiChosen() | filter
- ) | rpl::start_with_next([=](ChatHelpers::FileChosen &&data) {
- fileChosen(std::move(data));
- }, lifetime());
- selector->photoChosen(
- ) | filter | rpl::start_with_next([=](ChatHelpers::PhotoChosen data) {
- sendExistingPhoto(data.photo, data.options);
- }, lifetime());
- selector->inlineResultChosen(
- ) | filter | rpl::filter([=](const ChatHelpers::InlineChosen &data) {
- if (!data.recipientOverride) {
- return true;
- } else if (data.recipientOverride != _peer) {
- showHistory(data.recipientOverride->id, ShowAtTheEndMsgId);
- }
- return (data.recipientOverride == _peer);
- }) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) {
- sendInlineResult(data);
- }, lifetime());
- selector->contextMenuRequested(
- ) | filter | rpl::start_with_next([=] {
- selector->showMenuWithDetails(sendMenuDetails());
- }, lifetime());
- selector->choosingStickerUpdated(
- ) | rpl::start_with_next([=](const Selector::Action &data) {
- if (!_history) {
- return;
- }
- const auto type = Api::SendProgressType::ChooseSticker;
- if (data != Selector::Action::Cancel) {
- session().sendProgressManager().update(_history, type);
- } else {
- session().sendProgressManager().cancel(_history, type);
- }
- }, lifetime());
- }
- void HistoryWidget::supportInitAutocomplete() {
- _supportAutocomplete->hide();
- _supportAutocomplete->insertRequests(
- ) | rpl::start_with_next([=](const QString &text) {
- supportInsertText(text);
- }, _supportAutocomplete->lifetime());
- _supportAutocomplete->shareContactRequests(
- ) | rpl::start_with_next([=](const Support::Contact &contact) {
- supportShareContact(contact);
- }, _supportAutocomplete->lifetime());
- }
- void HistoryWidget::supportInsertText(const QString &text) {
- _field->setFocus();
- _field->textCursor().insertText(text);
- _field->ensureCursorVisible();
- }
- void HistoryWidget::supportShareContact(Support::Contact contact) {
- if (!_history) {
- return;
- }
- supportInsertText(contact.comment);
- contact.comment = _field->getLastText();
- const auto submit = [=](Qt::KeyboardModifiers modifiers) {
- const auto history = _history;
- if (!history) {
- return;
- }
- auto options = Api::SendOptions{
- .sendAs = prepareSendAction({}).options.sendAs,
- };
- auto action = Api::SendAction(history);
- send(options);
- options.handleSupportSwitch = Support::HandleSwitch(modifiers);
- action.options = options;
- session().api().shareContact(
- contact.phone,
- contact.firstName,
- contact.lastName,
- action);
- };
- const auto box = controller()->show(Box<Support::ConfirmContactBox>(
- controller(),
- _history,
- contact,
- crl::guard(this, submit)));
- box->boxClosing(
- ) | rpl::start_with_next([=] {
- _field->document()->undo();
- }, lifetime());
- }
- void HistoryWidget::scrollToCurrentVoiceMessage(
- FullMsgId fromId,
- FullMsgId toId) {
- if (crl::now() <= _lastUserScrolled + kScrollToVoiceAfterScrolledMs) {
- return;
- }
- if (!_list) {
- return;
- }
- auto from = session().data().message(fromId);
- auto to = session().data().message(toId);
- if (!from || !to) {
- return;
- }
- // If history has pending resize items, the scrollTopItem won't be updated.
- // And the scrollTop will be reset back to scrollTopItem + scrollTopOffset.
- handlePendingHistoryUpdate();
- if (const auto toView = to->mainView()) {
- auto toTop = _list->itemTop(toView);
- if (toTop >= 0 && !isItemCompletelyHidden(from)) {
- auto scrollTop = _scroll->scrollTop();
- auto scrollBottom = scrollTop + _scroll->height();
- auto toBottom = toTop + toView->height();
- if ((toTop < scrollTop && toBottom < scrollBottom)
- || (toTop > scrollTop && toBottom > scrollBottom)) {
- animatedScrollToItem(to->id);
- }
- }
- }
- }
- void HistoryWidget::animatedScrollToItem(MsgId msgId) {
- Expects(_history != nullptr);
- if (hasPendingResizedItems()) {
- updateListSize();
- }
- auto to = session().data().message(_history->peer, msgId);
- if (_list->itemTop(to) < 0) {
- return;
- }
- auto scrollTo = std::clamp(
- itemTopForHighlight(to->mainView()),
- 0,
- _scroll->scrollTopMax());
- animatedScrollToY(scrollTo, to);
- }
- void HistoryWidget::animatedScrollToY(int scrollTo, HistoryItem *attachTo) {
- Expects(_history != nullptr);
- if (hasPendingResizedItems()) {
- updateListSize();
- }
- // Attach our scroll animation to some item.
- auto itemTop = _list->itemTop(attachTo);
- auto scrollTop = _scroll->scrollTop();
- if (itemTop < 0 && !_history->isEmpty()) {
- attachTo = _history->blocks.back()->messages.back()->data();
- itemTop = _list->itemTop(attachTo);
- }
- if (itemTop < 0 || (scrollTop == scrollTo)) {
- synteticScrollToY(scrollTo);
- return;
- }
- _scrollToAnimation.stop();
- auto maxAnimatedDelta = _scroll->height();
- auto transition = anim::sineInOut;
- if (scrollTo > scrollTop + maxAnimatedDelta) {
- scrollTop = scrollTo - maxAnimatedDelta;
- synteticScrollToY(scrollTop);
- transition = anim::easeOutCubic;
- } else if (scrollTo + maxAnimatedDelta < scrollTop) {
- scrollTop = scrollTo + maxAnimatedDelta;
- synteticScrollToY(scrollTop);
- transition = anim::easeOutCubic;
- } else {
- // In local showHistory() we forget current scroll state,
- // so we need to restore it synchronously, otherwise we may
- // jump to the bottom of history in some updateHistoryGeometry() call.
- synteticScrollToY(scrollTop);
- }
- const auto itemId = attachTo->fullId();
- const auto relativeFrom = scrollTop - itemTop;
- const auto relativeTo = scrollTo - itemTop;
- _scrollToAnimation.start(
- [=] { scrollToAnimationCallback(itemId, relativeTo); },
- relativeFrom,
- relativeTo,
- st::slideDuration,
- anim::sineInOut);
- }
- void HistoryWidget::scrollToAnimationCallback(
- FullMsgId attachToId,
- int relativeTo) {
- auto itemTop = _list->itemTop(session().data().message(attachToId));
- if (itemTop < 0) {
- _scrollToAnimation.stop();
- } else {
- synteticScrollToY(qRound(_scrollToAnimation.value(relativeTo))
- + itemTop);
- }
- if (!_scrollToAnimation.animating()) {
- preloadHistoryByScroll();
- checkReplyReturns();
- }
- }
- void HistoryWidget::enqueueMessageHighlight(
- const HistoryView::SelectedQuote "e) {
- _highlighter.enqueue(quote);
- }
- Ui::ChatPaintHighlight HistoryWidget::itemHighlight(
- not_null<const HistoryItem*> item) const {
- return _highlighter.state(item);
- }
- int HistoryWidget::itemTopForHighlight(
- not_null<HistoryView::Element*> view) const {
- if (const auto group = session().data().groups().find(view->data())) {
- if (const auto leader = group->items.front()->mainView()) {
- view = leader;
- }
- }
- const auto itemTop = _list->itemTop(view);
- Assert(itemTop >= 0);
- const auto item = view->data();
- const auto unwatchedEffect = item->hasUnwatchedEffect();
- const auto showReactions = item->hasUnreadReaction() || unwatchedEffect;
- const auto reactionCenter = showReactions
- ? view->reactionButtonParameters({}, {}).center.y()
- : -1;
- const auto visibleAreaHeight = _scroll->height();
- const auto viewHeight = view->height();
- const auto heightLeft = (visibleAreaHeight - viewHeight);
- if (heightLeft >= 0) {
- return std::max(itemTop - (heightLeft / 2), 0);
- } else if (const auto sel = itemHighlight(item).range
- ; !sel.empty() && !IsSubGroupSelection(sel)) {
- const auto single = st::messageTextStyle.font->height;
- const auto begin = HistoryView::FindViewY(view, sel.from) - single;
- const auto end = HistoryView::FindViewY(view, sel.to, begin + single)
- + 2 * single;
- auto result = itemTop;
- if (end > visibleAreaHeight) {
- result = std::max(result, itemTop + end - visibleAreaHeight);
- }
- if (itemTop + begin < result) {
- result = itemTop + begin;
- }
- return result;
- } else if (reactionCenter >= 0) {
- const auto maxSize = st::reactionInlineImage;
- // Show message right till the bottom.
- const auto forBottom = itemTop + viewHeight - visibleAreaHeight;
- // Show message bottom and some space below for the effect.
- const auto bottomResult = forBottom + maxSize;
- // Show the reaction button center in the middle.
- const auto byReactionResult = itemTop
- + reactionCenter
- - visibleAreaHeight / 2;
- // Show the reaction center and some space above it for the effect.
- const auto maxAllowed = itemTop + reactionCenter - 2 * maxSize;
- return std::max(
- std::min(maxAllowed, std::max(bottomResult, byReactionResult)),
- 0);
- }
- return itemTop;
- }
- void HistoryWidget::initFieldAutocomplete() {
- _emojiSuggestions = nullptr;
- _autocomplete = nullptr;
- if (!_peer) {
- return;
- }
- const auto processShortcut = [=](QString shortcut) {
- if (!_peer) {
- return;
- }
- const auto messages = &_peer->owner().shortcutMessages();
- const auto shortcutId = messages->lookupShortcutId(shortcut);
- if (shortcut.isEmpty()) {
- controller()->showSettings(Settings::QuickRepliesId());
- } else if (!_peer->session().premium()) {
- ShowPremiumPreviewToBuy(
- controller(),
- PremiumFeature::QuickReplies);
- } else if (shortcutId) {
- session().api().sendShortcutMessages(_peer, shortcutId);
- session().api().finishForwarding(prepareSendAction({}));
- setFieldText(_field->getTextWithTagsPart(
- _field->textCursor().position()));
- }
- };
- ChatHelpers::InitFieldAutocomplete(_autocomplete, {
- .parent = this,
- .show = controller()->uiShow(),
- .field = _field.data(),
- .peer = _peer,
- .features = [=] {
- auto result = ChatHelpers::ComposeFeatures();
- if (_showAnimation
- || isChoosingTheme()
- || (_inlineBot && !_inlineLookingUpBot)) {
- result.autocompleteMentions = false;
- result.autocompleteHashtags = false;
- result.autocompleteCommands = false;
- }
- if (_editMsgId) {
- result.autocompleteCommands = false;
- result.suggestStickersByEmoji = false;
- }
- return result;
- },
- .sendMenuDetails = [=] { return sendMenuDetails(); },
- .stickerChoosing = [=] {
- if (_history) {
- session().sendProgressManager().update(
- _history,
- Api::SendProgressType::ChooseSticker);
- }
- },
- .stickerChosen = [=](ChatHelpers::FileChosen &&data) {
- fileChosen(std::move(data));
- },
- .setText = [=](TextWithTags text) { if (_peer) setFieldText(text); },
- .sendBotCommand = [=](QString command) {
- if (_peer) {
- sendBotCommand({ _peer, command, FullMsgId(), replyTo() });
- session().api().finishForwarding(prepareSendAction({}));
- }
- },
- .processShortcut = processShortcut,
- .moderateKeyActivateCallback = [=](int key) {
- const auto context = [=](FullMsgId itemId) {
- return _list->prepareClickContext(Qt::LeftButton, itemId);
- };
- return !_keyboard->isHidden() && _keyboard->moderateKeyActivate(
- key,
- context);
- },
- });
- const auto allow = [=](const auto&) {
- return _peer->isSelf();
- };
- _emojiSuggestions.reset(Ui::Emoji::SuggestionsController::Init(
- this,
- _field,
- &controller()->session(),
- { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }));
- }
- InlineBotQuery HistoryWidget::parseInlineBotQuery() const {
- return (isChoosingTheme() || _editMsgId)
- ? InlineBotQuery()
- : ParseInlineBotQuery(&session(), _field);
- }
- void HistoryWidget::updateInlineBotQuery() {
- if (!_history) {
- return;
- }
- const auto query = parseInlineBotQuery();
- if (_inlineBotUsername != query.username) {
- _inlineBotUsername = query.username;
- if (_inlineBotResolveRequestId) {
- _api.request(_inlineBotResolveRequestId).cancel();
- _inlineBotResolveRequestId = 0;
- }
- if (query.lookingUpBot) {
- _inlineBot = nullptr;
- _inlineLookingUpBot = true;
- const auto username = _inlineBotUsername;
- _inlineBotResolveRequestId = _api.request(
- MTPcontacts_ResolveUsername(
- MTP_flags(0),
- MTP_string(username),
- MTP_string())
- ).done([=](const MTPcontacts_ResolvedPeer &result) {
- const auto &data = result.data();
- const auto resolvedBot = [&]() -> UserData* {
- if (const auto user = session().data().processUsers(
- data.vusers())) {
- if (user->isBot()
- && !user->botInfo->inlinePlaceholder.isEmpty()) {
- return user;
- }
- }
- return nullptr;
- }();
- session().data().processChats(data.vchats());
- _inlineBotResolveRequestId = 0;
- const auto query = parseInlineBotQuery();
- if (_inlineBotUsername == query.username) {
- applyInlineBotQuery(
- query.lookingUpBot ? resolvedBot : query.bot,
- query.query);
- } else {
- clearInlineBot();
- }
- }).fail([=](const MTP::Error &error) {
- _inlineBotResolveRequestId = 0;
- if (username == _inlineBotUsername) {
- clearInlineBot();
- }
- }).send();
- } else {
- applyInlineBotQuery(query.bot, query.query);
- }
- } else if (query.lookingUpBot) {
- if (!_inlineLookingUpBot) {
- applyInlineBotQuery(_inlineBot, query.query);
- }
- } else {
- applyInlineBotQuery(query.bot, query.query);
- }
- }
- void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
- if (bot) {
- if (_inlineBot != bot) {
- _inlineBot = bot;
- _inlineLookingUpBot = false;
- inlineBotChanged();
- }
- if (!_inlineResults) {
- _inlineResults.create(this, controller());
- _inlineResults->setResultSelectedCallback([=](
- InlineBots::ResultSelected result) {
- if (result.open) {
- const auto request = result.result->openRequest();
- if (const auto photo = request.photo()) {
- controller()->openPhoto(photo, {});
- } else if (const auto document = request.document()) {
- controller()->openDocument(document, false, {});
- }
- } else {
- sendInlineResult(result);
- }
- });
- _inlineResults->setSendMenuDetails([=] {
- return sendMenuDetails();
- });
- _inlineResults->requesting(
- ) | rpl::start_with_next([=](bool requesting) {
- _tabbedSelectorToggle->setLoading(requesting);
- }, _inlineResults->lifetime());
- updateControlsGeometry();
- orderWidgets();
- }
- _inlineResults->queryInlineBot(_inlineBot, _peer, query);
- if (_autocomplete) {
- _autocomplete->hideAnimated();
- }
- } else {
- clearInlineBot();
- }
- }
- void HistoryWidget::orderWidgets() {
- _voiceRecordBar->raise();
- _send->raise();
- if (_businessBotStatus) {
- _businessBotStatus->bar().raise();
- }
- if (_contactStatus) {
- _contactStatus->bar().raise();
- }
- if (_paysStatus) {
- _paysStatus->bar().raise();
- }
- if (_translateBar) {
- _translateBar->raise();
- }
- if (_sponsoredMessageBar) {
- _sponsoredMessageBar->raise();
- }
- if (_pinnedBar) {
- _pinnedBar->raise();
- }
- if (_requestsBar) {
- _requestsBar->raise();
- }
- if (_groupCallBar) {
- _groupCallBar->raise();
- }
- if (_chooseTheme) {
- _chooseTheme->raise();
- }
- _topShadow->raise();
- if (_autocomplete) {
- _autocomplete->raise();
- }
- if (_membersDropdown) {
- _membersDropdown->raise();
- }
- if (_inlineResults) {
- _inlineResults->raise();
- }
- if (_tabbedPanel) {
- _tabbedPanel->raise();
- }
- if (_emojiSuggestions) {
- _emojiSuggestions->raise();
- }
- _attachDragAreas.document->raise();
- _attachDragAreas.photo->raise();
- }
- void HistoryWidget::toggleChooseChatTheme(
- not_null<PeerData*> peer,
- std::optional<bool> show) {
- const auto update = [=] {
- updateInlineBotQuery();
- updateControlsGeometry();
- updateControlsVisibility();
- };
- if (peer.get() != _peer) {
- return;
- } else if (_chooseTheme) {
- if (isChoosingTheme() && !show.value_or(false)) {
- const auto was = base::take(_chooseTheme);
- if (Ui::InFocusChain(this)) {
- setInnerFocus();
- }
- update();
- }
- return;
- } else if (!show.value_or(true)) {
- return;
- } else if (_voiceRecordBar->isActive()) {
- controller()->showToast(tr::lng_chat_theme_cant_voice(tr::now));
- return;
- }
- _chooseTheme = std::make_unique<Ui::ChooseThemeController>(
- this,
- controller(),
- peer);
- _chooseTheme->shouldBeShownValue(
- ) | rpl::start_with_next(update, _chooseTheme->lifetime());
- }
- Ui::ChatTheme *HistoryWidget::customChatTheme() const {
- return _list ? _list->theme().get() : nullptr;
- }
- void HistoryWidget::fieldChanged() {
- const auto updateTyping = (_textUpdateEvents
- & TextUpdateEvent::SendTyping);
- InvokeQueued(this, [=] {
- updateInlineBotQuery();
- if (_history
- && !_inlineBot
- && !_editMsgId
- && (!_autocomplete || !_autocomplete->stickersEmoji())
- && updateTyping) {
- session().sendProgressManager().update(
- _history,
- Api::SendProgressType::Typing);
- }
- });
- checkCharsCount();
- updateSendButtonType();
- if (!HasSendText(_field)) {
- _fieldIsEmpty = true;
- } else if (_fieldIsEmpty) {
- _fieldIsEmpty = false;
- if (_kbShown) {
- toggleKeyboard();
- }
- }
- if (updateCmdStartShown()) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- _saveCloudDraftTimer.cancel();
- if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
- return;
- }
- _saveDraftText = true;
- saveDraft(true);
- }
- void HistoryWidget::saveDraftDelayed() {
- if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
- return;
- }
- if (!_field->textCursor().position()
- && !_field->textCursor().anchor()
- && !_field->scrollTop().current()) {
- if (!session().local().hasDraftCursors(_peer->id)) {
- return;
- }
- }
- saveDraft(true);
- }
- void HistoryWidget::saveDraft(bool delayed) {
- if (!_peer) {
- return;
- } else if (delayed) {
- auto ms = crl::now();
- if (!_saveDraftStart) {
- _saveDraftStart = ms;
- return _saveDraftTimer.callOnce(kSaveDraftTimeout);
- } else if (ms - _saveDraftStart < kSaveDraftAnywayTimeout) {
- return _saveDraftTimer.callOnce(kSaveDraftTimeout);
- }
- }
- writeDrafts();
- }
- void HistoryWidget::saveFieldToHistoryLocalDraft() {
- if (!_history) {
- return;
- }
- const auto topicRootId = MsgId();
- if (_editMsgId) {
- _history->setLocalEditDraft(std::make_unique<Data::Draft>(
- _field,
- FullReplyTo{
- .messageId = FullMsgId(_history->peer->id, _editMsgId),
- .topicRootId = topicRootId,
- },
- _preview->draft(),
- _saveEditMsgRequestId));
- } else {
- if (_replyTo || !_field->empty()) {
- _history->setLocalDraft(std::make_unique<Data::Draft>(
- _field,
- _replyTo,
- _preview->draft()));
- } else {
- _history->clearLocalDraft(topicRootId);
- }
- _history->clearLocalEditDraft(topicRootId);
- }
- }
- void HistoryWidget::fileChosen(ChatHelpers::FileChosen &&data) {
- controller()->hideLayer(anim::type::normal);
- if (const auto info = data.document->sticker()
- ; info && info->setType == Data::StickersType::Emoji) {
- if (data.document->isPremiumEmoji()
- && !session().premium()
- && (!_peer
- || !Data::AllowEmojiWithoutPremium(
- _peer,
- data.document))) {
- showPremiumToast(data.document);
- } else if (!_field->isHidden()) {
- Data::InsertCustomEmoji(_field.data(), data.document);
- }
- } else if (_history) {
- controller()->sendingAnimation().appendSending(
- data.messageSendingFrom);
- const auto localId = data.messageSendingFrom.localId;
- auto messageToSend = Api::MessageToSend(
- prepareSendAction(data.options));
- messageToSend.textWithTags = base::take(data.caption);
- sendExistingDocument(
- data.document,
- std::move(messageToSend),
- localId);
- }
- }
- void HistoryWidget::saveCloudDraft() {
- controller()->session().api().saveCurrentDraftToCloud();
- }
- void HistoryWidget::writeDraftTexts() {
- Expects(_history != nullptr);
- session().local().writeDrafts(_history);
- if (_migrated) {
- _migrated->clearDrafts();
- session().local().writeDrafts(_migrated);
- }
- }
- void HistoryWidget::writeDraftCursors() {
- Expects(_history != nullptr);
- session().local().writeDraftCursors(_history);
- if (_migrated) {
- _migrated->clearDrafts();
- session().local().writeDraftCursors(_migrated);
- }
- }
- void HistoryWidget::writeDrafts() {
- const auto save = (_history != nullptr) && (_saveDraftStart > 0);
- _saveDraftStart = 0;
- _saveDraftTimer.cancel();
- if (save) {
- if (_saveDraftText) {
- writeDraftTexts();
- }
- writeDraftCursors();
- }
- _saveDraftText = false;
- if (!_editMsgId && !_inlineBot) {
- _saveCloudDraftTimer.callOnce(kSaveCloudDraftIdleTimeout);
- }
- }
- bool HistoryWidget::isRecording() const {
- return _voiceRecordBar->isRecording();
- }
- void HistoryWidget::activate() {
- if (_history) {
- if (!_historyInited) {
- updateHistoryGeometry(true);
- } else if (hasPendingResizedItems()) {
- updateHistoryGeometry();
- }
- }
- controller()->widget()->setInnerFocus();
- }
- void HistoryWidget::setInnerFocus() {
- if (_list) {
- if (isSearching() && !_nonEmptySelection) {
- _composeSearch->setInnerFocus();
- } else if (isChoosingTheme()) {
- _chooseTheme->setFocus();
- } else if (_showAnimation
- || _nonEmptySelection
- || (_list && _list->wasSelectedText())
- || isRecording()
- || isJoinChannel()
- || isBotStart()
- || isBlocked()
- || (!_canSendTexts && !_editMsgId)) {
- if (_scroll->isHidden()) {
- setFocus();
- } else {
- _list->setFocus();
- }
- } else {
- _field->setFocus();
- }
- } else if (_scroll->isHidden()) {
- setFocus();
- }
- }
- bool HistoryWidget::notify_switchInlineBotButtonReceived(
- const QString &query,
- UserData *samePeerBot,
- MsgId samePeerReplyTo) {
- if (samePeerBot) {
- const auto to = controller()->dialogsEntryStateCurrent();
- if (!to.key.owningHistory()) {
- return false;
- }
- controller()->switchInlineQuery(to, samePeerBot, query);
- return true;
- } else if (const auto bot = _peer ? _peer->asUser() : nullptr) {
- const auto to = bot->isBot()
- ? bot->botInfo->inlineReturnTo
- : Dialogs::EntryState();
- if (!to.key.owningHistory()) {
- return false;
- }
- bot->botInfo->inlineReturnTo = Dialogs::EntryState();
- controller()->switchInlineQuery(to, bot, query);
- return true;
- }
- return false;
- }
- void HistoryWidget::tryProcessKeyInput(not_null<QKeyEvent*> e) {
- e->accept();
- keyPressEvent(e);
- if (!e->isAccepted()
- && _canSendTexts
- && _field->isVisible()
- && !e->text().isEmpty()) {
- _field->setFocusFast();
- QCoreApplication::sendEvent(_field->rawTextEdit(), e);
- }
- }
- void HistoryWidget::setupShortcuts() {
- Shortcuts::Requests(
- ) | rpl::filter([=] {
- return _history
- && Ui::AppInFocus()
- && Ui::InFocusChain(this)
- && !controller()->isLayerShown()
- && window()->isActiveWindow();
- }) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
- using Command = Shortcuts::Command;
- request->check(Command::Search, 1) && request->handle([=] {
- controller()->searchInChat(_history);
- return true;
- });
- request->check(Command::ShowChatMenu, 1) && request->handle([=] {
- Window::ActivateWindow(controller());
- _topBar->showPeerMenu();
- return true;
- });
- _canSendMessages
- && request->check(Command::ShowScheduled, 1)
- && request->handle([=] {
- using Scheduled = HistoryView::ScheduledMemento;
- controller()->showSection(
- std::make_shared<Scheduled>(_history));
- return true;
- });
- if (session().supportMode()) {
- request->check(
- Command::SupportToggleMuted
- ) && request->handle([=] {
- toggleMuteUnmute();
- return true;
- });
- }
- }, lifetime());
- }
- void HistoryWidget::setupGiftToChannelButton() {
- const auto setupButton = [=](not_null<Ui::RpWidget*> parent) {
- auto *button = Ui::CreateChild<Ui::IconButton>(
- parent.get(),
- st::historyGiftToChannel);
- parent->widthValue() | rpl::start_with_next([=](int width) {
- button->moveToRight(0, 0);
- }, button->lifetime());
- button->setClickedCallback([=] {
- if (_peer) {
- Ui::ShowStarGiftBox(controller(), _peer);
- }
- });
- return button;
- };
- _giftToChannelIn = setupButton(_muteUnmute);
- _giftToChannelOut = setupButton(_joinChannel);
- }
- void HistoryWidget::pushReplyReturn(not_null<HistoryItem*> item) {
- if (item->history() != _history && item->history() != _migrated) {
- return;
- }
- _cornerButtons.pushReplyReturn(item);
- updateControlsVisibility();
- }
- QVector<FullMsgId> HistoryWidget::replyReturns() const {
- return _cornerButtons.replyReturns();
- }
- void HistoryWidget::setReplyReturns(
- PeerId peer,
- QVector<FullMsgId> replyReturns) {
- if (!_peer || _peer->id != peer) {
- return;
- }
- _cornerButtons.setReplyReturns(std::move(replyReturns));
- }
- void HistoryWidget::fastShowAtEnd(not_null<History*> history) {
- if (_history != history) {
- return;
- }
- clearAllLoadRequests();
- setMsgId(ShowAtUnreadMsgId);
- _pinnedClickedId = FullMsgId();
- _minPinnedId = std::nullopt;
- if (_history->isReadyFor(_showAtMsgId)) {
- historyLoaded();
- } else {
- firstLoadMessages();
- doneShow();
- }
- }
- bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
- InvokeQueued(this, [=] {
- if (_autocomplete) {
- _autocomplete->requestStickersUpdate();
- }
- });
- const auto editDraft = _history ? _history->localEditDraft({}) : nullptr;
- const auto draft = editDraft
- ? editDraft
- : _history
- ? _history->localDraft({})
- : nullptr;
- auto fieldAvailable = canWriteMessage();
- const auto editMsgId = editDraft ? editDraft->reply.messageId.msg : 0;
- if (_voiceRecordBar->isActive() || (!_canSendTexts && !editMsgId)) {
- if (!_canSendTexts) {
- clearFieldText(0, fieldHistoryAction);
- }
- return false;
- }
- if (!draft || (!editDraft && !fieldAvailable)) {
- const auto fieldWillBeHiddenAfterEdit = (!fieldAvailable
- && _editMsgId != 0);
- clearFieldText(0, fieldHistoryAction);
- setInnerFocus();
- _processingReplyItem = _replyEditMsg = nullptr;
- _processingReplyTo = _replyTo = FullReplyTo();
- setEditMsgId(0);
- if (_preview) {
- _preview->apply({ .removed = true });
- }
- if (fieldWillBeHiddenAfterEdit) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- refreshTopBarActiveChat();
- return true;
- }
- _textUpdateEvents = 0;
- setFieldText(draft->textWithTags, 0, fieldHistoryAction);
- setInnerFocus();
- draft->cursor.applyTo(_field);
- _textUpdateEvents = TextUpdateEvent::SaveDraft
- | TextUpdateEvent::SendTyping;
- _processingReplyItem = _replyEditMsg = nullptr;
- _processingReplyTo = _replyTo = FullReplyTo();
- setEditMsgId(editMsgId);
- updateCmdStartShown();
- updateControlsVisibility();
- updateControlsGeometry();
- refreshTopBarActiveChat();
- if (_editMsgId) {
- updateReplyEditTexts();
- if (!_replyEditMsg) {
- requestMessageData(_editMsgId);
- }
- } else {
- const auto draft = _history->localDraft({});
- _processingReplyTo = draft ? draft->reply : FullReplyTo();
- if (_processingReplyTo) {
- _processingReplyItem = session().data().message(
- _processingReplyTo.messageId);
- }
- processReply();
- }
- if (_preview) {
- _preview->setDisabled(_editMsgId
- && _replyEditMsg
- && _replyEditMsg->media()
- && !_replyEditMsg->media()->webpage());
- if (!_editMsgId) {
- _preview->apply(draft->webpage, true);
- } else if (!_replyEditMsg
- || !_replyEditMsg->media()
- || _replyEditMsg->media()->webpage()) {
- _preview->apply(draft->webpage, false);
- }
- }
- return true;
- }
- void HistoryWidget::applyCloudDraft(History *history) {
- Expects(!session().supportMode());
- if (_history == history && !_editMsgId) {
- applyDraft(Ui::InputField::HistoryAction::NewEntry);
- updateControlsVisibility();
- updateControlsGeometry();
- }
- }
- bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const {
- Expects(_history != nullptr);
- if (session().supportMode() || !_history->trackUnreadMessages()) {
- return true;
- } else if (!_historyInited) {
- return false;
- }
- _history->calculateFirstUnreadMessage();
- const auto unread = _history->firstUnreadMessage();
- const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
- "unread: %4, top: %5, visibleBottom: %6."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())
- ).arg(unread ? unread->data()->id.bare : 0
- ).arg(unread ? _list->itemTop(unread) : -1
- ).arg(visibleBottom));
- return unread && _list->itemTop(unread) <= visibleBottom;
- }
- void HistoryWidget::showHistory(
- PeerId peerId,
- MsgId showAtMsgId,
- const Window::SectionShow ¶ms) {
- _pinnedClickedId = FullMsgId();
- _minPinnedId = std::nullopt;
- _showAtMsgParams = {};
- const auto wasState = controller()->dialogsEntryStateCurrent();
- const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
- _showAndMaybeSendStart = (showAtMsgId == ShowAndMaybeStartBotMsgId);
- if (startBot || _showAndMaybeSendStart) {
- showAtMsgId = ShowAtTheEndMsgId;
- }
- _highlighter.clear();
- controller()->sendingAnimation().clear();
- _topToast.hide(anim::type::instant);
- if (_history) {
- if (_peer->id == peerId) {
- updateForwarding();
- if (showAtMsgId == ShowAtUnreadMsgId
- && insideJumpToEndInsteadOfToUnread()) {
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
- "Jumping to end instead of unread."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())));
- showAtMsgId = ShowAtTheEndMsgId;
- } else if (showAtMsgId == ShowForChooseMessagesMsgId) {
- if (_chooseForReport) {
- clearSelected();
- _chooseForReport->active = true;
- _list->setChooseReportReason(
- _chooseForReport->reportInput);
- updateControlsVisibility();
- updateControlsGeometry();
- updateTopBarChooseForReport();
- }
- return;
- }
- if (!IsServerMsgId(showAtMsgId)
- && !IsClientMsgId(showAtMsgId)
- && !IsServerMsgId(-showAtMsgId)) {
- // To end or to unread.
- destroyUnreadBar();
- }
- const auto canShowNow = _history->isReadyFor(showAtMsgId);
- if (!canShowNow) {
- if (!_firstLoadRequest) {
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): Showing delayed at %4."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())
- ).arg(showAtMsgId.bare));
- delayedShowAt(showAtMsgId, params);
- } else if (_showAtMsgId != showAtMsgId) {
- clearAllLoadRequests();
- setMsgId(showAtMsgId, params);
- firstLoadMessages();
- doneShow();
- }
- } else {
- _history->forgetScrollState();
- if (_migrated) {
- _migrated->forgetScrollState();
- }
- clearDelayedShowAt();
- const auto skipId = (_migrated && showAtMsgId < 0)
- ? FullMsgId(_migrated->peer->id, -showAtMsgId)
- : (showAtMsgId > 0)
- ? FullMsgId(_history->peer->id, showAtMsgId)
- : FullMsgId();
- if (skipId) {
- _cornerButtons.skipReplyReturn(skipId);
- }
- setMsgId(showAtMsgId, params);
- if (_historyInited) {
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): "
- "Showing instant at %4."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())
- ).arg(showAtMsgId.bare));
- const auto to = countInitialScrollTop();
- const auto item = getItemFromHistoryOrMigrated(
- _showAtMsgId);
- animatedScrollToY(
- std::clamp(to, 0, _scroll->scrollTopMax()),
- item);
- } else {
- historyLoaded();
- }
- }
- _topBar->update();
- update();
- if (const auto user = _peer->asUser()) {
- if (const auto &info = user->botInfo) {
- if (startBot || clearMaybeSendStart()) {
- if (startBot && wasState.key) {
- info->inlineReturnTo = wasState;
- }
- sendBotStartCommand();
- _history->clearLocalDraft({});
- applyDraft();
- _send->finishAnimating();
- }
- }
- }
- return;
- } else {
- _sponsoredMessagesStateKnown = false;
- session().sponsoredMessages().clearItems(_history);
- session().data().hideShownSpoilers();
- _composeSearch = nullptr;
- }
- session().sendProgressManager().update(
- _history,
- Api::SendProgressType::Typing,
- -1);
- session().data().histories().sendPendingReadInbox(_history);
- session().sendProgressManager().cancelTyping(_history);
- }
- _cornerButtons.clearReplyReturns();
- if (_history) {
- if (Ui::InFocusChain(this)) {
- // Removing focus from list clears selected and updates top bar.
- setFocus();
- }
- controller()->session().api().saveCurrentDraftToCloud();
- if (_migrated) {
- _migrated->clearDrafts(); // use migrated draft only once
- }
- _history->showAtMsgId = _showAtMsgId;
- destroyUnreadBarOnClose();
- _sponsoredMessageBar = nullptr;
- _pinnedBar = nullptr;
- _translateBar = nullptr;
- _pinnedTracker = nullptr;
- _groupCallBar = nullptr;
- _requestsBar = nullptr;
- _chooseTheme = nullptr;
- _membersDropdown.destroy();
- _scrollToAnimation.stop();
- setHistory(nullptr);
- _list = nullptr;
- _peer = nullptr;
- _sendPayment.clear();
- _topicsRequested.clear();
- _canSendMessages = false;
- _canSendTexts = false;
- _fieldDisabled = nullptr;
- _silent.destroy();
- updateBotKeyboard();
- } else {
- Assert(_list == nullptr);
- }
- HistoryView::Element::ClearGlobal();
- _saveEditMsgRequestId = 0;
- _processingReplyItem = _replyEditMsg = nullptr;
- _processingReplyTo = _replyTo = FullReplyTo();
- _editMsgId = MsgId();
- _canReplaceMedia = _canAddMedia = false;
- _photoEditMedia = nullptr;
- updateReplaceMediaButton();
- _fieldBarCancel->hide();
- _membersDropdownShowTimer.cancel();
- _scroll->takeWidget<HistoryInner>().destroy();
- clearInlineBot();
- _showAtMsgId = showAtMsgId;
- _showAtMsgParams = params;
- _historyInited = false;
- _paysStatus = nullptr;
- _contactStatus = nullptr;
- _businessBotStatus = nullptr;
- Core::App().mediaDevices().refreshRecordAvailability();
- if (peerId) {
- using namespace HistoryView;
- _peer = session().data().peer(peerId);
- _contactStatus = std::make_unique<ContactStatus>(
- controller(),
- this,
- _peer,
- false);
- _contactStatus->bar().heightValue(
- ) | rpl::start_with_next([=] {
- updateControlsGeometry();
- }, _contactStatus->bar().lifetime());
- refreshGiftToChannelShown();
- if (const auto user = _peer->asUser()) {
- _paysStatus = std::make_unique<PaysStatus>(
- controller(),
- this,
- user);
- _paysStatus->bar().heightValue(
- ) | rpl::start_with_next([=] {
- updateControlsGeometry();
- }, _paysStatus->bar().lifetime());
- _businessBotStatus = std::make_unique<BusinessBotStatus>(
- controller(),
- this,
- user);
- _businessBotStatus->bar().heightValue(
- ) | rpl::start_with_next([=] {
- updateControlsGeometry();
- }, _businessBotStatus->bar().lifetime());
- }
- orderWidgets();
- controller()->tabbedSelector()->setCurrentPeer(_peer);
- }
- refreshTabbedPanel();
- initFieldAutocomplete();
- if (_peer) {
- _unblock->setText(((_peer->isUser()
- && _peer->asUser()->isBot()
- && !_peer->asUser()->isSupport())
- ? tr::lng_restart_button(tr::now)
- : tr::lng_unblock_button(tr::now)).toUpper());
- }
- _nonEmptySelection = false;
- _itemRevealPending.clear();
- _itemRevealAnimations.clear();
- _itemsRevealHeight = 0;
- if (_peer) {
- setHistory(_peer->owner().history(_peer));
- if (_migrated
- && !_migrated->isEmpty()
- && (!_history->loadedAtTop() || !_migrated->loadedAtBottom())) {
- _migrated->clear(History::ClearType::Unload);
- }
- _history->setFakeUnreadWhileOpened(true);
- if (_showAtMsgId == ShowForChooseMessagesMsgId) {
- _showAtMsgId = ShowAtUnreadMsgId;
- if (_chooseForReport) {
- _chooseForReport->active = true;
- }
- } else {
- _chooseForReport = nullptr;
- }
- if (_showAtMsgId == ShowAtUnreadMsgId
- && !_history->trackUnreadMessages()
- && !hasSavedScroll()) {
- _showAtMsgId = ShowAtTheEndMsgId;
- }
- refreshTopBarActiveChat();
- updateTopBarSelection();
- if (_peer->isChannel()) {
- updateNotifyControls();
- session().data().notifySettings().request(_peer);
- refreshSilentToggle();
- } else if (_peer->isRepliesChat() || _peer->isVerifyCodes()) {
- updateNotifyControls();
- }
- refreshScheduledToggle();
- refreshSendAsToggle();
- if (_showAtMsgId == ShowAtUnreadMsgId) {
- if (_history->scrollTopItem) {
- _showAtMsgId = _history->showAtMsgId;
- }
- } else {
- _history->forgetScrollState();
- if (_migrated) {
- _migrated->forgetScrollState();
- }
- }
- _scroll->hide();
- _list = _scroll->setOwnedWidget(
- object_ptr<HistoryInner>(this, _scroll, controller(), _history));
- _list->sendIntroSticker(
- ) | rpl::start_with_next([=](not_null<DocumentData*> sticker) {
- sendExistingDocument(
- sticker,
- Api::MessageToSend(prepareSendAction({})));
- }, _list->lifetime());
- _list->show();
- if (const auto channel = _peer->asChannel()) {
- channel->updateFull();
- if (!channel->isBroadcast()) {
- channel->flagsValue(
- ) | rpl::start_with_next([=] {
- refreshJoinChannelText();
- }, _list->lifetime());
- } else {
- refreshJoinChannelText();
- }
- }
- controller()->adaptive().changes(
- ) | rpl::start_with_next([=] {
- _history->forceFullResize();
- if (_migrated) {
- _migrated->forceFullResize();
- }
- updateHistoryGeometry();
- update();
- }, _list->lifetime());
- if (_chooseForReport && _chooseForReport->active) {
- _list->setChooseReportReason(_chooseForReport->reportInput);
- }
- updateTopBarChooseForReport();
- _updateHistoryItems.cancel();
- setupTranslateBar();
- setupPinnedTracker();
- setupGroupCallBar();
- setupRequestsBar();
- checkMessagesTTL();
- if (_history->scrollTopItem
- || (_migrated && _migrated->scrollTopItem)
- || _history->isReadyFor(_showAtMsgId)) {
- historyLoaded();
- } else {
- firstLoadMessages();
- doneShow();
- }
- handlePeerUpdate();
- session().local().readDraftsWithCursors(_history);
- if (!applyDraft()) {
- clearFieldText();
- }
- checkCharsCount();
- _send->finishAnimating();
- updateControlsGeometry();
- if (const auto user = _peer->asUser()) {
- if (const auto &info = user->botInfo) {
- if (startBot
- || (!_history->isEmpty() && clearMaybeSendStart())) {
- if (startBot && wasState.key) {
- info->inlineReturnTo = wasState;
- }
- sendBotStartCommand();
- }
- }
- }
- if (!_history->folderKnown()) {
- session().data().histories().requestDialogEntry(_history);
- }
- // Must be done before unreadCountUpdated(), or we auto-close.
- if (_history->unreadMark()) {
- session().data().histories().changeDialogUnreadMark(
- _history,
- false);
- }
- if (_migrated && _migrated->unreadMark()) {
- session().data().histories().changeDialogUnreadMark(
- _migrated,
- false);
- }
- unreadCountUpdated(); // set _historyDown badge.
- showAboutTopPromotion();
- if (!session().sponsoredMessages().isTopBarFor(_history)) {
- _scroll->setTrackingContent(false);
- const auto checkState = [=] {
- using State = Data::SponsoredMessages::State;
- const auto state = session().sponsoredMessages().state(
- _history);
- _sponsoredMessagesStateKnown = (state != State::None);
- if (state == State::AppendToEnd) {
- _scroll->setTrackingContent(
- session().sponsoredMessages().canHaveFor(_history));
- } else if (state == State::InjectToMiddle) {
- injectSponsoredMessages();
- } else if (state == State::AppendToTopBar) {
- }
- };
- const auto history = _history;
- session().sponsoredMessages().request(
- _history,
- crl::guard(this, [=, this] {
- if (history == _history) {
- checkState();
- }
- }));
- checkState();
- } else {
- requestSponsoredMessageBar();
- }
- } else {
- _chooseForReport = nullptr;
- refreshTopBarActiveChat();
- updateTopBarSelection();
- checkMessagesTTL();
- clearFieldText();
- doneShow();
- }
- updateForwarding();
- updateOverStates(mapFromGlobal(QCursor::pos()));
- if (_history) {
- const auto msgId = (_showAtMsgId == ShowAtTheEndMsgId)
- ? ShowAtUnreadMsgId
- : _showAtMsgId;
- controller()->setActiveChatEntry({
- _history,
- FullMsgId(_history->peer->id, msgId) });
- }
- update();
- controller()->floatPlayerAreaUpdated();
- session().data().itemVisibilitiesUpdated();
- crl::on_main(this, [=] { controller()->widget()->setInnerFocus(); });
- }
- void HistoryWidget::setHistory(History *history) {
- if (_history == history) {
- return;
- }
- const auto was = _attachBotsMenu && _history && _history->peer->isUser();
- const auto now = _attachBotsMenu && history && history->peer->isUser();
- if (was && !now) {
- _attachToggle->removeEventFilter(_attachBotsMenu.get());
- _attachBotsMenu->hideFast();
- } else if (now && !was && !ChatHelpers::ShowPanelOnClick()) {
- _attachToggle->installEventFilter(_attachBotsMenu.get());
- }
- const auto unloadHeavyViewParts = [](History *history) {
- if (history) {
- history->owner().unloadHeavyViewParts(
- history->delegateMixin()->delegate());
- history->forceFullResize();
- }
- };
- if (_history) {
- unregisterDraftSources();
- clearAllLoadRequests();
- clearSupportPreloadRequest();
- _historySponsoredPreloading.destroy();
- const auto wasHistory = base::take(_history);
- const auto wasMigrated = base::take(_migrated);
- unloadHeavyViewParts(wasHistory);
- unloadHeavyViewParts(wasMigrated);
- }
- if (history) {
- _history = history;
- _migrated = _history ? _history->migrateFrom() : nullptr;
- registerDraftSource();
- if (_history) {
- setupPreview();
- } else {
- _previewDrawPreview = nullptr;
- _preview = nullptr;
- }
- }
- refreshAttachBotsMenu();
- }
- void HistoryWidget::setupPreview() {
- Expects(_history != nullptr);
- using namespace HistoryView::Controls;
- _preview = std::make_unique<WebpageProcessor>(_history, _field);
- _preview->repaintRequests() | rpl::start_with_next([=] {
- updateField();
- }, _preview->lifetime());
- _preview->parsedValue(
- ) | rpl::start_with_next([=](WebpageParsed value) {
- _previewTitle.setText(
- st::msgNameStyle,
- value.title,
- Ui::NameTextOptions());
- _previewDescription.setText(
- st::defaultTextStyle,
- value.description,
- Ui::DialogTextOptions());
- const auto changed = (!_previewDrawPreview != !value.drawPreview);
- _previewDrawPreview = value.drawPreview;
- if (changed) {
- updateControlsGeometry();
- updateControlsVisibility();
- }
- updateField();
- }, _preview->lifetime());
- }
- void HistoryWidget::injectSponsoredMessages() const {
- session().sponsoredMessages().inject(
- _history,
- _showAtMsgId,
- _scroll->height() * 2,
- _scroll->width());
- }
- void HistoryWidget::refreshAttachBotsMenu() {
- _attachBotsMenu = nullptr;
- if (!_history) {
- return;
- }
- _attachBotsMenu = InlineBots::MakeAttachBotsMenu(
- this,
- controller(),
- _history->peer,
- [=] { return prepareSendAction({}); },
- [=](bool compress) { chooseAttach(compress); });
- if (!_attachBotsMenu) {
- return;
- }
- _attachBotsMenu->setOrigin(
- Ui::PanelAnimation::Origin::BottomLeft);
- if (!ChatHelpers::ShowPanelOnClick()) {
- _attachToggle->installEventFilter(_attachBotsMenu.get());
- }
- _attachBotsMenu->heightValue(
- ) | rpl::start_with_next([=] {
- moveFieldControls();
- }, _attachBotsMenu->lifetime());
- }
- void HistoryWidget::unregisterDraftSources() {
- if (!_history) {
- return;
- }
- session().local().unregisterDraftSource(
- _history,
- Data::DraftKey::Local({}));
- session().local().unregisterDraftSource(
- _history,
- Data::DraftKey::LocalEdit({}));
- }
- void HistoryWidget::registerDraftSource() {
- if (!_history) {
- return;
- }
- const auto peerId = _history->peer->id;
- const auto editMsgId = _editMsgId;
- const auto draft = [=] {
- return Storage::MessageDraft{
- (editMsgId
- ? FullReplyTo{ FullMsgId(peerId, editMsgId) }
- : _replyTo),
- _field->getTextWithTags(),
- _preview->draft(),
- };
- };
- auto draftSource = Storage::MessageDraftSource{
- .draft = draft,
- .cursor = [=] { return MessageCursor(_field); },
- };
- session().local().registerDraftSource(
- _history,
- (editMsgId
- ? Data::DraftKey::LocalEdit({})
- : Data::DraftKey::Local({})),
- std::move(draftSource));
- }
- void HistoryWidget::setEditMsgId(MsgId msgId) {
- unregisterDraftSources();
- _editMsgId = msgId;
- if (!msgId) {
- _mediaEditManager.cancel();
- _canReplaceMedia = _canAddMedia = false;
- if (_preview) {
- _preview->setDisabled(false);
- }
- }
- if (_history) {
- refreshSendAsToggle();
- orderWidgets();
- }
- registerDraftSource();
- }
- void HistoryWidget::clearDelayedShowAt() {
- _delayedShowAtMsgId = -1;
- clearDelayedShowAtRequest();
- }
- void HistoryWidget::clearDelayedShowAtRequest() {
- Expects(_history != nullptr);
- if (_delayedShowAtRequest) {
- _history->owner().histories().cancelRequest(_delayedShowAtRequest);
- _delayedShowAtRequest = 0;
- }
- }
- void HistoryWidget::clearSupportPreloadRequest() {
- Expects(_history != nullptr);
- if (_supportPreloadRequest) {
- auto &histories = _history->owner().histories();
- histories.cancelRequest(_supportPreloadRequest);
- _supportPreloadRequest = 0;
- }
- }
- void HistoryWidget::clearAllLoadRequests() {
- Expects(_history != nullptr);
- auto &histories = _history->owner().histories();
- clearDelayedShowAtRequest();
- if (_firstLoadRequest) {
- histories.cancelRequest(_firstLoadRequest);
- _firstLoadRequest = 0;
- }
- if (_preloadRequest) {
- histories.cancelRequest(_preloadRequest);
- _preloadRequest = 0;
- }
- if (_preloadDownRequest) {
- histories.cancelRequest(_preloadDownRequest);
- _preloadDownRequest = 0;
- }
- }
- bool HistoryWidget::updateReplaceMediaButton() {
- if (!_canReplaceMedia && !_canAddMedia) {
- const auto result = (_replaceMedia != nullptr);
- _replaceMedia.destroy();
- return result;
- } else if (_replaceMedia) {
- return false;
- }
- _replaceMedia.create(
- this,
- _canReplaceMedia ? st::historyReplaceMedia : st::historyAddMedia);
- const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration;
- _replaceMedia->setClickedCallback([=] {
- base::call_delayed(hideDuration, this, [=] {
- EditCaptionBox::StartMediaReplace(
- controller(),
- { _history->peer->id, _editMsgId },
- _field->getTextWithTags(),
- _mediaEditManager.spoilered(),
- _mediaEditManager.invertCaption(),
- crl::guard(_list, [=] { cancelEdit(); }));
- });
- });
- return true;
- }
- void HistoryWidget::updateFieldSubmitSettings() {
- const auto settings = _isInlineBot
- ? Ui::InputField::SubmitSettings::None
- : Core::App().settings().sendSubmitWay();
- _field->setSubmitSettings(settings);
- }
- void HistoryWidget::updateNotifyControls() {
- if (!_peer || (!_peer->isChannel() && !_peer->isRepliesChat() && !_peer->isVerifyCodes())) {
- return;
- }
- _muteUnmute->setText((_history->muted()
- ? tr::lng_channel_unmute(tr::now)
- : tr::lng_channel_mute(tr::now)).toUpper());
- if (!session().data().notifySettings().silentPostsUnknown(_peer)) {
- if (_silent) {
- _silent->setChecked(
- session().data().notifySettings().silentPosts(_peer));
- updateFieldPlaceholder();
- } else if (hasSilentToggle()) {
- refreshSilentToggle();
- updateControlsVisibility();
- updateControlsGeometry();
- }
- }
- }
- void HistoryWidget::refreshSilentToggle() {
- if (!_silent && hasSilentToggle()) {
- _silent.create(this, _peer->asChannel());
- orderWidgets();
- } else if (_silent && !hasSilentToggle()) {
- _silent.destroy();
- }
- }
- void HistoryWidget::setupFastButtonMode() {
- const auto field = _field->rawTextEdit();
- base::install_event_filter(field, [=](not_null<QEvent*> e) {
- if (e->type() != QEvent::KeyPress
- || !_history
- || !FastButtonsMode()
- || !session().fastButtonsBots().enabled(_history->peer)
- || !_field->getLastText().isEmpty()) {
- return base::EventFilterResult::Continue;
- }
- const auto k = static_cast<QKeyEvent*>(e.get());
- const auto key = k->key();
- if (key < Qt::Key_1 || key > Qt::Key_9 || k->modifiers()) {
- return base::EventFilterResult::Continue;
- }
- const auto item = _history ? _history->lastMessage() : nullptr;
- const auto markup = item ? item->inlineReplyKeyboard() : nullptr;
- const auto link = markup
- ? markup->getLinkByIndex(key - Qt::Key_1)
- : nullptr;
- if (!link) {
- return base::EventFilterResult::Continue;
- }
- ActivateClickHandler(window(), link, {
- Qt::LeftButton,
- QVariant::fromValue(ClickHandlerContext{
- .itemId = item->fullId(),
- .sessionWindow = base::make_weak(controller()),
- }),
- });
- return base::EventFilterResult::Cancel;
- });
- }
- void HistoryWidget::setupScheduledToggle() {
- controller()->activeChatValue(
- ) | rpl::map([=](Dialogs::Key key) -> rpl::producer<> {
- if (const auto history = key.history()) {
- return session().scheduledMessages().updates(history);
- } else if (const auto topic = key.topic()) {
- return session().scheduledMessages().updates(
- topic->owningHistory());
- }
- return rpl::never<rpl::empty_value>();
- }) | rpl::flatten_latest(
- ) | rpl::start_with_next([=] {
- refreshScheduledToggle();
- updateControlsVisibility();
- updateControlsGeometry();
- }, lifetime());
- }
- void HistoryWidget::refreshScheduledToggle() {
- const auto has = _history
- && _canSendMessages
- && (session().scheduledMessages().count(_history) > 0);
- if (!_scheduled && has) {
- _scheduled.create(this, st::historyScheduledToggle);
- _scheduled->show();
- _scheduled->addClickHandler([=] {
- controller()->showSection(
- std::make_shared<HistoryView::ScheduledMemento>(_history));
- });
- orderWidgets(); // Raise drag areas to the top.
- } else if (_scheduled && !has) {
- _scheduled.destroy();
- }
- }
- void HistoryWidget::setupSendAsToggle() {
- session().sendAsPeers().updated(
- ) | rpl::filter([=](not_null<PeerData*> peer) {
- return (peer == _peer);
- }) | rpl::start_with_next([=] {
- refreshSendAsToggle();
- updateControlsVisibility();
- updateControlsGeometry();
- orderWidgets();
- }, lifetime());
- }
- void HistoryWidget::refreshSendAsToggle() {
- Expects(_peer != nullptr);
- if (_editMsgId || !session().sendAsPeers().shouldChoose(_peer)) {
- _sendAs.destroy();
- return;
- } else if (_sendAs) {
- return;
- }
- _sendAs.create(this, st::sendAsButton);
- Ui::SetupSendAsButton(_sendAs.data(), controller());
- }
- bool HistoryWidget::contentOverlapped(const QRect &globalRect) {
- return (_attachDragAreas.document->overlaps(globalRect)
- || _attachDragAreas.photo->overlaps(globalRect)
- || (_autocomplete && _autocomplete->overlaps(globalRect))
- || (_tabbedPanel && _tabbedPanel->overlaps(globalRect))
- || (_inlineResults && _inlineResults->overlaps(globalRect)));
- }
- bool HistoryWidget::canWriteMessage() const {
- return _history
- && _canSendMessages
- && !isBlocked()
- && !isJoinChannel()
- && !isMuteUnmute()
- && !isBotStart()
- && !isSearching();
- }
- void HistoryWidget::updateControlsVisibility() {
- auto fieldDisabledRemoved = (_fieldDisabled != nullptr);
- const auto hideExtraButtons = _fieldCharsCountManager.isLimitExceeded();
- const auto guard = gsl::finally([&] {
- if (fieldDisabledRemoved) {
- _fieldDisabled = nullptr;
- }
- });
- if (!_showAnimation) {
- _topShadow->setVisible(_peer != nullptr);
- _topBar->setVisible(_peer != nullptr);
- }
- _cornerButtons.updateJumpDownVisibility();
- _cornerButtons.updateUnreadThingsVisibility();
- if (!_history || _showAnimation) {
- hideChildWidgets();
- return;
- }
- if (_firstLoadRequest && !_scroll->isHidden()) {
- if (Ui::InFocusChain(_scroll.data())) {
- // Don't loose focus back to chats list.
- setFocus();
- }
- _scroll->hide();
- } else if (!_firstLoadRequest && _scroll->isHidden()) {
- _scroll->show();
- }
- if (_pinnedBar) {
- _pinnedBar->show();
- }
- if (_sponsoredMessageBar && checkSponsoredMessageBarVisibility()) {
- _sponsoredMessageBar->toggle(true, anim::type::normal);
- }
- if (_translateBar) {
- _translateBar->show();
- }
- if (_groupCallBar) {
- _groupCallBar->show();
- }
- if (_requestsBar) {
- _requestsBar->show();
- }
- if (_paysStatus) {
- _paysStatus->show();
- }
- if (_contactStatus) {
- _contactStatus->show();
- }
- if (_businessBotStatus) {
- _businessBotStatus->show();
- }
- if (isChoosingTheme()
- || (!editingMessage()
- && (isSearching()
- || isBlocked()
- || isJoinChannel()
- || isMuteUnmute()
- || isBotStart()
- || isReportMessages()))) {
- const auto toggle = [&](Ui::FlatButton *shown) {
- const auto toggleOne = [&](not_null<Ui::FlatButton*> button) {
- if (button.get() != shown) {
- button->hide();
- } else if (button->isHidden()) {
- button->clearState();
- button->show();
- }
- };
- toggleOne(_reportMessages);
- toggleOne(_joinChannel);
- toggleOne(_muteUnmute);
- toggleOne(_botStart);
- toggleOne(_unblock);
- };
- if (isChoosingTheme()) {
- _chooseTheme->show();
- setInnerFocus();
- toggle(nullptr);
- } else if (isReportMessages()) {
- toggle(_reportMessages);
- } else if (isBlocked()) {
- toggle(_unblock);
- } else if (isJoinChannel()) {
- toggle(_joinChannel);
- } else if (isMuteUnmute()) {
- toggle(_muteUnmute);
- } else if (isBotStart()) {
- toggle(_botStart);
- }
- _kbShown = false;
- if (_autocomplete) {
- _autocomplete->hide();
- }
- if (_supportAutocomplete) {
- _supportAutocomplete->hide();
- }
- _send->hide();
- if (_silent) {
- _silent->hide();
- }
- if (_scheduled) {
- _scheduled->hide();
- }
- if (_ttlInfo) {
- _ttlInfo->hide();
- }
- if (_sendAs) {
- _sendAs->hide();
- }
- _kbScroll->hide();
- _fieldBarCancel->hide();
- _attachToggle->hide();
- if (_replaceMedia) {
- _replaceMedia->hide();
- }
- _tabbedSelectorToggle->hide();
- _botKeyboardShow->hide();
- _botKeyboardHide->hide();
- _botCommandStart->hide();
- if (_botMenu.button) {
- _botMenu.button->hide();
- }
- if (_tabbedPanel) {
- _tabbedPanel->hide();
- }
- if (_voiceRecordBar) {
- _voiceRecordBar->hideFast();
- }
- if (_inlineResults) {
- _inlineResults->hide();
- }
- if (_sendRestriction) {
- _sendRestriction->hide();
- }
- hideFieldIfVisible();
- } else if (editingMessage() || _canSendMessages) {
- if (_autocomplete) {
- _autocomplete->requestRefresh();
- }
- _unblock->hide();
- _botStart->hide();
- _joinChannel->hide();
- _muteUnmute->hide();
- _reportMessages->hide();
- _send->show();
- updateSendButtonType();
- if (_canSendTexts || _editMsgId) {
- _field->show();
- } else {
- fieldDisabledRemoved = false;
- if (!_fieldDisabled) {
- _fieldDisabled = CreateDisabledFieldView(this, _peer);
- orderWidgets();
- updateControlsGeometry();
- update();
- }
- _fieldDisabled->show();
- hideFieldIfVisible();
- }
- if (_kbShown) {
- _kbScroll->show();
- _tabbedSelectorToggle->hide();
- showKeyboardHideButton();
- _botKeyboardShow->hide();
- _botCommandStart->hide();
- } else if (_kbReplyTo) {
- _kbScroll->hide();
- _tabbedSelectorToggle->show();
- _botKeyboardHide->hide();
- _botKeyboardShow->hide();
- _botCommandStart->hide();
- } else {
- _kbScroll->hide();
- _tabbedSelectorToggle->show();
- _botKeyboardHide->hide();
- if (_keyboard->hasMarkup()) {
- _botKeyboardShow->show();
- _botCommandStart->hide();
- } else {
- _botKeyboardShow->hide();
- _botCommandStart->setVisible(_cmdStartShown);
- }
- }
- if (_replaceMedia) {
- _replaceMedia->show();
- _attachToggle->hide();
- } else {
- _attachToggle->show();
- }
- if (_botMenu.button) {
- _botMenu.button->show();
- }
- if (_sendRestriction) {
- _sendRestriction->hide();
- }
- {
- auto rightButtonsChanged = false;
- if (_silent) {
- const auto was = _silent->isVisible();
- const auto now = (!_editMsgId) && (!hideExtraButtons);
- if (was != now) {
- _silent->setVisible(now);
- rightButtonsChanged = true;
- }
- }
- if (_scheduled) {
- const auto was = _scheduled->isVisible();
- const auto now = (!_editMsgId) && (!hideExtraButtons);
- if (was != now) {
- _scheduled->setVisible(now);
- rightButtonsChanged = true;
- }
- }
- if (_ttlInfo) {
- const auto was = _ttlInfo->isVisible();
- const auto now = (!_editMsgId) && (!hideExtraButtons);
- if (was != now) {
- _ttlInfo->setVisible(now);
- rightButtonsChanged = true;
- }
- }
- if (rightButtonsChanged) {
- updateFieldSize();
- }
- }
- if (_sendAs) {
- _sendAs->show();
- }
- updateFieldPlaceholder();
- if (_editMsgId
- || _replyTo
- || readyToForward()
- || _previewDrawPreview
- || _kbReplyTo) {
- if (_fieldBarCancel->isHidden()) {
- _fieldBarCancel->show();
- updateControlsGeometry();
- update();
- }
- } else {
- _fieldBarCancel->hide();
- }
- } else {
- if (_autocomplete) {
- _autocomplete->hide();
- }
- if (_supportAutocomplete) {
- _supportAutocomplete->hide();
- }
- _send->hide();
- _unblock->hide();
- _botStart->hide();
- _joinChannel->hide();
- _muteUnmute->hide();
- _reportMessages->hide();
- _attachToggle->hide();
- if (_silent) {
- _silent->hide();
- }
- if (_scheduled) {
- _scheduled->hide();
- }
- if (_ttlInfo) {
- _ttlInfo->hide();
- }
- if (_sendAs) {
- _sendAs->hide();
- }
- if (_botMenu.button) {
- _botMenu.button->hide();
- }
- _kbScroll->hide();
- if (_replyTo || readyToForward() || _kbReplyTo) {
- if (_fieldBarCancel->isHidden()) {
- _fieldBarCancel->show();
- updateControlsGeometry();
- update();
- }
- } else {
- _fieldBarCancel->hide();
- }
- _tabbedSelectorToggle->hide();
- _botKeyboardShow->hide();
- _botKeyboardHide->hide();
- _botCommandStart->hide();
- if (_tabbedPanel) {
- _tabbedPanel->hide();
- }
- if (_voiceRecordBar) {
- _voiceRecordBar->hideFast();
- }
- if (_composeSearch) {
- _composeSearch->hideAnimated();
- }
- if (_inlineResults) {
- _inlineResults->hide();
- }
- if (_sendRestriction) {
- _sendRestriction->show();
- }
- _kbScroll->hide();
- hideFieldIfVisible();
- }
- //checkTabbedSelectorToggleTooltip();
- updateMouseTracking();
- }
- void HistoryWidget::hideFieldIfVisible() {
- if (_field->isHidden()) {
- return;
- } else if (InFocusChain(_field)) {
- setFocus();
- }
- _field->hide();
- updateControlsGeometry();
- update();
- }
- void HistoryWidget::showAboutTopPromotion() {
- Expects(_history != nullptr);
- Expects(_list != nullptr);
- if (!_history->useTopPromotion() || _history->topPromotionAboutShown()) {
- return;
- }
- _history->markTopPromotionAboutShown();
- const auto type = _history->topPromotionType();
- const auto custom = type.isEmpty()
- ? QString()
- : Lang::GetNonDefaultValue(kPsaAboutPrefix + type.toUtf8());
- const auto text = type.isEmpty()
- ? tr::lng_proxy_sponsor_about(tr::now, Ui::Text::RichLangValue)
- : custom.isEmpty()
- ? tr::lng_about_psa_default(tr::now, Ui::Text::RichLangValue)
- : Ui::Text::RichLangValue(custom);
- showInfoTooltip(text, nullptr);
- }
- void HistoryWidget::updateMouseTracking() {
- const auto trackMouse = !_fieldBarCancel->isHidden();
- setMouseTracking(trackMouse);
- }
- void HistoryWidget::destroyUnreadBar() {
- if (_history) _history->destroyUnreadBar();
- if (_migrated) _migrated->destroyUnreadBar();
- }
- void HistoryWidget::destroyUnreadBarOnClose() {
- if (!_history || !_historyInited) {
- return;
- } else if (_scroll->scrollTop() == _scroll->scrollTopMax()) {
- destroyUnreadBar();
- return;
- }
- const auto top = unreadBarTop();
- if (top && *top < _scroll->scrollTop()) {
- destroyUnreadBar();
- return;
- }
- }
- void HistoryWidget::newItemAdded(not_null<HistoryItem*> item) {
- if (_history != item->history()
- || !_historyInited
- || item->isScheduled()) {
- return;
- }
- if (item->isSponsored()) {
- if (const auto view = item->mainView()) {
- view->resizeGetHeight(width());
- updateHistoryGeometry(
- false,
- true,
- { ScrollChangeNoJumpToBottom, 0 });
- }
- return;
- }
- // If we get here in non-resized state we can't rely on results of
- // markingMessagesRead() and mark chat as read.
- // If we receive N messages being not at bottom:
- // - on first message we set unreadcount += 1, firstUnreadMessage.
- // - on second we get wrong markingMessagesRead() and read both.
- session().data().sendHistoryChangeNotifications();
- if (item->isSending()) {
- synteticScrollToY(_scroll->scrollTopMax());
- } else if (_scroll->scrollTop() < _scroll->scrollTopMax()) {
- return;
- }
- if (item->showNotification()) {
- destroyUnreadBar();
- if (markingMessagesRead()) {
- if (_list && item->hasUnwatchedEffect()) {
- _list->startEffectOnRead(item);
- }
- if (item->isUnreadMention() && !item->isUnreadMedia()) {
- session().api().markContentsRead(item);
- }
- session().data().histories().readInboxOnNewMessage(item);
- // Also clear possible scheduled messages notifications.
- // Side-effect: Also clears all notifications from forum topics.
- Core::App().notifications().clearFromHistory(_history);
- }
- }
- const auto view = item->mainView();
- if (!view) {
- return;
- } else if (anim::Disabled()) {
- if (!On(PowerSaving::kChatBackground)) {
- // Strange case of disabled animations, but enabled bg rotation.
- if (item->out() || _history->peer->isSelf()) {
- _list->theme()->rotateComplexGradientBackground();
- }
- }
- return;
- }
- _itemRevealPending.emplace(item);
- }
- void HistoryWidget::maybeMarkReactionsRead(not_null<HistoryItem*> item) {
- if (!_historyInited || !_list) {
- return;
- }
- const auto view = item->mainView();
- const auto itemTop = _list->itemTop(view);
- if (itemTop <= 0 || !markingContentsRead()) {
- return;
- }
- const auto reactionCenter
- = view->reactionButtonParameters({}, {}).center.y();
- const auto visibleTop = _scroll->scrollTop();
- const auto visibleBottom = visibleTop + _scroll->height();
- if (itemTop + reactionCenter < visibleTop
- || itemTop + view->height() > visibleBottom) {
- return;
- }
- session().api().markContentsRead(item);
- }
- void HistoryWidget::unreadCountUpdated() {
- if (_history->unreadMark() || (_migrated && _migrated->unreadMark())) {
- crl::on_main(this, [=, history = _history] {
- if (history == _history) {
- closeCurrent();
- }
- });
- } else {
- const auto hideCounter = _history->isForum()
- || !_history->trackUnreadMessages();
- _cornerButtons.updateJumpDownVisibility(hideCounter
- ? 0
- : _history->chatListBadgesState().unreadCounter);
- }
- }
- void HistoryWidget::closeCurrent() {
- if (controller()->isPrimary()) {
- controller()->showBackFromStack();
- } else {
- controller()->window().close();
- }
- }
- void HistoryWidget::messagesFailed(const MTP::Error &error, int requestId) {
- if (error.type() == u"CHANNEL_PRIVATE"_q
- && _peer->isChannel()
- && _peer->asChannel()->invitePeekExpires()) {
- _peer->asChannel()->privateErrorReceived();
- } else if (error.type() == u"CHANNEL_PRIVATE"_q
- || error.type() == u"CHANNEL_PUBLIC_GROUP_NA"_q
- || error.type() == u"USER_BANNED_IN_CHANNEL"_q) {
- auto was = _peer;
- closeCurrent();
- const auto wasAccount = not_null(&was->account());
- if (const auto primary = Core::App().windowFor(wasAccount)) {
- primary->showToast(was->isMegagroup()
- ? tr::lng_group_not_accessible(tr::now)
- : tr::lng_channel_not_accessible(tr::now));
- }
- return;
- }
- LOG(("RPC Error: %1 %2: %3").arg(
- QString::number(error.code()),
- error.type(),
- error.description()));
- if (_preloadRequest == requestId) {
- _preloadRequest = 0;
- } else if (_preloadDownRequest == requestId) {
- _preloadDownRequest = 0;
- } else if (_firstLoadRequest == requestId) {
- _firstLoadRequest = 0;
- closeCurrent();
- } else if (_delayedShowAtRequest == requestId) {
- _delayedShowAtRequest = 0;
- }
- }
- void HistoryWidget::messagesReceived(
- not_null<PeerData*> peer,
- const MTPmessages_Messages &messages,
- int requestId) {
- Expects(_history != nullptr);
- const auto toMigrated = (peer == _peer->migrateFrom());
- if (peer != _peer && !toMigrated) {
- if (_preloadRequest == requestId) {
- _preloadRequest = 0;
- } else if (_preloadDownRequest == requestId) {
- _preloadDownRequest = 0;
- } else if (_firstLoadRequest == requestId) {
- _firstLoadRequest = 0;
- } else if (_delayedShowAtRequest == requestId) {
- _delayedShowAtRequest = 0;
- }
- return;
- }
- auto count = 0;
- const QVector<MTPMessage> emptyList, *histList = &emptyList;
- switch (messages.type()) {
- case mtpc_messages_messages: {
- auto &d(messages.c_messages_messages());
- _history->owner().processUsers(d.vusers());
- _history->owner().processChats(d.vchats());
- histList = &d.vmessages().v;
- count = histList->size();
- } break;
- case mtpc_messages_messagesSlice: {
- auto &d(messages.c_messages_messagesSlice());
- _history->owner().processUsers(d.vusers());
- _history->owner().processChats(d.vchats());
- histList = &d.vmessages().v;
- count = d.vcount().v;
- } break;
- case mtpc_messages_channelMessages: {
- auto &d(messages.c_messages_channelMessages());
- if (const auto channel = peer->asChannel()) {
- channel->ptsReceived(d.vpts().v);
- channel->processTopics(d.vtopics());
- } else {
- LOG(("API Error: received messages.channelMessages when "
- "no channel was passed! (HistoryWidget::messagesReceived)"));
- }
- _history->owner().processUsers(d.vusers());
- _history->owner().processChats(d.vchats());
- histList = &d.vmessages().v;
- count = d.vcount().v;
- } break;
- case mtpc_messages_messagesNotModified: {
- LOG(("API Error: received messages.messagesNotModified! "
- "(HistoryWidget::messagesReceived)"));
- } break;
- }
- if (_preloadRequest == requestId) {
- addMessagesToFront(peer, *histList);
- _preloadRequest = 0;
- preloadHistoryIfNeeded();
- } else if (_preloadDownRequest == requestId) {
- addMessagesToBack(peer, *histList);
- _preloadDownRequest = 0;
- preloadHistoryIfNeeded();
- if (_history->loadedAtBottom()) {
- checkActivation();
- }
- } else if (_firstLoadRequest == requestId) {
- if (toMigrated) {
- _history->clear(History::ClearType::Unload);
- } else if (_migrated) {
- _migrated->clear(History::ClearType::Unload);
- }
- addMessagesToFront(peer, *histList);
- _firstLoadRequest = 0;
- if (_history->loadedAtTop() && _history->isEmpty() && count > 0) {
- firstLoadMessages();
- return;
- }
- historyLoaded();
- injectSponsoredMessages();
- } else if (_delayedShowAtRequest == requestId) {
- if (toMigrated) {
- _history->clear(History::ClearType::Unload);
- } else if (_migrated) {
- _migrated->clear(History::ClearType::Unload);
- }
- clearAllLoadRequests();
- _firstLoadRequest = -1; // hack - don't updateListSize yet
- _history->getReadyFor(_delayedShowAtMsgId);
- if (_history->isEmpty()) {
- addMessagesToFront(peer, *histList);
- }
- _firstLoadRequest = 0;
- if (_history->loadedAtTop()
- && _history->isEmpty()
- && count > 0) {
- firstLoadMessages();
- return;
- }
- const auto skipId = (_migrated && _delayedShowAtMsgId < 0)
- ? FullMsgId(_migrated->peer->id, -_delayedShowAtMsgId)
- : (_delayedShowAtMsgId > 0)
- ? FullMsgId(_history->peer->id, _delayedShowAtMsgId)
- : FullMsgId();
- if (skipId) {
- _cornerButtons.skipReplyReturn(skipId);
- }
- _delayedShowAtRequest = 0;
- setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgParams);
- historyLoaded();
- }
- if (session().supportMode()) {
- crl::on_main(this, [=] { checkSupportPreload(); });
- }
- }
- void HistoryWidget::historyLoaded() {
- _historyInited = false;
- doneShow();
- }
- bool HistoryWidget::clearMaybeSendStart() {
- if (!_showAndMaybeSendStart || !_history) {
- return false;
- } else if (!_history->peer->isFullLoaded()) {
- _history->peer->updateFull();
- return false;
- }
- _showAndMaybeSendStart = false;
- if (const auto user = _history ? _history->peer->asUser() : nullptr) {
- if (user->blockStatus() == PeerData::BlockStatus::NotBlocked) {
- if (const auto info = user->botInfo.get()) {
- if (!info->startToken.isEmpty()) {
- return true;
- }
- }
- }
- }
- return false;
- }
- void HistoryWidget::windowShown() {
- updateControlsGeometry();
- }
- bool HistoryWidget::markingMessagesRead() const {
- return markingContentsRead() && !session().supportMode();
- }
- bool HistoryWidget::markingContentsRead() const {
- return _history
- && _list
- && _historyInited
- && !_firstLoadRequest
- && !_delayedShowAtRequest
- && !_showAnimation
- && controller()->widget()->markingAsRead();
- }
- void HistoryWidget::checkActivation() {
- if (_list) {
- _list->checkActivation();
- }
- }
- void HistoryWidget::firstLoadMessages() {
- if (!_history || _firstLoadRequest) {
- return;
- }
- auto from = _history;
- auto offsetId = MsgId();
- auto offset = 0;
- auto loadCount = kMessagesPerPage;
- if (_showAtMsgId == ShowAtUnreadMsgId) {
- if (const auto around = _migrated ? _migrated->loadAroundId() : 0) {
- _history->getReadyFor(_showAtMsgId);
- from = _migrated;
- offset = -loadCount / 2;
- offsetId = around;
- } else if (const auto around = _history->loadAroundId()) {
- _history->getReadyFor(_showAtMsgId);
- offset = -loadCount / 2;
- offsetId = around;
- } else {
- _history->getReadyFor(ShowAtTheEndMsgId);
- }
- } else if (_showAtMsgId == ShowAtTheEndMsgId) {
- _history->getReadyFor(_showAtMsgId);
- loadCount = kMessagesPerPageFirst;
- } else if (_showAtMsgId > 0) {
- _history->getReadyFor(_showAtMsgId);
- offset = -loadCount / 2;
- offsetId = _showAtMsgId;
- } else if (_showAtMsgId < 0 && _history->peer->isChannel()) {
- if (_showAtMsgId < 0 && -_showAtMsgId < ServerMaxMsgId && _migrated) {
- _history->getReadyFor(_showAtMsgId);
- from = _migrated;
- offset = -loadCount / 2;
- offsetId = -_showAtMsgId;
- } else if (_showAtMsgId == SwitchAtTopMsgId) {
- _history->getReadyFor(_showAtMsgId);
- }
- }
- const auto offsetDate = 0;
- const auto maxId = 0;
- const auto minId = 0;
- const auto historyHash = uint64(0);
- const auto history = from;
- const auto type = Data::Histories::RequestType::History;
- auto &histories = history->owner().histories();
- _firstLoadRequest = histories.sendRequest(history, type, [=](
- Fn<void()> finish) {
- return history->session().api().request(MTPmessages_GetHistory(
- history->peer->input,
- MTP_int(offsetId),
- MTP_int(offsetDate),
- MTP_int(offset),
- MTP_int(loadCount),
- MTP_int(maxId),
- MTP_int(minId),
- MTP_long(historyHash)
- )).done([=](const MTPmessages_Messages &result) {
- messagesReceived(history->peer, result, _firstLoadRequest);
- finish();
- }).fail([=](const MTP::Error &error) {
- messagesFailed(error, _firstLoadRequest);
- finish();
- }).send();
- });
- }
- void HistoryWidget::loadMessages() {
- if (!_history || _preloadRequest) {
- return;
- }
- if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
- return firstLoadMessages();
- }
- auto loadMigrated = _migrated
- && (_history->isEmpty()
- || _history->loadedAtTop()
- || (!_migrated->isEmpty() && !_migrated->loadedAtBottom()));
- const auto from = loadMigrated ? _migrated : _history;
- if (from->loadedAtTop()) {
- return;
- }
- const auto offsetId = from->minMsgId();
- const auto addOffset = 0;
- const auto loadCount = offsetId
- ? kMessagesPerPage
- : kMessagesPerPageFirst;
- const auto offsetDate = 0;
- const auto maxId = 0;
- const auto minId = 0;
- const auto historyHash = uint64(0);
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading up before %4."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())
- ).arg(offsetId.bare));
- const auto history = from;
- const auto type = Data::Histories::RequestType::History;
- auto &histories = history->owner().histories();
- _preloadRequest = histories.sendRequest(history, type, [=](
- Fn<void()> finish) {
- return history->session().api().request(MTPmessages_GetHistory(
- history->peer->input,
- MTP_int(offsetId),
- MTP_int(offsetDate),
- MTP_int(addOffset),
- MTP_int(loadCount),
- MTP_int(maxId),
- MTP_int(minId),
- MTP_long(historyHash)
- )).done([=](const MTPmessages_Messages &result) {
- messagesReceived(history->peer, result, _preloadRequest);
- finish();
- }).fail([=](const MTP::Error &error) {
- messagesFailed(error, _preloadRequest);
- finish();
- }).send();
- });
- }
- void HistoryWidget::loadMessagesDown() {
- if (!_history || _preloadDownRequest) {
- return;
- }
- if (_history->isEmpty() && _migrated && _migrated->isEmpty()) {
- return firstLoadMessages();
- }
- const auto loadMigrated = _migrated
- && !(_migrated->isEmpty()
- || _migrated->loadedAtBottom()
- || (!_history->isEmpty() && !_history->loadedAtTop()));
- const auto from = loadMigrated ? _migrated : _history;
- if (from->loadedAtBottom()) {
- if (_sponsoredMessagesStateKnown) {
- session().sponsoredMessages().request(_history, nullptr);
- }
- return;
- }
- const auto loadCount = kMessagesPerPage;
- auto addOffset = -loadCount;
- auto offsetId = from->maxMsgId();
- if (!offsetId) {
- if (loadMigrated || !_migrated) {
- return;
- }
- ++offsetId;
- ++addOffset;
- }
- const auto offsetDate = 0;
- const auto maxId = 0;
- const auto minId = 0;
- const auto historyHash = uint64(0);
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading down after %4."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())
- ).arg(offsetId.bare));
- const auto history = from;
- const auto type = Data::Histories::RequestType::History;
- auto &histories = history->owner().histories();
- _preloadDownRequest = histories.sendRequest(history, type, [=](
- Fn<void()> finish) {
- return history->session().api().request(MTPmessages_GetHistory(
- history->peer->input,
- MTP_int(offsetId + 1),
- MTP_int(offsetDate),
- MTP_int(addOffset),
- MTP_int(loadCount),
- MTP_int(maxId),
- MTP_int(minId),
- MTP_long(historyHash)
- )).done([=](const MTPmessages_Messages &result) {
- messagesReceived(history->peer, result, _preloadDownRequest);
- finish();
- }).fail([=](const MTP::Error &error) {
- messagesFailed(error, _preloadDownRequest);
- finish();
- }).send();
- });
- }
- void HistoryWidget::delayedShowAt(
- MsgId showAtMsgId,
- const Window::SectionShow ¶ms) {
- if (!_history) {
- return;
- }
- _delayedShowAtMsgParams = params;
- if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) {
- return;
- }
- clearAllLoadRequests();
- _delayedShowAtMsgId = showAtMsgId;
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): Loading delayed around %4."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())
- ).arg(showAtMsgId.bare));
- auto from = _history;
- auto offsetId = MsgId();
- auto offset = 0;
- auto loadCount = kMessagesPerPage;
- if (_delayedShowAtMsgId == ShowAtUnreadMsgId) {
- if (const auto around = _migrated ? _migrated->loadAroundId() : 0) {
- from = _migrated;
- offset = -loadCount / 2;
- offsetId = around;
- } else if (const auto around = _history->loadAroundId()) {
- offset = -loadCount / 2;
- offsetId = around;
- } else {
- loadCount = kMessagesPerPageFirst;
- }
- } else if (_delayedShowAtMsgId == ShowAtTheEndMsgId) {
- loadCount = kMessagesPerPageFirst;
- } else if (_delayedShowAtMsgId > 0) {
- offset = -loadCount / 2;
- offsetId = _delayedShowAtMsgId;
- } else if (_delayedShowAtMsgId < 0 && _history->peer->isChannel()) {
- if ((_delayedShowAtMsgId < 0)
- && (-_delayedShowAtMsgId < ServerMaxMsgId)
- && _migrated) {
- from = _migrated;
- offset = -loadCount / 2;
- offsetId = -_delayedShowAtMsgId;
- }
- }
- const auto offsetDate = 0;
- const auto maxId = 0;
- const auto minId = 0;
- const auto historyHash = uint64(0);
- const auto history = from;
- const auto type = Data::Histories::RequestType::History;
- auto &histories = history->owner().histories();
- _delayedShowAtRequest = histories.sendRequest(history, type, [=](
- Fn<void()> finish) {
- return history->session().api().request(MTPmessages_GetHistory(
- history->peer->input,
- MTP_int(offsetId),
- MTP_int(offsetDate),
- MTP_int(offset),
- MTP_int(loadCount),
- MTP_int(maxId),
- MTP_int(minId),
- MTP_long(historyHash)
- )).done([=](const MTPmessages_Messages &result) {
- messagesReceived(history->peer, result, _delayedShowAtRequest);
- finish();
- }).fail([=](const MTP::Error &error) {
- messagesFailed(error, _delayedShowAtRequest);
- finish();
- }).send();
- });
- }
- void HistoryWidget::handleScroll() {
- if (!_itemsRevealHeight) {
- preloadHistoryIfNeeded();
- }
- visibleAreaUpdated();
- if (!_itemsRevealHeight) {
- updatePinnedViewer();
- }
- const auto now = crl::now();
- if (!_synteticScrollEvent) {
- _lastUserScrolled = now;
- }
- const auto scrollTop = _scroll->scrollTop();
- if (scrollTop != _lastScrollTop) {
- if (!_synteticScrollEvent) {
- checkLastPinnedClickedIdReset(_lastScrollTop, scrollTop);
- }
- _lastScrolled = now;
- _lastScrollTop = scrollTop;
- }
- }
- bool HistoryWidget::isItemCompletelyHidden(HistoryItem *item) const {
- const auto view = item ? item->mainView() : nullptr;
- if (!view) {
- return true;
- }
- const auto top = _list ? _list->itemTop(item) : -2;
- if (top < 0) {
- return true;
- }
- const auto bottom = top + view->height();
- const auto scrollTop = _scroll->scrollTop();
- const auto scrollBottom = scrollTop + _scroll->height();
- return (top >= scrollBottom || bottom <= scrollTop);
- }
- void HistoryWidget::visibleAreaUpdated() {
- if (_list && !_scroll->isHidden()) {
- const auto scrollTop = _scroll->scrollTop();
- const auto scrollBottom = scrollTop + _scroll->height();
- _list->visibleAreaUpdated(scrollTop, scrollBottom);
- controller()->floatPlayerAreaUpdated();
- session().data().itemVisibilitiesUpdated();
- }
- }
- void HistoryWidget::preloadHistoryIfNeeded() {
- if (_firstLoadRequest
- || _delayedShowAtRequest
- || _scroll->isHidden()
- || !_peer
- || !_historyInited) {
- return;
- }
- _cornerButtons.updateJumpDownVisibility();
- _cornerButtons.updateUnreadThingsVisibility();
- if (!_scrollToAnimation.animating()) {
- preloadHistoryByScroll();
- checkReplyReturns();
- }
- const auto hasNonEmpty = _history->findFirstNonEmpty();
- const auto readyForBotStart = hasNonEmpty
- || (_history->loadedAtTop() && _history->loadedAtBottom());
- if (readyForBotStart && clearMaybeSendStart() && hasNonEmpty) {
- sendBotStartCommand();
- }
- }
- void HistoryWidget::preloadHistoryByScroll() {
- if (_firstLoadRequest
- || _delayedShowAtRequest
- || _scroll->isHidden()
- || !_peer
- || !_historyInited) {
- return;
- }
- auto scrollTop = _scroll->scrollTop();
- auto scrollTopMax = _scroll->scrollTopMax();
- auto scrollHeight = _scroll->height();
- if (scrollTop + kPreloadHeightsCount * scrollHeight >= scrollTopMax) {
- loadMessagesDown();
- }
- if (scrollTop <= kPreloadHeightsCount * scrollHeight) {
- loadMessages();
- }
- if (session().supportMode()) {
- crl::on_main(this, [=] { checkSupportPreload(); });
- }
- }
- void HistoryWidget::checkSupportPreload(bool force) {
- if (!_history
- || _firstLoadRequest
- || _preloadRequest
- || _preloadDownRequest
- || (_supportPreloadRequest && !force)
- || controller()->activeChatEntryCurrent().key.history() != _history) {
- return;
- }
- const auto setting = session().settings().supportSwitch();
- const auto command = Support::GetSwitchCommand(setting);
- const auto descriptor = !command
- ? Dialogs::RowDescriptor()
- : (*command == Shortcuts::Command::ChatNext)
- ? controller()->resolveChatNext()
- : controller()->resolveChatPrevious();
- auto history = descriptor.key.history();
- if (!history || _supportPreloadHistory == history) {
- return;
- }
- clearSupportPreloadRequest();
- _supportPreloadHistory = history;
- _supportPreloadRequest = Support::SendPreloadRequest(history, [=] {
- _supportPreloadRequest = 0;
- _supportPreloadHistory = nullptr;
- crl::on_main(this, [=] { checkSupportPreload(); });
- });
- }
- void HistoryWidget::checkReplyReturns() {
- if (_firstLoadRequest
- || _scroll->isHidden()
- || !_peer
- || !_historyInited) {
- return;
- }
- auto scrollTop = _scroll->scrollTop();
- auto scrollTopMax = _scroll->scrollTopMax();
- auto scrollHeight = _scroll->height();
- while (const auto replyReturn = _cornerButtons.replyReturn()) {
- auto below = !replyReturn->mainView()
- && (replyReturn->history() == _history)
- && !_history->isEmpty()
- && (replyReturn->id
- < _history->blocks.back()->messages.back()->data()->id);
- if (!below) {
- below = !replyReturn->mainView()
- && (replyReturn->history() == _migrated)
- && !_history->isEmpty();
- }
- if (!below) {
- below = !replyReturn->mainView()
- && _migrated
- && (replyReturn->history() == _migrated)
- && !_migrated->isEmpty()
- && (replyReturn->id
- < _migrated->blocks.back()->messages.back()->data()->id);
- }
- if (!below && replyReturn->mainView()) {
- below = (scrollTop >= scrollTopMax)
- || (_list->itemTop(replyReturn)
- < scrollTop + scrollHeight / 2);
- }
- if (below) {
- _cornerButtons.calculateNextReplyReturn();
- } else {
- break;
- }
- }
- }
- void HistoryWidget::cancelInlineBot() {
- const auto &textWithTags = _field->getTextWithTags();
- if (textWithTags.text.size() > _inlineBotUsername.size() + 2) {
- setFieldText(
- { '@' + _inlineBotUsername + ' ', TextWithTags::Tags() },
- TextUpdateEvent::SaveDraft,
- Ui::InputField::HistoryAction::NewEntry);
- } else {
- clearFieldText(
- TextUpdateEvent::SaveDraft,
- Ui::InputField::HistoryAction::NewEntry);
- }
- }
- void HistoryWidget::windowIsVisibleChanged() {
- InvokeQueued(this, [=] {
- preloadHistoryIfNeeded();
- });
- }
- TextWithEntities HistoryWidget::prepareTextForEditMsg() const {
- const auto textWithTags = _field->getTextWithAppliedMarkdown();
- const auto prepareFlags = Ui::ItemTextOptions(
- _history,
- session().user()).flags;
- auto left = TextWithEntities {
- textWithTags.text,
- TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) };
- TextUtilities::PrepareForSending(left, prepareFlags);
- return left;
- }
- void HistoryWidget::saveEditMsg() {
- Expects(_history != nullptr);
- if (_saveEditMsgRequestId) {
- return;
- }
- const auto item = session().data().message(_history->peer, _editMsgId);
- if (!item) {
- cancelEdit();
- return;
- }
- const auto webPageDraft = _preview->draft();
- const auto sending = prepareTextForEditMsg();
- const auto hasMediaWithCaption = item
- && item->media()
- && item->media()->allowsEditCaption();
- if (sending.text.isEmpty()
- && (webPageDraft.removed
- || webPageDraft.url.isEmpty()
- || !webPageDraft.manual)
- && !hasMediaWithCaption) {
- const auto suggestModerateActions = false;
- controller()->show(
- Box<DeleteMessagesBox>(item, suggestModerateActions));
- return;
- } else {
- const auto maxCaptionSize = !hasMediaWithCaption
- ? MaxMessageSize
- : Data::PremiumLimits(&session()).captionLengthCurrent();
- const auto remove = _fieldCharsCountManager.count() - maxCaptionSize;
- if (remove > 0) {
- controller()->showToast(
- tr::lng_edit_limit_reached(tr::now, lt_count, remove));
- #ifndef _DEBUG
- return;
- #else
- if (!base::IsCtrlPressed()) {
- return;
- }
- #endif
- }
- }
- const auto weak = Ui::MakeWeak(this);
- const auto history = _history;
- const auto done = [=](mtpRequestId requestId) {
- crl::guard(weak, [=] {
- if (requestId == _saveEditMsgRequestId) {
- _saveEditMsgRequestId = 0;
- cancelEdit();
- }
- })();
- if (const auto editDraft = history->localEditDraft({})) {
- if (editDraft->saveRequestId == requestId) {
- history->clearLocalEditDraft({});
- history->session().local().writeDrafts(history);
- }
- }
- };
- const auto fail = [=](const QString &error, mtpRequestId requestId) {
- if (const auto editDraft = history->localEditDraft({})) {
- if (editDraft->saveRequestId == requestId) {
- editDraft->saveRequestId = 0;
- }
- }
- crl::guard(weak, [=] {
- if (requestId == _saveEditMsgRequestId) {
- _saveEditMsgRequestId = 0;
- }
- if (ranges::contains(Api::kDefaultEditMessagesErrors, error)) {
- controller()->showToast(tr::lng_edit_error(tr::now));
- } else if (error == u"MESSAGE_NOT_MODIFIED"_q) {
- cancelEdit();
- } else if (error == u"MESSAGE_EMPTY"_q) {
- _field->selectAll();
- setInnerFocus();
- } else {
- controller()->showToast(tr::lng_edit_error(tr::now));
- }
- update();
- })();
- };
- _saveEditMsgRequestId = Api::EditTextMessage(
- item,
- sending,
- webPageDraft,
- { .invertCaption = _mediaEditManager.invertCaption() },
- done,
- fail,
- _mediaEditManager.spoilered());
- }
- void HistoryWidget::hideChildWidgets() {
- if (Ui::InFocusChain(this)) {
- // Removing focus from list clears selected and updates top bar.
- setFocus();
- }
- if (_tabbedPanel) {
- _tabbedPanel->hideFast();
- }
- if (_pinnedBar) {
- _pinnedBar->hide();
- }
- if (_sponsoredMessageBar) {
- _sponsoredMessageBar->toggle(false, anim::type::instant);
- }
- if (_translateBar) {
- _translateBar->hide();
- }
- if (_groupCallBar) {
- _groupCallBar->hide();
- }
- if (_requestsBar) {
- _requestsBar->hide();
- }
- if (_voiceRecordBar) {
- _voiceRecordBar->hideFast();
- }
- if (_composeSearch) {
- _composeSearch->hideAnimated();
- }
- if (_chooseTheme) {
- _chooseTheme->hide();
- }
- if (_paysStatus) {
- _paysStatus->hide();
- }
- if (_contactStatus) {
- _contactStatus->hide();
- }
- if (_businessBotStatus) {
- _businessBotStatus->hide();
- }
- hideChildren();
- }
- void HistoryWidget::hideSelectorControlsAnimated() {
- if (_autocomplete) {
- _autocomplete->hideAnimated();
- }
- if (_supportAutocomplete) {
- _supportAutocomplete->hide();
- }
- if (_tabbedPanel) {
- _tabbedPanel->hideAnimated();
- }
- if (_inlineResults) {
- _inlineResults->hideAnimated();
- }
- }
- Api::SendAction HistoryWidget::prepareSendAction(
- Api::SendOptions options) const {
- auto result = Api::SendAction(_history, options);
- result.replyTo = replyTo();
- result.options.sendAs = _sendAs
- ? _history->session().sendAsPeers().resolveChosen(
- _history->peer).get()
- : nullptr;
- return result;
- }
- void HistoryWidget::sendVoice(const VoiceToSend &data) {
- if (!canWriteMessage() || data.bytes.isEmpty() || !_history) {
- return;
- }
- const auto withPaymentApproved = [=](int approved) {
- auto copy = data;
- copy.options.starsApproved = approved;
- sendVoice(copy);
- };
- const auto checked = checkSendPayment(
- 1 + int(_forwardPanel->items().size()),
- data.options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return;
- }
- auto action = prepareSendAction(data.options);
- session().api().sendVoiceMessage(
- data.bytes,
- data.waveform,
- data.duration,
- data.video,
- action);
- _voiceRecordBar->clearListenState();
- }
- void HistoryWidget::send(Api::SendOptions options) {
- if (!_history) {
- return;
- } else if (_editMsgId) {
- saveEditMsg();
- return;
- } else if (!options.scheduled && showSlowmodeError()) {
- return;
- } else if (_voiceRecordBar->isListenState()) {
- _voiceRecordBar->requestToSendWithOptions(options);
- return;
- }
- if (!options.scheduled) {
- _cornerButtons.clearReplyReturns();
- }
- auto message = Api::MessageToSend(prepareSendAction(options));
- message.textWithTags = _field->getTextWithAppliedMarkdown();
- message.webPage = _preview->draft();
- const auto ignoreSlowmodeCountdown = (options.scheduled != 0);
- const auto withPaymentApproved = [=](int approved) {
- auto copy = options;
- copy.starsApproved = approved;
- send(copy);
- };
- if (showSendMessageError(
- message.textWithTags,
- ignoreSlowmodeCountdown,
- withPaymentApproved,
- options.starsApproved)) {
- return;
- }
- // Just a flag not to drop reply info if we're not sending anything.
- _justMarkingAsRead = !HasSendText(_field)
- && message.webPage.url.isEmpty();
- session().api().sendMessage(std::move(message));
- _justMarkingAsRead = false;
- clearFieldText();
- if (_preview) {
- _preview->apply({ .removed = true });
- }
- _saveDraftText = true;
- _saveDraftStart = crl::now();
- saveDraft();
- hideSelectorControlsAnimated();
- setInnerFocus();
- if (!_keyboard->hasMarkup() && _keyboard->forceReply() && !_kbReplyTo) {
- toggleKeyboard();
- }
- session().changes().historyUpdated(
- _history,
- (options.scheduled
- ? Data::HistoryUpdate::Flag::ScheduledSent
- : Data::HistoryUpdate::Flag::MessageSent));
- }
- void HistoryWidget::sendWithModifiers(Qt::KeyboardModifiers modifiers) {
- send({ .handleSupportSwitch = Support::HandleSwitch(modifiers) });
- }
- void HistoryWidget::sendScheduled(Api::SendOptions initialOptions) {
- if (!_list) {
- return;
- }
- const auto ignoreSlowmodeCountdown = true;
- if (showSendMessageError(
- _field->getTextWithAppliedMarkdown(),
- ignoreSlowmodeCountdown)) {
- return;
- }
- controller()->show(
- HistoryView::PrepareScheduleBox(
- _list,
- controller()->uiShow(),
- sendButtonDefaultDetails(),
- [=](Api::SendOptions options) { send(options); },
- initialOptions));
- }
- SendMenu::Details HistoryWidget::sendMenuDetails() const {
- const auto type = !_peer
- ? SendMenu::Type::Disabled
- : _peer->starsPerMessageChecked()
- ? SendMenu::Type::SilentOnly
- : _peer->isSelf()
- ? SendMenu::Type::Reminder
- : HistoryView::CanScheduleUntilOnline(_peer)
- ? SendMenu::Type::ScheduledToUser
- : SendMenu::Type::Scheduled;
- const auto effectAllowed = _peer && _peer->isUser();
- return { .type = type, .effectAllowed = effectAllowed };
- }
- SendMenu::Details HistoryWidget::saveMenuDetails() const {
- return (_editMsgId && _replyEditMsg)
- ? _mediaEditManager.sendMenuDetails(HasSendText(_field))
- : SendMenu::Details();
- }
- auto HistoryWidget::computeSendButtonType() const {
- using Type = Ui::SendButton::Type;
- if (_editMsgId) {
- return Type::Save;
- } else if (_isInlineBot) {
- return Type::Cancel;
- } else if (showRecordButton()) {
- const auto both = Webrtc::RecordAvailability::VideoAndAudio;
- const auto video = Core::App().settings().recordVideoMessages();
- return (video && _recordAvailability == both)
- ? Type::Round
- : Type::Record;
- }
- return Type::Send;
- }
- SendMenu::Details HistoryWidget::sendButtonMenuDetails() const {
- using Type = Ui::SendButton::Type;
- const auto type = computeSendButtonType();
- if (type == Type::Save) {
- return saveMenuDetails();
- } else if (type != Type::Send) {
- return {};
- }
- return sendButtonDefaultDetails();
- }
- SendMenu::Details HistoryWidget::sendButtonDefaultDetails() const {
- auto result = sendMenuDetails();
- if (!HasSendText(_field) && !_previewDrawPreview) {
- result.effectAllowed = false;
- }
- return result;
- }
- void HistoryWidget::unblockUser() {
- if (const auto user = _peer ? _peer->asUser() : nullptr) {
- const auto show = controller()->uiShow();
- Window::PeerMenuUnblockUserWithBotRestart(show, user);
- } else {
- updateControlsVisibility();
- }
- }
- void HistoryWidget::sendBotStartCommand() {
- if (!_peer
- || !_peer->isUser()
- || !_peer->asUser()->isBot()
- || !_canSendMessages) {
- updateControlsVisibility();
- return;
- }
- session().api().sendBotStart(controller()->uiShow(), _peer->asUser());
- updateControlsVisibility();
- updateControlsGeometry();
- }
- void HistoryWidget::joinChannel() {
- if (!_peer || !_peer->isChannel() || !isJoinChannel()) {
- updateControlsVisibility();
- return;
- }
- session().api().joinChannel(_peer->asChannel());
- }
- void HistoryWidget::toggleMuteUnmute() {
- const auto wasMuted = _history->muted();
- const auto muteForSeconds = Data::MuteValue{
- .unmute = wasMuted,
- .forever = !wasMuted,
- };
- session().data().notifySettings().update(_peer, muteForSeconds);
- }
- void HistoryWidget::reportSelectedMessages() {
- if (!_list || !_chooseForReport || !_list->getSelectionState().count) {
- return;
- }
- const auto ids = _list->getSelectedItems();
- const auto done = _chooseForReport->callback;
- clearSelected();
- controller()->clearChooseReportMessages();
- if (done) {
- done(ranges::views::all(
- ids
- ) | ranges::views::transform(&FullMsgId::msg) | ranges::to_vector);
- }
- }
- History *HistoryWidget::history() const {
- return _history;
- }
- PeerData *HistoryWidget::peer() const {
- return _peer;
- }
- // Sometimes _showAtMsgId is set directly.
- void HistoryWidget::setMsgId(
- MsgId showAtMsgId,
- const Window::SectionShow ¶ms) {
- _showAtMsgParams = params;
- if (_showAtMsgId != showAtMsgId) {
- _showAtMsgId = showAtMsgId;
- if (_history) {
- controller()->setActiveChatEntry({
- _history,
- FullMsgId(_history->peer->id, _showAtMsgId) });
- }
- }
- }
- MsgId HistoryWidget::msgId() const {
- return _showAtMsgId;
- }
- void HistoryWidget::showAnimated(
- Window::SlideDirection direction,
- const Window::SectionSlideParams ¶ms) {
- _showAnimation = nullptr;
- // If we show pinned bar here, we don't want it to change the
- // calculated and prepared scrollTop of the messages history.
- _preserveScrollTop = true;
- show();
- _topBar->finishAnimating();
- _cornerButtons.finishAnimations();
- if (_pinnedBar) {
- _pinnedBar->finishAnimating();
- }
- if (_translateBar) {
- _translateBar->finishAnimating();
- }
- if (_groupCallBar) {
- _groupCallBar->finishAnimating();
- }
- if (_requestsBar) {
- _requestsBar->finishAnimating();
- }
- _topShadow->setVisible(params.withTopBarShadow ? false : true);
- _preserveScrollTop = false;
- _stickerToast = nullptr;
- auto newContentCache = Ui::GrabWidget(this);
- hideChildWidgets();
- if (params.withTopBarShadow) _topShadow->show();
- if (_history) {
- _topBar->show();
- _topBar->setAnimatingMode(true);
- }
- _showAnimation = std::make_unique<Window::SlideAnimation>();
- _showAnimation->setDirection(direction);
- _showAnimation->setRepaintCallback([=] { update(); });
- _showAnimation->setFinishedCallback([=] { showFinished(); });
- _showAnimation->setPixmaps(params.oldContentCache, newContentCache);
- _showAnimation->start();
- activate();
- }
- void HistoryWidget::showFinished() {
- _cornerButtons.finishAnimations();
- if (_pinnedBar) {
- _pinnedBar->finishAnimating();
- }
- if (_translateBar) {
- _translateBar->finishAnimating();
- }
- if (_groupCallBar) {
- _groupCallBar->finishAnimating();
- }
- if (_requestsBar) {
- _requestsBar->finishAnimating();
- }
- _showAnimation = nullptr;
- doneShow();
- synteticScrollToY(_scroll->scrollTop());
- }
- void HistoryWidget::doneShow() {
- _topBar->setAnimatingMode(false);
- updateCanSendMessage();
- updateBotKeyboard();
- updateControlsVisibility();
- if (!_historyInited) {
- updateHistoryGeometry(true);
- } else {
- handlePendingHistoryUpdate();
- }
- // If we show pinned bar here, we don't want it to change the
- // calculated and prepared scrollTop of the messages history.
- _preserveScrollTop = true;
- preloadHistoryIfNeeded();
- updatePinnedViewer();
- if (_pinnedBar) {
- _pinnedBar->finishAnimating();
- }
- checkSponsoredMessageBar();
- if (_sponsoredMessageBar) {
- _sponsoredMessageBar->finishAnimating();
- }
- if (_translateBar) {
- _translateBar->finishAnimating();
- }
- if (_groupCallBar) {
- _groupCallBar->finishAnimating();
- }
- if (_requestsBar) {
- _requestsBar->finishAnimating();
- }
- checkActivation();
- controller()->widget()->setInnerFocus();
- _preserveScrollTop = false;
- checkSuggestToGigagroup();
- }
- void HistoryWidget::cornerButtonsShowAtPosition(
- Data::MessagePosition position) {
- if (position == Data::UnreadMessagePosition) {
- DEBUG_LOG(("JumpToEnd(%1, %2, %3): Show at unread requested."
- ).arg(_history->peer->name()
- ).arg(_history->inboxReadTillId().bare
- ).arg(Logs::b(_history->loadedAtBottom())));
- showHistory(_peer->id, ShowAtUnreadMsgId);
- } else if (_peer && position.fullId.peer == _peer->id) {
- showHistory(_peer->id, position.fullId.msg);
- } else if (_migrated && position.fullId.peer == _migrated->peer->id) {
- showHistory(_peer->id, -position.fullId.msg);
- }
- }
- Data::Thread *HistoryWidget::cornerButtonsThread() {
- return _history;
- }
- FullMsgId HistoryWidget::cornerButtonsCurrentId() {
- return (_migrated && _showAtMsgId < 0)
- ? FullMsgId(_migrated->peer->id, -_showAtMsgId)
- : (_history && _showAtMsgId > 0)
- ? FullMsgId(_history->peer->id, _showAtMsgId)
- : FullMsgId();
- }
- bool HistoryWidget::checkSendPayment(
- int messagesCount,
- int starsApproved,
- Fn<void(int)> withPaymentApproved) {
- return _peer
- && _sendPayment.check(
- controller(),
- _peer,
- messagesCount,
- starsApproved,
- std::move(withPaymentApproved));
- }
- void HistoryWidget::checkSuggestToGigagroup() {
- const auto group = _peer ? _peer->asMegagroup() : nullptr;
- if (!group || !group->owner().suggestToGigagroup(group)) {
- return;
- }
- InvokeQueued(_list, [=] {
- if (!controller()->isLayerShown()) {
- group->owner().setSuggestToGigagroup(group, false);
- group->session().api().request(MTPhelp_DismissSuggestion(
- group->input,
- MTP_string("convert_to_gigagroup")
- )).send();
- controller()->show(Box([=](not_null<Ui::GenericBox*> box) {
- box->setTitle(tr::lng_gigagroup_suggest_title());
- box->addRow(
- object_ptr<Ui::FlatLabel>(
- box,
- tr::lng_gigagroup_suggest_text(
- ) | Ui::Text::ToRichLangValue(),
- st::infoAboutGigagroup));
- box->addButton(
- tr::lng_gigagroup_suggest_more(),
- AboutGigagroupCallback(group, controller()));
- box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
- }));
- }
- });
- }
- void HistoryWidget::finishAnimating() {
- if (!_showAnimation) {
- return;
- }
- _showAnimation = nullptr;
- _topShadow->setVisible(_peer != nullptr);
- _topBar->setVisible(_peer != nullptr);
- _cornerButtons.finishAnimations();
- }
- void HistoryWidget::chooseAttach(
- std::optional<bool> overrideSendImagesAsPhotos) {
- if (_editMsgId) {
- controller()->showToast(tr::lng_edit_caption_attach(tr::now));
- return;
- }
- if (!_peer || !_canSendMessages) {
- return;
- } else if (const auto error = Data::AnyFileRestrictionError(_peer)) {
- Data::ShowSendErrorToast(controller(), _peer, error);
- return;
- } else if (showSlowmodeError()) {
- return;
- }
- const auto filter = (overrideSendImagesAsPhotos == true)
- ? FileDialog::PhotoVideoFilesFilter()
- : FileDialog::AllOrImagesFilter();
- const auto callbackOnResult = crl::guard(this, [=](
- FileDialog::OpenResult &&result) {
- if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
- return;
- }
- if (!result.remoteContent.isEmpty()) {
- auto read = Images::Read({
- .content = result.remoteContent,
- });
- if (!read.image.isNull() && !read.animated) {
- confirmSendingFiles(
- std::move(read.image),
- std::move(result.remoteContent),
- overrideSendImagesAsPhotos);
- } else {
- uploadFile(result.remoteContent, SendMediaType::File);
- }
- } else {
- const auto premium = controller()->session().user()->isPremium();
- auto list = Storage::PrepareMediaList(
- result.paths,
- st::sendMediaPreviewSize,
- premium);
- list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
- confirmSendingFiles(std::move(list));
- }
- });
- FileDialog::GetOpenPaths(
- this,
- tr::lng_choose_files(tr::now),
- filter,
- callbackOnResult,
- nullptr);
- }
- void HistoryWidget::sendButtonClicked() {
- const auto type = _send->type();
- if (type == Ui::SendButton::Type::Cancel) {
- cancelInlineBot();
- } else if (type != Ui::SendButton::Type::Record
- && type != Ui::SendButton::Type::Round) {
- send({});
- }
- }
- void HistoryWidget::leaveEventHook(QEvent *e) {
- if (hasMouseTracking()) {
- mouseMoveEvent(nullptr);
- }
- }
- void HistoryWidget::mouseMoveEvent(QMouseEvent *e) {
- const auto pos = e ? e->pos() : mapFromGlobal(QCursor::pos());
- updateOverStates(pos);
- }
- void HistoryWidget::updateOverStates(QPoint pos) {
- const auto isReadyToForward = readyToForward();
- const auto detailsRect = QRect(
- 0,
- _field->y() - st::historySendPadding - st::historyReplyHeight,
- width() - _fieldBarCancel->width(),
- st::historyReplyHeight);
- const auto hasWebPage = !!_previewDrawPreview;
- const auto inDetails = detailsRect.contains(pos)
- && (_editMsgId || replyTo() || isReadyToForward || hasWebPage);
- const auto inPhotoEdit = inDetails
- && _photoEditMedia
- && QRect(
- detailsRect.x() + st::historyReplySkip,
- (detailsRect.y()
- + (detailsRect.height() - st::historyReplyPreview) / 2),
- st::historyReplyPreview,
- st::historyReplyPreview).contains(pos);
- const auto inClickable = inDetails;
- if (_inPhotoEdit != inPhotoEdit) {
- _inPhotoEdit = inPhotoEdit;
- if (_photoEditMedia) {
- _inPhotoEditOver.start(
- [=] { updateField(); },
- _inPhotoEdit ? 0. : 1.,
- _inPhotoEdit ? 1. : 0.,
- st::defaultMessageBar.duration);
- } else {
- _inPhotoEditOver.stop();
- }
- }
- _inDetails = inDetails && !inPhotoEdit;
- if (inClickable != _inClickable) {
- _inClickable = inClickable;
- setCursor(_inClickable ? style::cur_pointer : style::cur_default);
- }
- }
- void HistoryWidget::leaveToChildEvent(QEvent *e, QWidget *child) {
- // e -- from enterEvent() of child TWidget
- if (hasMouseTracking()) {
- updateOverStates(mapFromGlobal(QCursor::pos()));
- }
- }
- void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) {
- }
- void HistoryWidget::sendBotCommand(const Bot::SendCommandRequest &request) {
- sendBotCommand(request, {});
- }
- void HistoryWidget::sendBotCommand(
- const Bot::SendCommandRequest &request,
- Api::SendOptions options) {
- // replyTo != 0 from ReplyKeyboardMarkup, == 0 from command links
- if (_peer != request.peer.get()) {
- return;
- } else if (showSlowmodeError()) {
- return;
- }
- const auto withPaymentApproved = [=](int approved) {
- auto copy = options;
- copy.starsApproved = approved;
- sendBotCommand(request, copy);
- };
- const auto checked = checkSendPayment(
- 1,
- options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return;
- }
- const auto forMsgId = _keyboard->forMsgId();
- const auto lastKeyboardUsed = (forMsgId == request.replyTo.messageId)
- && (forMsgId == FullMsgId(_peer->id, _history->lastKeyboardId));
- // 'bot' may be nullptr in case of sending from FieldAutocomplete.
- const auto toSend = (request.replyTo/* || !bot*/)
- ? request.command
- : Bot::WrapCommandInChat(_peer, request.command, request.context);
- auto message = Api::MessageToSend(prepareSendAction(options));
- message.textWithTags = { toSend, TextWithTags::Tags() };
- message.action.replyTo = request.replyTo
- ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/)
- ? request.replyTo
- : replyTo())
- : FullReplyTo();
- session().api().sendMessage(std::move(message));
- if (request.replyTo) {
- if (_replyTo == request.replyTo) {
- cancelReply();
- saveCloudDraft();
- }
- if (_keyboard->singleUse()
- && _keyboard->hasMarkup()
- && lastKeyboardUsed) {
- if (_kbShown) {
- toggleKeyboard(false);
- }
- _history->lastKeyboardUsed = true;
- }
- }
- setInnerFocus();
- }
- void HistoryWidget::hideSingleUseKeyboard(FullMsgId replyToId) {
- if (!_peer || _peer->id != replyToId.peer) {
- return;
- }
- const auto lastKeyboardUsed = (_keyboard->forMsgId() == replyToId)
- && (_keyboard->forMsgId()
- == FullMsgId(_peer->id, _history->lastKeyboardId));
- if (replyToId) {
- if (_replyTo.messageId == replyToId) {
- cancelReply();
- saveCloudDraft();
- }
- if (_keyboard->singleUse()
- && _keyboard->hasMarkup()
- && lastKeyboardUsed) {
- if (_kbShown) {
- toggleKeyboard(false);
- }
- _history->lastKeyboardUsed = true;
- }
- }
- }
- bool HistoryWidget::insertBotCommand(const QString &cmd) {
- if (!_canSendTexts) {
- return false;
- }
- const auto insertingInlineBot = !cmd.isEmpty() && (cmd.at(0) == '@');
- auto toInsert = cmd;
- if (!toInsert.isEmpty() && !insertingInlineBot) {
- auto bot = (PeerData*)(_peer->asUser());
- if (!bot) {
- if (const auto link = HistoryView::Element::HoveredLink()) {
- bot = link->data()->fromOriginal().get();
- }
- }
- if (bot && (!bot->isUser() || !bot->asUser()->isBot())) {
- bot = nullptr;
- }
- const auto username = bot ? bot->asUser()->username() : QString();
- const auto botStatus = _peer->isChat()
- ? _peer->asChat()->botStatus
- : _peer->isMegagroup()
- ? _peer->asChannel()->mgInfo->botStatus
- : -1;
- if ((toInsert.indexOf('@') < 0)
- && !username.isEmpty()
- && (botStatus == 0 || botStatus == 2)) {
- toInsert += '@' + username;
- }
- }
- toInsert += ' ';
- if (!insertingInlineBot) {
- auto &textWithTags = _field->getTextWithTags();
- auto textWithTagsToSet = TextWithTags();
- const auto m = QRegularExpression(
- u"^/[A-Za-z_0-9]{0,64}(@[A-Za-z_0-9]{0,32})?(\\s|$)"_q).match(
- textWithTags.text);
- textWithTagsToSet = m.hasMatch()
- ? _field->getTextWithTagsPart(m.capturedLength())
- : textWithTags;
- textWithTagsToSet.text = toInsert + textWithTagsToSet.text;
- for (auto &tag : textWithTagsToSet.tags) {
- tag.offset += toInsert.size();
- }
- _field->setTextWithTags(textWithTagsToSet);
- auto cur = QTextCursor(_field->textCursor());
- cur.movePosition(QTextCursor::End);
- _field->setTextCursor(cur);
- } else {
- setFieldText(
- { toInsert, TextWithTags::Tags() },
- TextUpdateEvent::SaveDraft,
- Ui::InputField::HistoryAction::NewEntry);
- setInnerFocus();
- return true;
- }
- return false;
- }
- bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) {
- if (e->type() == QEvent::KeyPress) {
- const auto k = static_cast<QKeyEvent*>(e);
- if ((k->modifiers() & kCommonModifiers) == Qt::ControlModifier) {
- if (k->key() == Qt::Key_Up) {
- #ifdef Q_OS_MAC
- // Cmd + Up is used instead of Home.
- if (HasSendText(_field)) {
- return false;
- }
- #endif
- return replyToPreviousMessage();
- } else if (k->key() == Qt::Key_Down) {
- #ifdef Q_OS_MAC
- // Cmd + Down is used instead of End.
- if (HasSendText(_field)) {
- return false;
- }
- #endif
- return replyToNextMessage();
- }
- }
- }
- return TWidget::eventFilter(obj, e);
- }
- bool HistoryWidget::floatPlayerHandleWheelEvent(QEvent *e) {
- return _peer ? _scroll->viewportEvent(e) : false;
- }
- QRect HistoryWidget::floatPlayerAvailableRect() {
- return _peer ? mapToGlobal(_scroll->geometry()) : mapToGlobal(rect());
- }
- bool HistoryWidget::readyToForward() const {
- return _canSendMessages && !_forwardPanel->empty();
- }
- bool HistoryWidget::hasSilentToggle() const {
- return _peer
- && _peer->isBroadcast()
- && Data::CanSendAnything(_peer)
- && !session().data().notifySettings().silentPostsUnknown(_peer);
- }
- void HistoryWidget::handleSupportSwitch(not_null<History*> updated) {
- if (_history != updated || !session().supportMode()) {
- return;
- }
- const auto setting = session().settings().supportSwitch();
- if (auto method = Support::GetSwitchMethod(setting)) {
- crl::on_main(this, std::move(method));
- }
- }
- bool HistoryWidget::isBotStart() const {
- const auto user = _peer ? _peer->asUser() : nullptr;
- if (!user
- || !user->isBot()
- || !_canSendMessages) {
- return false;
- } else if (!user->botInfo->startToken.isEmpty()) {
- return true;
- } else if (_history->isEmpty() && !_history->lastMessage()) {
- return true;
- }
- return false;
- }
- bool HistoryWidget::isReportMessages() const {
- return _peer && _chooseForReport && _chooseForReport->active;
- }
- bool HistoryWidget::isBlocked() const {
- return _peer && _peer->isUser() && _peer->asUser()->isBlocked();
- }
- bool HistoryWidget::isJoinChannel() const {
- return _peer && _peer->isChannel() && !_peer->asChannel()->amIn();
- }
- bool HistoryWidget::isChoosingTheme() const {
- return _chooseTheme && _chooseTheme->shouldBeShown();
- }
- bool HistoryWidget::isMuteUnmute() const {
- return _peer
- && ((_peer->isBroadcast() && !_peer->asChannel()->canPostMessages())
- || (_peer->isGigagroup() && !Data::CanSendAnything(_peer))
- || _peer->isRepliesChat()
- || _peer->isVerifyCodes());
- }
- bool HistoryWidget::isSearching() const {
- return _composeSearch != nullptr;
- }
- bool HistoryWidget::showRecordButton() const {
- return (_recordAvailability != Webrtc::RecordAvailability::None)
- && !_voiceRecordBar->isListenState()
- && !_voiceRecordBar->isRecordingByAnotherBar()
- && !HasSendText(_field)
- && !_previewDrawPreview
- && (_replyTo || !readyToForward())
- && !_editMsgId;
- }
- bool HistoryWidget::showInlineBotCancel() const {
- return _inlineBot && !_inlineLookingUpBot;
- }
- void HistoryWidget::updateSendButtonType() {
- using Type = Ui::SendButton::Type;
- const auto type = computeSendButtonType();
- // This logic is duplicated in RepliesWidget.
- const auto disabledBySlowmode = _peer
- && _peer->slowmodeApplied()
- && (_history->latestSendingMessage() != nullptr);
- const auto delay = [&] {
- return (type != Type::Cancel && type != Type::Save && _peer)
- ? _peer->slowmodeSecondsLeft()
- : 0;
- }();
- const auto perMessage = _peer ? _peer->starsPerMessageChecked() : 0;
- const auto messages = !_peer
- ? 0
- : _voiceRecordBar->isListenState()
- ? 1
- : ComputeSendingMessagesCount(_history, {
- .forward = &_forwardPanel->items(),
- .text = &_field->getTextWithTags(),
- });
- const auto stars = perMessage ? (perMessage * messages) : 0;
- _send->setState({
- .type = (delay > 0) ? Type::Slowmode : type,
- .slowmodeDelay = delay,
- .starsToSend = stars,
- });
- _send->setDisabled(disabledBySlowmode
- && (type == Type::Send
- || type == Type::Record
- || type == Type::Round));
- if (delay != 0) {
- base::call_delayed(
- kRefreshSlowmodeLabelTimeout,
- this,
- [=] { updateSendButtonType(); });
- }
- }
- bool HistoryWidget::updateCmdStartShown() {
- const auto bot = (_peer && _peer->isUser() && _peer->asUser()->isBot())
- ? _peer->asUser()
- : nullptr;
- auto cmdStartShown = false;
- if (_history
- && _peer
- && (false
- || (_peer->isChat() && _peer->asChat()->botStatus > 0)
- || (_peer->isMegagroup()
- && _peer->asChannel()->mgInfo->botStatus > 0))) {
- if (!isBotStart()
- && !isBlocked()
- && !_keyboard->hasMarkup()
- && !_keyboard->forceReply()
- && !_editMsgId) {
- if (!HasSendText(_field)) {
- cmdStartShown = true;
- }
- }
- }
- constexpr auto kSmallMenuAfter = 10;
- const auto commandsChanged = (_cmdStartShown != cmdStartShown);
- auto buttonChanged = false;
- if (!bot
- || (bot->botInfo->botMenuButtonUrl.isEmpty()
- && bot->botInfo->commands.empty())) {
- buttonChanged = (_botMenu.button != nullptr);
- _botMenu.button.destroy();
- } else if (!_botMenu.button) {
- buttonChanged = true;
- _botMenu.text = bot->botInfo->botMenuButtonText;
- _botMenu.small = (_fieldCharsCountManager.count() > kSmallMenuAfter);
- if (_botMenu.small) {
- if (const auto e = FirstEmoji(_botMenu.text); !e.isEmpty()) {
- _botMenu.text = e;
- }
- }
- _botMenu.button.create(
- this,
- (_botMenu.text.isEmpty()
- ? tr::lng_bot_menu_button()
- : rpl::single(_botMenu.text)),
- st::historyBotMenuButton);
- orderWidgets();
- _botMenu.button->setTextTransform(
- Ui::RoundButton::TextTransform::NoTransform);
- _botMenu.button->setFullRadius(true);
- _botMenu.button->setClickedCallback([=] {
- const auto user = _peer ? _peer->asUser() : nullptr;
- const auto bot = (user && user->isBot()) ? user : nullptr;
- if (bot && !bot->botInfo->botMenuButtonUrl.isEmpty()) {
- session().attachWebView().open({
- .bot = bot,
- .context = { .controller = controller() },
- .button = {
- .url = bot->botInfo->botMenuButtonUrl.toUtf8(),
- },
- .source = InlineBots::WebViewSourceBotMenu(),
- });
- } else if (_autocomplete && !_autocomplete->isHidden()) {
- _autocomplete->hideAnimated();
- } else if (_autocomplete) {
- _autocomplete->showFiltered(_peer, "/", true);
- }
- });
- _botMenu.button->widthValue(
- ) | rpl::start_with_next([=](int width) {
- if (width > st::historyBotMenuMaxWidth) {
- _botMenu.button->setFullWidth(st::historyBotMenuMaxWidth);
- } else {
- updateFieldSize();
- }
- }, _botMenu.button->lifetime());
- }
- const auto textSmall = _fieldCharsCountManager.count() > kSmallMenuAfter;
- const auto textChanged = _botMenu.button
- && ((_botMenu.text != bot->botInfo->botMenuButtonText)
- || (_botMenu.small != textSmall));
- if (textChanged) {
- _botMenu.text = bot->botInfo->botMenuButtonText;
- if ((_botMenu.small = textSmall)) {
- if (const auto e = FirstEmoji(_botMenu.text); !e.isEmpty()) {
- _botMenu.text = e;
- }
- }
- _botMenu.button->setText(_botMenu.text.isEmpty()
- ? tr::lng_bot_menu_button()
- : rpl::single(_botMenu.text));
- }
- _cmdStartShown = cmdStartShown;
- return commandsChanged || buttonChanged || textChanged;
- }
- bool HistoryWidget::searchInChatEmbedded(
- QString query,
- Dialogs::Key chat,
- PeerData *searchFrom) {
- const auto peer = chat.peer(); // windows todo
- if (!peer || Window::SeparateId(peer) != controller()->windowId()) {
- return false;
- } else if (_peer != peer) {
- const auto weak = Ui::MakeWeak(this);
- controller()->showPeerHistory(peer);
- if (!weak) {
- return false;
- }
- }
- if (_peer != peer) {
- return false;
- } else if (_composeSearch) {
- _composeSearch->setQuery(query);
- _composeSearch->setInnerFocus();
- return true;
- }
- switchToSearch(query);
- return true;
- }
- void HistoryWidget::switchToSearch(QString query) {
- const auto search = crl::guard(_list, [=] {
- if (!_peer) {
- return;
- }
- const auto update = [=] {
- updateControlsVisibility();
- updateBotKeyboard();
- updateFieldPlaceholder();
- updateControlsGeometry();
- };
- const auto from = (PeerData*)nullptr;
- _composeSearch = std::make_unique<HistoryView::ComposeSearch>(
- this,
- controller(),
- _history,
- from,
- query);
- update();
- setInnerFocus();
- using Activation = HistoryView::ComposeSearch::Activation;
- _composeSearch->activations(
- ) | rpl::start_with_next([=](Activation activation) {
- const auto item = activation.item;
- auto params = ::Window::SectionShow(
- ::Window::SectionShow::Way::ClearStack);
- params.highlightPart = { activation.query };
- params.highlightPartOffsetHint = kSearchQueryOffsetHint;
- controller()->showPeerHistory(
- item->history()->peer->id,
- params,
- item->fullId().msg);
- }, _composeSearch->lifetime());
- _composeSearch->destroyRequests(
- ) | rpl::take(1) | rpl::start_with_next([=] {
- _composeSearch = nullptr;
- update();
- setInnerFocus();
- }, _composeSearch->lifetime());
- });
- if (!preventsClose(search)) {
- search();
- }
- }
- bool HistoryWidget::kbWasHidden() const {
- return _history
- && (_keyboard->forMsgId()
- == FullMsgId(
- _history->peer->id,
- _history->lastKeyboardHiddenId));
- }
- void HistoryWidget::showKeyboardHideButton() {
- _botKeyboardHide->setVisible(!_peer->isUser()
- || !_keyboard->persistent());
- }
- void HistoryWidget::toggleKeyboard(bool manual) {
- const auto fieldEnabled = canWriteMessage() && !_showAnimation;
- if (_kbShown || _kbReplyTo) {
- _botKeyboardHide->hide();
- if (_kbShown) {
- if (fieldEnabled) {
- _botKeyboardShow->show();
- }
- if (manual && _history) {
- _history->lastKeyboardHiddenId = _keyboard->forMsgId().msg;
- }
- _kbScroll->hide();
- _kbShown = false;
- _field->setMaxHeight(computeMaxFieldHeight());
- _kbReplyTo = nullptr;
- if (!readyToForward()
- && !_previewDrawPreview
- && !_editMsgId
- && !_replyTo) {
- _fieldBarCancel->hide();
- updateMouseTracking();
- }
- } else {
- if (_history) {
- _history->clearLastKeyboard();
- } else {
- updateBotKeyboard();
- }
- }
- } else if (!_keyboard->hasMarkup() && _keyboard->forceReply()) {
- _botKeyboardHide->hide();
- _botKeyboardShow->hide();
- if (fieldEnabled) {
- _botCommandStart->show();
- }
- _kbScroll->hide();
- _kbShown = false;
- _field->setMaxHeight(computeMaxFieldHeight());
- _kbReplyTo = (false
- || _peer->isChat()
- || _peer->isChannel()
- || _keyboard->forceReply())
- ? session().data().message(_keyboard->forMsgId())
- : nullptr;
- if (_kbReplyTo && !_editMsgId && !_replyTo && fieldEnabled) {
- updateReplyToName();
- updateReplyEditText(_kbReplyTo);
- }
- if (manual && _history) {
- _history->lastKeyboardHiddenId = 0;
- }
- } else if (fieldEnabled) {
- showKeyboardHideButton();
- _botKeyboardShow->hide();
- _kbScroll->show();
- _kbShown = true;
- const auto maxheight = computeMaxFieldHeight();
- const auto kbheight = qMin(
- _keyboard->height(),
- maxheight - (maxheight / 2));
- _field->setMaxHeight(maxheight - kbheight);
- _kbReplyTo = (false
- || _peer->isChat()
- || _peer->isChannel()
- || _keyboard->forceReply())
- ? session().data().message(_keyboard->forMsgId())
- : nullptr;
- if (_kbReplyTo && !_editMsgId && !_replyTo) {
- updateReplyToName();
- updateReplyEditText(_kbReplyTo);
- }
- if (manual && _history) {
- _history->lastKeyboardHiddenId = 0;
- }
- }
- updateControlsGeometry();
- updateFieldPlaceholder();
- if (_botKeyboardHide->isHidden()
- && canWriteMessage()
- && !_showAnimation) {
- _tabbedSelectorToggle->show();
- } else {
- _tabbedSelectorToggle->hide();
- }
- updateField();
- }
- void HistoryWidget::startBotCommand() {
- setFieldText(
- { u"/"_q, TextWithTags::Tags() },
- 0,
- Ui::InputField::HistoryAction::NewEntry);
- }
- void HistoryWidget::setMembersShowAreaActive(bool active) {
- if (!active) {
- _membersDropdownShowTimer.cancel();
- }
- if (active && _peer && (_peer->isChat() || _peer->isMegagroup())) {
- if (_membersDropdown) {
- _membersDropdown->otherEnter();
- } else if (!_membersDropdownShowTimer.isActive()) {
- _membersDropdownShowTimer.callOnce(kShowMembersDropdownTimeoutMs);
- }
- } else if (_membersDropdown) {
- _membersDropdown->otherLeave();
- }
- }
- void HistoryWidget::showMembersDropdown() {
- if (!_peer) {
- return;
- }
- if (!_membersDropdown) {
- _membersDropdown.create(this, st::membersInnerDropdown);
- _membersDropdown->setOwnedWidget(
- object_ptr<Profile::GroupMembersWidget>(
- this,
- controller(),
- _peer,
- st::membersInnerItem));
- _membersDropdown->resizeToWidth(st::membersInnerWidth);
- _membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
- _membersDropdown->moveToLeft(0, _topBar->height());
- _membersDropdown->setHiddenCallback([this] {
- _membersDropdown.destroyDelayed();
- });
- }
- _membersDropdown->otherEnter();
- }
- bool HistoryWidget::pushTabbedSelectorToThirdSection(
- not_null<Data::Thread*> thread,
- const Window::SectionShow ¶ms) {
- if (!_tabbedPanel) {
- return true;
- } else if (!Data::CanSendAnyOf(
- thread,
- Data::TabbedPanelSendRestrictions())) {
- Core::App().settings().setTabbedReplacedWithInfo(true);
- controller()->showPeerInfo(thread, params.withThirdColumn());
- return false;
- }
- Core::App().settings().setTabbedReplacedWithInfo(false);
- controller()->resizeForThirdSection();
- controller()->showSection(
- std::make_shared<ChatHelpers::TabbedMemento>(),
- params.withThirdColumn());
- return true;
- }
- bool HistoryWidget::returnTabbedSelector() {
- createTabbedPanel();
- moveFieldControls();
- return true;
- }
- void HistoryWidget::createTabbedPanel() {
- setTabbedPanel(std::make_unique<TabbedPanel>(
- this,
- controller(),
- controller()->tabbedSelector()));
- }
- void HistoryWidget::setTabbedPanel(std::unique_ptr<TabbedPanel> panel) {
- _tabbedPanel = std::move(panel);
- if (const auto raw = _tabbedPanel.get()) {
- _tabbedSelectorToggle->installEventFilter(raw);
- _tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
- } else {
- _tabbedSelectorToggle->setColorOverrides(
- &st::historyAttachEmojiActive,
- &st::historyRecordVoiceFgActive,
- &st::historyRecordVoiceRippleBgActive);
- }
- }
- bool HistoryWidget::preventsClose(Fn<void()> &&continueCallback) const {
- if (_voiceRecordBar->isActive()) {
- _voiceRecordBar->showDiscardBox(std::move(continueCallback));
- return true;
- }
- return false;
- }
- void HistoryWidget::toggleTabbedSelectorMode() {
- if (!_history) {
- return;
- }
- if (_tabbedPanel) {
- if (controller()->canShowThirdSection()
- && !controller()->adaptive().isOneColumn()) {
- Core::App().settings().setTabbedSelectorSectionEnabled(true);
- Core::App().saveSettingsDelayed();
- pushTabbedSelectorToThirdSection(
- _history,
- Window::SectionShow::Way::ClearStack);
- } else {
- _tabbedPanel->toggleAnimated();
- }
- } else {
- controller()->closeThirdSection();
- }
- }
- void HistoryWidget::recountChatWidth() {
- const auto layout = (width() < st::adaptiveChatWideWidth)
- ? Window::Adaptive::ChatLayout::Normal
- : Window::Adaptive::ChatLayout::Wide;
- controller()->adaptive().setChatLayout(layout);
- }
- int HistoryWidget::fieldHeight() const {
- return (_canSendTexts || _editMsgId)
- ? _field->height()
- : (st::historySendSize.height() - 2 * st::historySendPadding);
- }
- bool HistoryWidget::fieldOrDisabledShown() const {
- return !_field->isHidden() || _fieldDisabled;
- }
- void HistoryWidget::moveFieldControls() {
- auto keyboardHeight = 0;
- auto bottom = height();
- auto maxKeyboardHeight = computeMaxFieldHeight() - fieldHeight();
- _keyboard->resizeToWidth(width(), maxKeyboardHeight);
- if (_kbShown) {
- keyboardHeight = qMin(_keyboard->height(), maxKeyboardHeight);
- bottom -= keyboardHeight;
- _kbScroll->setGeometryToLeft(0, bottom, width(), keyboardHeight);
- }
- // (_botMenu.button) (_attachToggle|_replaceMedia) (_sendAs) ---- _inlineResults ------------------------------ _tabbedPanel ------ _fieldBarCancel
- // (_attachDocument|_attachPhoto) _field (_ttlInfo) (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
- // (_botStart|_unblock|_joinChannel|_muteUnmute|_reportMessages)
- auto buttonsBottom = bottom - _attachToggle->height();
- auto left = st::historySendRight;
- if (_botMenu.button) {
- const auto skip = st::historyBotMenuSkip;
- _botMenu.button->moveToLeft(left + skip, buttonsBottom + skip);
- left += skip + _botMenu.button->width();
- }
- if (_replaceMedia) {
- _replaceMedia->moveToLeft(left, buttonsBottom);
- }
- _attachToggle->moveToLeft(left, buttonsBottom);
- left += _attachToggle->width();
- if (_sendAs) {
- _sendAs->moveToLeft(left, buttonsBottom);
- left += _sendAs->width();
- }
- _field->moveToLeft(
- left,
- bottom - _field->height() - st::historySendPadding);
- if (_fieldDisabled) {
- _fieldDisabled->moveToLeft(
- left,
- bottom - fieldHeight() - st::historySendPadding);
- }
- auto right = st::historySendRight;
- _send->moveToRight(right, buttonsBottom); right += _send->width();
- _voiceRecordBar->moveToLeft(0, bottom - _voiceRecordBar->height());
- _tabbedSelectorToggle->moveToRight(right, buttonsBottom);
- _botKeyboardHide->moveToRight(right, buttonsBottom);
- right += _botKeyboardHide->width();
- _botKeyboardShow->moveToRight(right, buttonsBottom);
- _botCommandStart->moveToRight(right, buttonsBottom);
- if (_silent) {
- _silent->moveToRight(right, buttonsBottom);
- }
- const auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
- if (kbShowShown || _cmdStartShown || _silent) {
- right += _botCommandStart->width();
- }
- if (_scheduled) {
- _scheduled->moveToRight(right, buttonsBottom);
- right += _scheduled->width();
- }
- if (_ttlInfo) {
- _ttlInfo->move(width() - right - _ttlInfo->width(), buttonsBottom);
- }
- _fieldBarCancel->moveToRight(
- 0,
- _field->y() - st::historySendPadding - _fieldBarCancel->height());
- if (_inlineResults) {
- _inlineResults->moveBottom(_field->y() - st::historySendPadding);
- }
- if (_tabbedPanel) {
- _tabbedPanel->moveBottomRight(buttonsBottom, width());
- }
- if (_attachBotsMenu) {
- _attachBotsMenu->moveToLeft(
- 0,
- buttonsBottom - _attachBotsMenu->height());
- }
- const auto fullWidthButtonRect = myrtlrect(
- 0,
- bottom - _botStart->height(),
- width(),
- _botStart->height());
- _botStart->setGeometry(fullWidthButtonRect);
- _unblock->setGeometry(fullWidthButtonRect);
- _joinChannel->setGeometry(fullWidthButtonRect);
- _muteUnmute->setGeometry(fullWidthButtonRect);
- _reportMessages->setGeometry(fullWidthButtonRect);
- if (_sendRestriction) {
- _sendRestriction->setGeometry(fullWidthButtonRect);
- }
- }
- void HistoryWidget::updateFieldSize() {
- const auto kbShowShown = _history && !_kbShown && _keyboard->hasMarkup();
- auto fieldWidth = width()
- - _attachToggle->width()
- - st::historySendRight
- - _send->width()
- - _tabbedSelectorToggle->width();
- if (_botMenu.button) {
- fieldWidth -= st::historyBotMenuSkip + _botMenu.button->width();
- }
- if (_sendAs) {
- fieldWidth -= _sendAs->width();
- }
- if (kbShowShown) {
- fieldWidth -= _botKeyboardShow->width();
- }
- if (_cmdStartShown) {
- fieldWidth -= _botCommandStart->width();
- }
- if (_silent && _silent->isVisible()) {
- fieldWidth -= _silent->width();
- }
- if (_scheduled && _scheduled->isVisible()) {
- fieldWidth -= _scheduled->width();
- }
- if (_ttlInfo && _ttlInfo->isVisible()) {
- fieldWidth -= _ttlInfo->width();
- }
- if (_fieldDisabled) {
- _fieldDisabled->resize(width(), st::historySendSize.height());
- }
- if (_field->width() != fieldWidth) {
- _field->resize(fieldWidth, _field->height());
- } else {
- moveFieldControls();
- }
- }
- void HistoryWidget::clearInlineBot() {
- if (_inlineBot || _inlineLookingUpBot) {
- _inlineBot = nullptr;
- _inlineLookingUpBot = false;
- inlineBotChanged();
- _field->finishAnimating();
- }
- if (_inlineResults) {
- _inlineResults->clearInlineBot();
- }
- if (_autocomplete) {
- _autocomplete->requestRefresh();
- }
- }
- void HistoryWidget::inlineBotChanged() {
- bool isInlineBot = showInlineBotCancel();
- if (_isInlineBot != isInlineBot) {
- _isInlineBot = isInlineBot;
- updateFieldPlaceholder();
- updateFieldSubmitSettings();
- updateControlsVisibility();
- }
- }
- void HistoryWidget::fieldResized() {
- moveFieldControls();
- updateHistoryGeometry();
- updateField();
- }
- void HistoryWidget::fieldFocused() {
- if (_list) {
- _list->clearSelected(true);
- }
- }
- void HistoryWidget::updateFieldPlaceholder() {
- _voiceRecordBar->setPauseInsteadSend(_history
- && _history->peer->starsPerMessageChecked() > 0);
- if (!_editMsgId && _inlineBot && !_inlineLookingUpBot) {
- _field->setPlaceholder(
- rpl::single(_inlineBot->botInfo->inlinePlaceholder.mid(1)),
- _inlineBotUsername.size() + 2);
- return;
- }
- _field->setPlaceholder([&]() -> rpl::producer<QString> {
- const auto peer = _history ? _history->peer.get() : nullptr;
- if (_editMsgId) {
- return tr::lng_edit_message_text();
- } else if (!peer) {
- return tr::lng_message_ph();
- } else if ((_kbShown || _keyboard->forceReply())
- && !_keyboard->placeholder().isEmpty()) {
- return rpl::single(_keyboard->placeholder());
- } else if (const auto stars = peer->starsPerMessageChecked()) {
- return tr::lng_message_paid_ph(
- lt_amount,
- tr::lng_prize_credits_amount(
- lt_count,
- rpl::single(stars * 1.)));
- } else if (const auto channel = peer->asChannel()) {
- const auto topic = resolveReplyToTopic();
- const auto topicRootId = topic
- ? topic->rootId()
- : channel->forum()
- ? resolveReplyToTopicRootId()
- : MsgId();
- if (topicRootId) {
- auto title = rpl::single(topic
- ? topic->title()
- : (topicRootId == Data::ForumTopic::kGeneralId)
- ? u"General"_q
- : u"Topic"_q
- ) | rpl::then(session().changes().topicUpdates(
- Data::TopicUpdate::Flag::Title
- ) | rpl::filter([=](const Data::TopicUpdate &update) {
- return (update.topic->peer() == channel)
- && (update.topic->rootId() == topicRootId);
- }) | rpl::map([=](const Data::TopicUpdate &update) {
- return update.topic->title();
- }));
- const auto phrase = replyTo().messageId
- ? tr::lng_forum_reply_in
- : tr::lng_forum_message_in;
- return phrase(lt_topic, std::move(title));
- } else if (channel->isBroadcast()) {
- return session().data().notifySettings().silentPosts(channel)
- ? tr::lng_broadcast_silent_ph()
- : tr::lng_broadcast_ph();
- } else if (channel->adminRights() & ChatAdminRight::Anonymous) {
- return tr::lng_send_anonymous_ph();
- } else {
- return tr::lng_message_ph();
- }
- } else {
- return tr::lng_message_ph();
- }
- }());
- updateSendButtonType();
- }
- bool HistoryWidget::showSendingFilesError(
- const Ui::PreparedList &list) const {
- return showSendingFilesError(list, std::nullopt);
- }
- bool HistoryWidget::showSendingFilesError(
- const Ui::PreparedList &list,
- std::optional<bool> compress) const {
- const auto error = [&]() -> Data::SendError {
- const auto error = _peer
- ? Data::FileRestrictionError(_peer, list, compress)
- : Data::SendError();
- if (!_peer || error) {
- return error;
- } else if (const auto left = _peer->slowmodeSecondsLeft()) {
- return tr::lng_slowmode_enabled(
- tr::now,
- lt_left,
- Ui::FormatDurationWordsSlowmode(left));
- }
- using Error = Ui::PreparedList::Error;
- switch (list.error) {
- case Error::None: return QString();
- case Error::EmptyFile:
- case Error::Directory:
- case Error::NonLocalUrl: return tr::lng_send_image_empty(
- tr::now,
- lt_name,
- list.errorData);
- case Error::TooLargeFile: return u"(toolarge)"_q;
- }
- return tr::lng_forward_send_files_cant(tr::now);
- }();
- if (!error) {
- return false;
- } else if (error.text == u"(toolarge)"_q) {
- const auto fileSize = list.files.back().size;
- controller()->show(
- Box(FileSizeLimitBox, &session(), fileSize, nullptr));
- return true;
- }
- Data::ShowSendErrorToast(controller(), _peer, error);
- return true;
- }
- MsgId HistoryWidget::resolveReplyToTopicRootId() {
- Expects(_peer != nullptr);
- const auto replyToInfo = replyTo();
- const auto replyToMessage = (replyToInfo.messageId.peer == _peer->id)
- ? session().data().message(replyToInfo.messageId)
- : nullptr;
- const auto result = replyToMessage
- ? replyToMessage->topicRootId()
- : replyToInfo.topicRootId;
- if (result
- && _peer->isForum()
- && !_peer->forumTopicFor(result)
- && _topicsRequested.emplace(result).second) {
- _peer->forum()->requestTopic(result, crl::guard(_list, [=] {
- updateCanSendMessage();
- updateFieldPlaceholder();
- _topicsRequested.remove(result);
- }));
- }
- return result;
- }
- Data::ForumTopic *HistoryWidget::resolveReplyToTopic() {
- return _peer
- ? _peer->forumTopicFor(resolveReplyToTopicRootId())
- : nullptr;
- }
- bool HistoryWidget::showSendMessageError(
- const TextWithTags &textWithTags,
- bool ignoreSlowmodeCountdown,
- Fn<void(int starsApproved)> withPaymentApproved,
- int starsApproved) {
- if (!_canSendMessages) {
- return false;
- }
- const auto topicRootId = resolveReplyToTopicRootId();
- auto request = SendingErrorRequest{
- .topicRootId = topicRootId,
- .forward = &_forwardPanel->items(),
- .text = &textWithTags,
- .ignoreSlowmodeCountdown = ignoreSlowmodeCountdown,
- };
- request.messagesCount = ComputeSendingMessagesCount(_history, request);
- const auto error = GetErrorForSending(_peer, request);
- if (error) {
- Data::ShowSendErrorToast(controller(), _peer, error);
- return true;
- }
- return withPaymentApproved
- && !checkSendPayment(
- request.messagesCount,
- starsApproved,
- withPaymentApproved);
- }
- bool HistoryWidget::confirmSendingFiles(const QStringList &files) {
- return confirmSendingFiles(files, QString());
- }
- bool HistoryWidget::confirmSendingFiles(not_null<const QMimeData*> data) {
- return confirmSendingFiles(data, std::nullopt);
- }
- bool HistoryWidget::confirmSendingFiles(
- const QStringList &files,
- const QString &insertTextOnCancel) {
- const auto premium = controller()->session().user()->isPremium();
- return confirmSendingFiles(
- Storage::PrepareMediaList(files, st::sendMediaPreviewSize, premium),
- insertTextOnCancel);
- }
- bool HistoryWidget::confirmSendingFiles(
- Ui::PreparedList &&list,
- const QString &insertTextOnCancel) {
- if (_editMsgId) {
- if (_canReplaceMedia || _canAddMedia) {
- EditCaptionBox::StartMediaReplace(
- controller(),
- { _history->peer->id, _editMsgId },
- std::move(list),
- _field->getTextWithTags(),
- _mediaEditManager.spoilered(),
- _mediaEditManager.invertCaption(),
- crl::guard(_list, [=] { cancelEdit(); }));
- return true;
- }
- controller()->showToast(tr::lng_edit_caption_attach(tr::now));
- return false;
- } else if (showSendingFilesError(list)) {
- return false;
- }
- const auto cursor = _field->textCursor();
- const auto position = cursor.position();
- const auto anchor = cursor.anchor();
- const auto text = _field->getTextWithTags();
- auto box = Box<SendFilesBox>(
- controller(),
- std::move(list),
- text,
- _peer,
- Api::SendType::Normal,
- sendMenuDetails());
- _field->setTextWithTags({});
- box->setConfirmedCallback(crl::guard(this, [=](
- Ui::PreparedList &&list,
- Ui::SendFilesWay way,
- TextWithTags &&caption,
- Api::SendOptions options,
- bool ctrlShiftEnter) {
- sendingFilesConfirmed(
- std::move(list),
- way,
- std::move(caption),
- options,
- ctrlShiftEnter);
- }));
- box->setCancelledCallback(crl::guard(this, [=] {
- _field->setTextWithTags(text);
- auto cursor = _field->textCursor();
- cursor.setPosition(anchor);
- if (position != anchor) {
- cursor.setPosition(position, QTextCursor::KeepAnchor);
- }
- _field->setTextCursor(cursor);
- if (Ui::InsertTextOnImageCancel(insertTextOnCancel)) {
- _field->textCursor().insertText(insertTextOnCancel);
- }
- }));
- Window::ActivateWindow(controller());
- controller()->show(std::move(box));
- return true;
- }
- void HistoryWidget::sendingFilesConfirmed(
- Ui::PreparedList &&list,
- Ui::SendFilesWay way,
- TextWithTags &&caption,
- Api::SendOptions options,
- bool ctrlShiftEnter) {
- Expects(list.filesToProcess.empty());
- const auto compress = way.sendImagesAsPhotos();
- if (showSendingFilesError(list, compress)) {
- return;
- }
- auto groups = DivideByGroups(
- std::move(list),
- way,
- _peer->slowmodeApplied());
- auto bundle = PrepareFilesBundle(
- std::move(groups),
- way,
- std::move(caption),
- ctrlShiftEnter);
- sendingFilesConfirmed(std::move(bundle), options);
- }
- void HistoryWidget::sendingFilesConfirmed(
- std::shared_ptr<Ui::PreparedBundle> bundle,
- Api::SendOptions options) {
- const auto withPaymentApproved = [=](int approved) {
- auto copy = options;
- copy.starsApproved = approved;
- sendingFilesConfirmed(bundle, copy);
- };
- const auto checked = checkSendPayment(
- bundle->totalCount,
- options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return;
- }
- const auto compress = bundle->way.sendImagesAsPhotos();
- const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
- auto action = prepareSendAction(options);
- action.clearDraft = false;
- if (bundle->sendComment) {
- auto message = Api::MessageToSend(action);
- message.textWithTags = base::take(bundle->caption);
- session().api().sendMessage(std::move(message));
- }
- for (auto &group : bundle->groups) {
- const auto album = (group.type != Ui::AlbumType::None)
- ? std::make_shared<SendingAlbum>()
- : nullptr;
- session().api().sendFiles(
- std::move(group.list),
- type,
- base::take(bundle->caption),
- album,
- action);
- }
- }
- bool HistoryWidget::confirmSendingFiles(
- QImage &&image,
- QByteArray &&content,
- std::optional<bool> overrideSendImagesAsPhotos,
- const QString &insertTextOnCancel) {
- if (image.isNull()) {
- return false;
- }
- auto list = Storage::PrepareMediaFromImage(
- std::move(image),
- std::move(content),
- st::sendMediaPreviewSize);
- list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
- return confirmSendingFiles(std::move(list), insertTextOnCancel);
- }
- bool HistoryWidget::canSendFiles(not_null<const QMimeData*> data) const {
- if (!canWriteMessage()) {
- return false;
- } else if (data->hasImage()) {
- return true;
- } else if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
- if (ranges::all_of(urls, &QUrl::isLocalFile)) {
- return true;
- }
- }
- return false;
- }
- bool HistoryWidget::confirmSendingFiles(
- not_null<const QMimeData*> data,
- std::optional<bool> overrideSendImagesAsPhotos,
- const QString &insertTextOnCancel) {
- if (!canWriteMessage()) {
- return false;
- }
- const auto hasImage = data->hasImage();
- const auto premium = controller()->session().user()->isPremium();
- if (const auto urls = Core::ReadMimeUrls(data); !urls.empty()) {
- auto list = Storage::PrepareMediaList(
- urls,
- st::sendMediaPreviewSize,
- premium);
- if (list.error != Ui::PreparedList::Error::NonLocalUrl) {
- if (list.error == Ui::PreparedList::Error::None
- || !hasImage) {
- const auto emptyTextOnCancel = QString();
- list.overrideSendImagesAsPhotos = overrideSendImagesAsPhotos;
- confirmSendingFiles(std::move(list), emptyTextOnCancel);
- return true;
- }
- }
- }
- if (auto read = Core::ReadMimeImage(data)) {
- confirmSendingFiles(
- std::move(read.image),
- std::move(read.content),
- overrideSendImagesAsPhotos,
- insertTextOnCancel);
- return true;
- }
- return false;
- }
- void HistoryWidget::uploadFile(
- const QByteArray &fileContent,
- SendMediaType type) {
- if (!canWriteMessage()) return;
- session().api().sendFile(fileContent, type, prepareSendAction({}));
- }
- void HistoryWidget::handleHistoryChange(not_null<const History*> history) {
- if (_list && (_history == history || _migrated == history)) {
- handlePendingHistoryUpdate();
- updateBotKeyboard();
- if (!_scroll->isHidden()) {
- const auto unblock = isBlocked();
- const auto botStart = isBotStart();
- const auto joinChannel = isJoinChannel();
- const auto muteUnmute = isMuteUnmute();
- const auto reportMessages = isReportMessages();
- const auto update = false
- || (_reportMessages->isHidden() == reportMessages)
- || (!reportMessages && _unblock->isHidden() == unblock)
- || (!reportMessages
- && !unblock
- && _botStart->isHidden() == botStart)
- || (!reportMessages
- && !unblock
- && !botStart
- && _joinChannel->isHidden() == joinChannel)
- || (!reportMessages
- && !unblock
- && !botStart
- && !joinChannel
- && _muteUnmute->isHidden() == muteUnmute);
- if (update) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- }
- }
- }
- QPixmap HistoryWidget::grabForShowAnimation(
- const Window::SectionSlideParams ¶ms) {
- if (params.withTopBarShadow) {
- _topShadow->hide();
- }
- _inGrab = true;
- updateControlsGeometry();
- auto result = Ui::GrabWidget(this);
- _inGrab = false;
- updateControlsGeometry();
- if (params.withTopBarShadow) {
- _topShadow->show();
- }
- return result;
- }
- bool HistoryWidget::skipItemRepaint() {
- auto ms = crl::now();
- if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
- return false;
- }
- _updateHistoryItems.callOnce(
- _lastScrolled + kSkipRepaintWhileScrollMs - ms);
- return true;
- }
- void HistoryWidget::updateHistoryItemsByTimer() {
- if (!_list) {
- return;
- }
- auto ms = crl::now();
- if (_lastScrolled + kSkipRepaintWhileScrollMs <= ms) {
- _list->update();
- } else {
- _updateHistoryItems.callOnce(
- _lastScrolled + kSkipRepaintWhileScrollMs - ms);
- }
- }
- void HistoryWidget::handlePendingHistoryUpdate() {
- if (hasPendingResizedItems() || _updateHistoryGeometryRequired) {
- updateHistoryGeometry();
- _list->update();
- }
- }
- void HistoryWidget::resizeEvent(QResizeEvent *e) {
- //updateTabbedSelectorSectionShown();
- recountChatWidth();
- updateControlsGeometry();
- }
- void HistoryWidget::updateControlsGeometry() {
- _topBar->resizeToWidth(width());
- _topBar->moveToLeft(0, 0);
- _voiceRecordBar->resizeToWidth(width());
- moveFieldControls();
- const auto groupCallTop = _topBar->bottomNoMargins();
- if (_groupCallBar) {
- _groupCallBar->move(0, groupCallTop);
- _groupCallBar->resizeToWidth(width());
- }
- const auto requestsTop = groupCallTop
- + (_groupCallBar ? _groupCallBar->height() : 0);
- if (_requestsBar) {
- _requestsBar->move(0, requestsTop);
- _requestsBar->resizeToWidth(width());
- }
- const auto pinnedBarTop = requestsTop
- + (_requestsBar ? _requestsBar->height() : 0);
- if (_pinnedBar) {
- _pinnedBar->move(0, pinnedBarTop);
- _pinnedBar->resizeToWidth(width());
- }
- const auto sponsoredMessageBarTop = pinnedBarTop
- + (_pinnedBar ? _pinnedBar->height() : 0);
- if (_sponsoredMessageBar) {
- _sponsoredMessageBar->move(0, sponsoredMessageBarTop);
- _sponsoredMessageBar->resizeToWidth(width());
- }
- const auto translateTop = sponsoredMessageBarTop
- + (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0);
- if (_translateBar) {
- _translateBar->move(0, translateTop);
- _translateBar->resizeToWidth(width());
- }
- const auto paysStatusTop = translateTop
- + (_translateBar ? _translateBar->height() : 0);
- if (_paysStatus) {
- _paysStatus->bar().move(0, paysStatusTop);
- }
- const auto contactStatusTop = paysStatusTop
- + (_paysStatus ? _paysStatus->bar().height() : 0);
- if (_contactStatus) {
- _contactStatus->bar().move(0, contactStatusTop);
- }
- const auto businessBotTop = contactStatusTop
- + (_contactStatus ? _contactStatus->bar().height() : 0);
- if (_businessBotStatus) {
- _businessBotStatus->bar().move(0, businessBotTop);
- }
- const auto scrollAreaTop = businessBotTop
- + (_businessBotStatus ? _businessBotStatus->bar().height() : 0);
- if (_scroll->y() != scrollAreaTop) {
- _scroll->moveToLeft(0, scrollAreaTop);
- if (_autocomplete) {
- _autocomplete->setBoundings(_scroll->geometry());
- }
- if (_supportAutocomplete) {
- _supportAutocomplete->setBoundings(_scroll->geometry());
- }
- }
- updateHistoryGeometry(false, false, { ScrollChangeAdd, _topDelta });
- updateFieldSize();
- _cornerButtons.updatePositions();
- if (_membersDropdown) {
- _membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
- }
- const auto isOneColumn = controller()->adaptive().isOneColumn();
- const auto isThreeColumn = controller()->adaptive().isThreeColumn();
- const auto topShadowLeft = (isOneColumn || _inGrab)
- ? 0
- : st::lineWidth;
- const auto topShadowRight = (isThreeColumn && !_inGrab && _peer)
- ? st::lineWidth
- : 0;
- _topShadow->setGeometryToLeft(
- topShadowLeft,
- _topBar->bottomNoMargins(),
- width() - topShadowLeft - topShadowRight,
- st::lineWidth);
- }
- void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
- if (item == _replyEditMsg && _editMsgId) {
- cancelEdit();
- }
- if (item == _replyEditMsg && _replyTo) {
- cancelReply();
- }
- if (item == _processingReplyItem) {
- _processingReplyTo = {};
- _processingReplyItem = nullptr;
- }
- if (_kbReplyTo && item == _kbReplyTo) {
- toggleKeyboard();
- _kbReplyTo = nullptr;
- }
- const auto i = _itemRevealAnimations.find(item);
- if (i != end(_itemRevealAnimations)) {
- _itemRevealAnimations.erase(i);
- revealItemsCallback();
- }
- const auto j = _itemRevealPending.find(item);
- if (j != _itemRevealPending.end()) {
- _itemRevealPending.erase(j);
- }
- }
- void HistoryWidget::itemEdited(not_null<HistoryItem*> item) {
- if (item.get() == _replyEditMsg) {
- updateReplyEditTexts(true);
- }
- }
- FullReplyTo HistoryWidget::replyTo() const {
- return _replyTo
- ? _replyTo
- : _kbReplyTo
- ? FullReplyTo{ _kbReplyTo->fullId() }
- : (_peer && _peer->forum())
- ? FullReplyTo{ .topicRootId = Data::ForumTopic::kGeneralId }
- : FullReplyTo();
- }
- bool HistoryWidget::hasSavedScroll() const {
- Expects(_history != nullptr);
- return _history->scrollTopItem
- || (_migrated && _migrated->scrollTopItem);
- }
- int HistoryWidget::countInitialScrollTop() {
- if (hasSavedScroll()) {
- return _list->historyScrollTop();
- } else if (_showAtMsgId
- && (IsServerMsgId(_showAtMsgId)
- || IsClientMsgId(_showAtMsgId)
- || IsServerMsgId(-_showAtMsgId))) {
- const auto item = getItemFromHistoryOrMigrated(_showAtMsgId);
- const auto itemTop = _list->itemTop(item);
- if (itemTop < 0) {
- setMsgId(ShowAtUnreadMsgId);
- controller()->showToast(tr::lng_message_not_found(tr::now));
- return countInitialScrollTop();
- } else {
- const auto view = item->mainView();
- Assert(view != nullptr);
- enqueueMessageHighlight({
- item,
- base::take(_showAtMsgParams.highlightPart),
- base::take(_showAtMsgParams.highlightPartOffsetHint),
- });
- const auto result = itemTopForHighlight(view);
- createUnreadBarIfBelowVisibleArea(result);
- return result;
- }
- } else if (_showAtMsgId == ShowAtTheEndMsgId) {
- return ScrollMax;
- } else if (const auto top = unreadBarTop()) {
- return *top;
- } else {
- _history->calculateFirstUnreadMessage();
- return countAutomaticScrollTop();
- }
- }
- void HistoryWidget::createUnreadBarIfBelowVisibleArea(int withScrollTop) {
- Expects(_history != nullptr);
- if (_history->unreadBar()) {
- return;
- }
- _history->calculateFirstUnreadMessage();
- if (const auto unread = _history->firstUnreadMessage()) {
- if (_list->itemTop(unread) > withScrollTop) {
- createUnreadBarAndResize();
- }
- }
- }
- void HistoryWidget::createUnreadBarAndResize() {
- if (!_history->firstUnreadMessage()) {
- return;
- }
- const auto was = base::take(_historyInited);
- _history->addUnreadBar();
- if (hasPendingResizedItems()) {
- updateListSize();
- }
- _historyInited = was;
- }
- int HistoryWidget::countAutomaticScrollTop() {
- Expects(_history != nullptr);
- Expects(_list != nullptr);
- if (const auto unread = _history->firstUnreadMessage()) {
- const auto firstUnreadTop = _list->itemTop(unread);
- const auto possibleUnreadBarTop = _scroll->scrollTopMax()
- + HistoryView::UnreadBar::height()
- - HistoryView::UnreadBar::marginTop();
- if (firstUnreadTop < possibleUnreadBarTop) {
- createUnreadBarAndResize();
- if (_history->unreadBar() != nullptr) {
- setMsgId(ShowAtUnreadMsgId);
- return countInitialScrollTop();
- }
- }
- }
- return ScrollMax;
- }
- Data::SendError HistoryWidget::computeSendRestriction() const {
- const auto allWithoutPolls = Data::AllSendRestrictions()
- & ~ChatRestriction::SendPolls;
- return (_peer && !Data::CanSendAnyOf(_peer, allWithoutPolls))
- ? Data::RestrictionError(_peer, ChatRestriction::SendOther)
- : Data::SendError();
- }
- void HistoryWidget::updateSendRestriction() {
- const auto restriction = computeSendRestriction();
- if (_sendRestrictionKey == restriction.text) {
- return;
- }
- _sendRestrictionKey = restriction.text;
- if (!restriction) {
- _sendRestriction = nullptr;
- } else if (restriction.premiumToLift) {
- _sendRestriction = PremiumRequiredSendRestriction(
- this,
- _peer->asUser(),
- controller());
- } else if (const auto lifting = restriction.boostsToLift) {
- auto button = base::make_unique_q<Ui::FlatButton>(
- this,
- restriction.text,
- st::historyComposeButton);
- const auto channel = _peer->asChannel();
- button->setClickedCallback([=] {
- controller()->resolveBoostState(channel, lifting);
- });
- _sendRestriction = std::move(button);
- } else {
- _sendRestriction = TextErrorSendRestriction(this, restriction.text);
- }
- if (_sendRestriction) {
- _sendRestriction->show();
- moveFieldControls();
- }
- }
- void HistoryWidget::updateHistoryGeometry(
- bool initial,
- bool loadedDown,
- const ScrollChange &change) {
- const auto guard = gsl::finally([&] {
- _itemRevealPending.clear();
- });
- if (!_history
- || (initial && _historyInited)
- || (!initial && !_historyInited)) {
- return;
- }
- if (_firstLoadRequest || _showAnimation) {
- _updateHistoryGeometryRequired = true;
- // scrollTopMax etc are not working after recountHistoryGeometry()
- return;
- }
- auto newScrollHeight = height() - _topBar->height();
- if (_translateBar) {
- newScrollHeight -= _translateBar->height();
- }
- if (_sponsoredMessageBar) {
- newScrollHeight -= _sponsoredMessageBar->height();
- }
- if (_pinnedBar) {
- newScrollHeight -= _pinnedBar->height();
- }
- if (_groupCallBar) {
- newScrollHeight -= _groupCallBar->height();
- }
- if (_requestsBar) {
- newScrollHeight -= _requestsBar->height();
- }
- if (_paysStatus) {
- newScrollHeight -= _paysStatus->bar().height();
- }
- if (_contactStatus) {
- newScrollHeight -= _contactStatus->bar().height();
- }
- if (_businessBotStatus) {
- newScrollHeight -= _businessBotStatus->bar().height();
- }
- if (isChoosingTheme()) {
- newScrollHeight -= _chooseTheme->height();
- } else if (!editingMessage()
- && (isSearching()
- || isBlocked()
- || isBotStart()
- || isJoinChannel()
- || isMuteUnmute()
- || isReportMessages())) {
- newScrollHeight -= _unblock->height();
- } else {
- if (editingMessage() || _canSendMessages) {
- newScrollHeight -= (fieldHeight() + 2 * st::historySendPadding);
- } else if (_sendRestriction) {
- newScrollHeight -= _sendRestriction->height();
- }
- if (_editMsgId
- || replyTo()
- || readyToForward()
- || _previewDrawPreview) {
- newScrollHeight -= st::historyReplyHeight;
- }
- if (_kbShown) {
- newScrollHeight -= _kbScroll->height();
- }
- }
- if (newScrollHeight <= 0) {
- return;
- }
- const auto wasScrollTop = _scroll->scrollTop();
- const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax());
- const auto needResize = (_scroll->width() != width())
- || (_scroll->height() != newScrollHeight);
- if (needResize) {
- _scroll->resize(width(), newScrollHeight);
- // on initial updateListSize we didn't put the _scroll->scrollTop
- // correctly yet so visibleAreaUpdated() call will erase it
- // with the new (undefined) value
- if (!initial) {
- visibleAreaUpdated();
- }
- }
- if (needResize || initial) {
- if (_autocomplete) {
- _autocomplete->setBoundings(_scroll->geometry());
- }
- if (_supportAutocomplete) {
- _supportAutocomplete->setBoundings(_scroll->geometry());
- }
- _cornerButtons.updatePositions();
- controller()->floatPlayerAreaUpdated();
- }
- updateListSize();
- _updateHistoryGeometryRequired = false;
- auto newScrollTop = 0;
- if (initial) {
- newScrollTop = countInitialScrollTop();
- _historyInited = true;
- _scrollToAnimation.stop();
- } else if (wasAtBottom && !loadedDown && !_history->unreadBar()) {
- newScrollTop = countAutomaticScrollTop();
- } else {
- newScrollTop = std::min(
- _list->historyScrollTop(),
- _scroll->scrollTopMax());
- if (change.type == ScrollChangeAdd) {
- newScrollTop += change.value;
- } else if (change.type == ScrollChangeNoJumpToBottom) {
- newScrollTop = wasScrollTop;
- }
- }
- const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
- synteticScrollToY(toY);
- if (initial && _showAtMsgId) {
- const auto timestamp = base::take(_showAtMsgParams.videoTimestamp);
- if (timestamp.has_value()) {
- const auto item = session().data().message(_peer, _showAtMsgId);
- const auto media = item ? item->media() : nullptr;
- const auto document = media ? media->document() : nullptr;
- if (document && document->isVideoFile()) {
- controller()->openDocument(
- document,
- true,
- { item->fullId() },
- nullptr,
- timestamp);
- }
- }
- }
- }
- void HistoryWidget::revealItemsCallback() {
- auto height = 0;
- if (!_historyInited) {
- _itemRevealAnimations.clear();
- }
- for (auto i = begin(_itemRevealAnimations)
- ; i != end(_itemRevealAnimations);) {
- if (!i->second.animation.animating()) {
- i = _itemRevealAnimations.erase(i);
- } else {
- height += anim::interpolate(
- i->second.startHeight,
- 0,
- i->second.animation.value(1.));
- ++i;
- }
- }
- if (_itemsRevealHeight != height) {
- const auto wasScrollTop = _scroll->scrollTop();
- const auto wasAtBottom = (wasScrollTop == _scroll->scrollTopMax());
- if (!wasAtBottom) {
- height = 0;
- _itemRevealAnimations.clear();
- }
- _itemsRevealHeight = height;
- _list->changeItemsRevealHeight(_itemsRevealHeight);
- const auto newScrollTop = (wasAtBottom && !_history->unreadBar())
- ? countAutomaticScrollTop()
- : _list->historyScrollTop();
- const auto toY = std::clamp(newScrollTop, 0, _scroll->scrollTopMax());
- synteticScrollToY(toY);
- }
- }
- void HistoryWidget::startItemRevealAnimations() {
- for (const auto &item : base::take(_itemRevealPending)) {
- if (const auto view = item->mainView()) {
- if (const auto top = _list->itemTop(view); top >= 0) {
- if (const auto height = view->height()) {
- startMessageSendingAnimation(item);
- if (!_itemRevealAnimations.contains(item)) {
- auto &animation = _itemRevealAnimations[item];
- animation.startHeight = height;
- _itemsRevealHeight += height;
- animation.animation.start(
- [=] { revealItemsCallback(); },
- 0.,
- 1.,
- HistoryView::ListWidget::kItemRevealDuration,
- anim::easeOutCirc);
- if (item->out() || _history->peer->isSelf()) {
- _list->theme()->rotateComplexGradientBackground();
- }
- }
- }
- }
- }
- }
- }
- void HistoryWidget::startMessageSendingAnimation(
- not_null<HistoryItem*> item) {
- auto &sendingAnimation = controller()->sendingAnimation();
- if (!sendingAnimation.checkExpectedType(item)) {
- return;
- }
- Assert(item->mainView() != nullptr);
- Assert(item->mainView()->media() != nullptr);
- auto globalEndTopLeft = rpl::merge(
- _scroll->innerResizes() | rpl::to_empty,
- session().data().newItemAdded() | rpl::to_empty,
- geometryValue() | rpl::to_empty,
- _scroll->geometryValue() | rpl::to_empty,
- _list->geometryValue() | rpl::to_empty
- ) | rpl::map([=]() -> std::optional<QPoint> {
- const auto view = item->mainView();
- const auto top = view ? _list->itemTop(view) : -1;
- if (top < 0) {
- return std::nullopt;
- }
- const auto additional = (_list->height() == _scroll->height())
- ? view->height()
- : 0;
- return _list->mapToGlobal(QPoint(0, top - additional));
- });
- sendingAnimation.startAnimation({
- .globalEndTopLeft = std::move(globalEndTopLeft),
- .view = [=] { return item->mainView(); },
- .paintContext = [=] { return _list->preparePaintContext({}); },
- });
- }
- void HistoryWidget::updateListSize() {
- Expects(_list != nullptr);
- _list->recountHistoryGeometry(!_historyInited);
- auto washidden = _scroll->isHidden();
- if (washidden) {
- _scroll->show();
- }
- startItemRevealAnimations();
- _list->setItemsRevealHeight(_itemsRevealHeight);
- _list->updateSize();
- if (washidden) {
- _scroll->hide();
- }
- _updateHistoryGeometryRequired = true;
- }
- bool HistoryWidget::hasPendingResizedItems() const {
- if (!_list) {
- // Based on the crash reports there is a codepath (at least on macOS)
- // that leads from _list = _scroll->setOwnedWidget(...) right into
- // the HistoryWidget::paintEvent (by sending fake mouse move events
- // inside scroll area -> hiding tooltip window -> exposing the main
- // window -> syncing it backing store synchronously).
- //
- // So really we could get here with !_list && (_history != nullptr).
- return false;
- }
- return (_history && _history->hasPendingResizedItems())
- || (_migrated && _migrated->hasPendingResizedItems());
- }
- std::optional<int> HistoryWidget::unreadBarTop() const {
- const auto bar = [&]() -> HistoryView::Element* {
- if (const auto bar = _migrated ? _migrated->unreadBar() : nullptr) {
- return bar;
- }
- return _history->unreadBar();
- }();
- if (bar) {
- const auto result = _list->itemTop(bar)
- + HistoryView::UnreadBar::marginTop();
- if (bar->Has<HistoryView::DateBadge>()) {
- return result + bar->Get<HistoryView::DateBadge>()->height();
- }
- return result;
- }
- return std::nullopt;
- }
- void HistoryWidget::addMessagesToFront(
- not_null<PeerData*> peer,
- const QVector<MTPMessage> &messages) {
- _list->messagesReceived(peer, messages);
- if (!_firstLoadRequest) {
- updateHistoryGeometry();
- updateBotKeyboard();
- }
- }
- void HistoryWidget::addMessagesToBack(
- not_null<PeerData*> peer,
- const QVector<MTPMessage> &messages) {
- const auto checkForUnreadStart = [&] {
- if (_history->unreadBar() || !_history->trackUnreadMessages()) {
- return false;
- }
- _history->calculateFirstUnreadMessage();
- return !_history->firstUnreadMessage();
- }();
- _list->messagesReceivedDown(peer, messages);
- if (checkForUnreadStart) {
- _history->calculateFirstUnreadMessage();
- createUnreadBarAndResize();
- }
- if (!_firstLoadRequest) {
- updateHistoryGeometry(false, true, { ScrollChangeNoJumpToBottom, 0 });
- }
- injectSponsoredMessages();
- }
- void HistoryWidget::updateBotKeyboard(History *h, bool force) {
- if (h && h != _history && h != _migrated) {
- return;
- }
- const auto wasVisible = _kbShown || _kbReplyTo;
- const auto wasMsgId = _keyboard->forMsgId();
- auto changed = false;
- if ((_replyTo && !_replyEditMsg) || _editMsgId || !_history) {
- changed = _keyboard->updateMarkup(nullptr, force);
- } else if (_replyTo && _replyEditMsg) {
- changed = _keyboard->updateMarkup(_replyEditMsg, force);
- } else {
- const auto keyboardItem = _history->lastKeyboardId
- ? session().data().message(
- _history->peer,
- _history->lastKeyboardId)
- : nullptr;
- changed = _keyboard->updateMarkup(keyboardItem, force);
- }
- const auto controlsChanged = updateCmdStartShown();
- if (!changed) {
- if (controlsChanged) {
- updateControlsGeometry();
- }
- return;
- } else if (_keyboard->forMsgId() != wasMsgId) {
- _kbScroll->scrollTo({ 0, 0 });
- }
- const auto hasMarkup = _keyboard->hasMarkup();
- const auto forceReply = _keyboard->forceReply()
- && (!_replyTo || !_replyEditMsg);
- if (hasMarkup || forceReply) {
- if (_keyboard->singleUse()
- && _keyboard->hasMarkup()
- && (_keyboard->forMsgId()
- == FullMsgId(_history->peer->id, _history->lastKeyboardId))
- && _history->lastKeyboardUsed) {
- _history->lastKeyboardHiddenId = _history->lastKeyboardId;
- }
- if (!isSearching()
- && !isBotStart()
- && !isBlocked()
- && _canSendMessages
- && (wasVisible
- || (_replyTo && _replyEditMsg)
- || (!HasSendText(_field) && !kbWasHidden()))) {
- if (!_showAnimation) {
- if (hasMarkup) {
- _kbScroll->show();
- _tabbedSelectorToggle->hide();
- showKeyboardHideButton();
- } else {
- _kbScroll->hide();
- _tabbedSelectorToggle->show();
- _botKeyboardHide->hide();
- }
- _botKeyboardShow->hide();
- _botCommandStart->hide();
- }
- const auto maxheight = computeMaxFieldHeight();
- const auto kbheight = hasMarkup
- ? qMin(_keyboard->height(), maxheight - (maxheight / 2))
- : 0;
- _field->setMaxHeight(maxheight - kbheight);
- _kbShown = hasMarkup;
- _kbReplyTo = (_peer->isChat()
- || _peer->isChannel()
- || _keyboard->forceReply())
- ? session().data().message(_keyboard->forMsgId())
- : nullptr;
- if (_kbReplyTo && !_replyTo) {
- updateReplyToName();
- updateReplyEditText(_kbReplyTo);
- }
- } else {
- if (!_showAnimation) {
- _kbScroll->hide();
- _tabbedSelectorToggle->show();
- _botKeyboardHide->hide();
- _botKeyboardShow->show();
- _botCommandStart->hide();
- }
- _field->setMaxHeight(computeMaxFieldHeight());
- _kbShown = false;
- _kbReplyTo = nullptr;
- if (!readyToForward()
- && !_previewDrawPreview
- && !_replyTo) {
- _fieldBarCancel->hide();
- updateMouseTracking();
- }
- }
- } else {
- if (!_scroll->isHidden()) {
- _kbScroll->hide();
- _tabbedSelectorToggle->show();
- _botKeyboardHide->hide();
- _botKeyboardShow->hide();
- _botCommandStart->setVisible(!_editMsgId);
- }
- _field->setMaxHeight(computeMaxFieldHeight());
- _kbShown = false;
- _kbReplyTo = nullptr;
- if (!readyToForward()
- && !_previewDrawPreview
- && !_replyTo
- && !_editMsgId) {
- _fieldBarCancel->hide();
- updateMouseTracking();
- }
- }
- refreshTopBarActiveChat();
- updateFieldPlaceholder();
- updateControlsGeometry();
- update();
- }
- void HistoryWidget::botCallbackSent(not_null<HistoryItem*> item) {
- if (!item->isRegular() || _peer != item->history()->peer) {
- return;
- }
- const auto keyId = _keyboard->forMsgId();
- const auto lastKeyboardUsed = (keyId == FullMsgId(_peer->id, item->id))
- && (keyId == FullMsgId(_peer->id, _history->lastKeyboardId));
- session().data().requestItemRepaint(item);
- if (_replyTo.messageId == item->fullId()) {
- cancelReply();
- }
- if (_keyboard->singleUse()
- && _keyboard->hasMarkup()
- && lastKeyboardUsed) {
- if (_kbShown) {
- toggleKeyboard(false);
- }
- _history->lastKeyboardUsed = true;
- }
- }
- int HistoryWidget::computeMaxFieldHeight() const {
- const auto available = height()
- - _topBar->height()
- - (_paysStatus ? _paysStatus->bar().height() : 0)
- - (_contactStatus ? _contactStatus->bar().height() : 0)
- - (_businessBotStatus ? _businessBotStatus->bar().height() : 0)
- - (_sponsoredMessageBar ? _sponsoredMessageBar->height() : 0)
- - (_pinnedBar ? _pinnedBar->height() : 0)
- - (_groupCallBar ? _groupCallBar->height() : 0)
- - (_requestsBar ? _requestsBar->height() : 0)
- - ((_editMsgId
- || replyTo()
- || readyToForward()
- || _previewDrawPreview)
- ? st::historyReplyHeight
- : 0)
- - (2 * st::historySendPadding)
- - st::historyReplyHeight; // at least this height for history.
- return std::min(st::historyComposeFieldMaxHeight, available);
- }
- bool HistoryWidget::cornerButtonsIgnoreVisibility() {
- return _showAnimation != nullptr;
- }
- std::optional<bool> HistoryWidget::cornerButtonsDownShown() {
- if (!_list || _firstLoadRequest) {
- return false;
- }
- if (_voiceRecordBar->isLockPresent()
- || _voiceRecordBar->isTTLButtonShown()) {
- return false;
- }
- if (!_history->loadedAtBottom() || _cornerButtons.replyReturn()) {
- return true;
- }
- const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
- if (top < _scroll->scrollTopMax()) {
- return true;
- }
- const auto haveUnreadBelowBottom = [&](History *history) {
- if (!_list
- || !history
- || history->unreadCount() <= 0
- || !history->trackUnreadMessages()) {
- return false;
- }
- const auto unread = history->firstUnreadMessage();
- if (!unread) {
- return false;
- }
- const auto top = _list->itemTop(unread);
- return (top >= _scroll->scrollTop() + _scroll->height());
- };
- if (haveUnreadBelowBottom(_history)
- || haveUnreadBelowBottom(_migrated)) {
- return true;
- }
- return false;
- }
- bool HistoryWidget::cornerButtonsUnreadMayBeShown() {
- return !_firstLoadRequest && !_voiceRecordBar->isLockPresent();
- }
- bool HistoryWidget::cornerButtonsHas(HistoryView::CornerButtonType type) {
- return true;
- }
- void HistoryWidget::mousePressEvent(QMouseEvent *e) {
- if (!_list) {
- // Remove focus from the chats list search.
- setFocus();
- // Set it back to the chats list so that typing filter chats.
- controller()->widget()->setInnerFocus();
- return;
- }
- const auto isReadyToForward = readyToForward();
- if (_editMsgId
- && (_inDetails || _inPhotoEdit)
- && (e->button() == Qt::RightButton)) {
- _mediaEditManager.showMenu(
- _list,
- [=] { mouseMoveEvent(nullptr); },
- HasSendText(_field));
- } else if (_inPhotoEdit && _photoEditMedia) {
- EditCaptionBox::StartPhotoEdit(
- controller(),
- _photoEditMedia,
- { _history->peer->id, _editMsgId },
- _field->getTextWithTags(),
- _mediaEditManager.spoilered(),
- _mediaEditManager.invertCaption(),
- crl::guard(_list, [=] { cancelEdit(); }));
- } else if (!_inDetails) {
- return;
- } else if (_previewDrawPreview) {
- editDraftOptions();
- } else if (_editMsgId) {
- controller()->showPeerHistory(
- _peer,
- Window::SectionShow::Way::Forward,
- _editMsgId);
- } else if (_replyTo
- && ((e->modifiers() & Qt::ControlModifier)
- || (e->button() != Qt::LeftButton))) {
- jumpToReply(_replyTo);
- } else if (_replyTo
- || (isReadyToForward && e->button() == Qt::LeftButton)) {
- editDraftOptions();
- } else if (isReadyToForward) {
- _forwardPanel->editToNextOption();
- } else if (_kbReplyTo) {
- controller()->showPeerHistory(
- _kbReplyTo->history()->peer->id,
- Window::SectionShow::Way::Forward,
- _kbReplyTo->id);
- }
- }
- void HistoryWidget::editDraftOptions() {
- Expects(_history != nullptr);
- const auto history = _history;
- const auto reply = _replyTo;
- const auto webpage = _preview->draft();
- const auto forward = _forwardPanel->draft();
- const auto done = [=](
- FullReplyTo replyTo,
- Data::WebPageDraft webpage,
- Data::ForwardDraft forward) {
- if (replyTo) {
- replyToMessage(replyTo);
- } else {
- cancelReply();
- }
- history->setForwardDraft({}, std::move(forward));
- _preview->apply(webpage);
- };
- const auto replyToId = reply.messageId;
- const auto highlight = crl::guard(this, [=](FullReplyTo to) {
- jumpToReply(to);
- });
- using namespace HistoryView::Controls;
- EditDraftOptions({
- .show = controller()->uiShow(),
- .history = history,
- .draft = Data::Draft(_field, reply, _preview->draft()),
- .usedLink = _preview->link(),
- .forward = _forwardPanel->draft(),
- .links = _preview->links(),
- .resolver = _preview->resolver(),
- .done = done,
- .highlight = highlight,
- .clearOldDraft = [=] { ClearDraftReplyTo(history, 0, replyToId); },
- });
- }
- void HistoryWidget::jumpToReply(FullReplyTo to) {
- if (const auto item = session().data().message(to.messageId)) {
- JumpToMessageClickHandler(
- item,
- {},
- to.quote,
- to.quoteOffset
- )->onClick({});
- }
- }
- void HistoryWidget::keyPressEvent(QKeyEvent *e) {
- if (!_history) return;
- const auto commonModifiers = e->modifiers() & kCommonModifiers;
- if (e->key() == Qt::Key_Escape) {
- if (hasFocus()) {
- escape();
- } else {
- e->ignore();
- }
- } else if (e->key() == Qt::Key_Back) {
- _cancelRequests.fire({});
- } else if (e->key() == Qt::Key_PageDown) {
- _scroll->keyPressEvent(e);
- } else if (e->key() == Qt::Key_PageUp) {
- _scroll->keyPressEvent(e);
- } else if (e->key() == Qt::Key_Down && !commonModifiers) {
- _scroll->keyPressEvent(e);
- } else if (e->key() == Qt::Key_Up && !commonModifiers) {
- const auto item = _history
- ? _history->lastEditableMessage()
- : nullptr;
- if (item
- && _field->empty()
- && !_editMsgId
- && !_replyTo) {
- editMessage(item, {});
- return;
- }
- _scroll->keyPressEvent(e);
- } else if (e->key() == Qt::Key_Up
- && commonModifiers == Qt::ControlModifier) {
- if (!replyToPreviousMessage()) {
- e->ignore();
- }
- } else if (e->key() == Qt::Key_Down
- && commonModifiers == Qt::ControlModifier) {
- if (!replyToNextMessage()) {
- e->ignore();
- }
- } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
- if (!_botStart->isHidden()) {
- sendBotStartCommand();
- }
- if (!_canSendMessages) {
- const auto submitting = Ui::InputField::ShouldSubmit(
- Core::App().settings().sendSubmitWay(),
- e->modifiers());
- if (submitting) {
- sendWithModifiers(e->modifiers());
- }
- }
- } else if ((e->key() == Qt::Key_O)
- && (e->modifiers() == Qt::ControlModifier)) {
- chooseAttach();
- } else {
- e->ignore();
- }
- }
- void HistoryWidget::handlePeerMigration() {
- const auto current = _peer->migrateToOrMe();
- const auto chat = current->migrateFrom();
- if (!chat) {
- return;
- }
- const auto channel = current->asChannel();
- Assert(channel != nullptr);
- if (_peer != channel) {
- showHistory(
- channel->id,
- (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId);
- channel->session().api().chatParticipants().requestCountDelayed(
- channel);
- } else {
- _migrated = _history->migrateFrom();
- _list->notifyMigrateUpdated();
- setupPinnedTracker();
- setupGroupCallBar();
- setupRequestsBar();
- updateHistoryGeometry();
- }
- const auto from = chat->owner().historyLoaded(chat);
- const auto to = channel->owner().historyLoaded(channel);
- if (from
- && to
- && !from->isEmpty()
- && (!from->loadedAtBottom() || !to->loadedAtTop())) {
- from->clear(History::ClearType::Unload);
- }
- }
- bool HistoryWidget::replyToPreviousMessage() {
- if (!_history
- || _editMsgId
- || _history->isForum()
- || (_replyTo && _replyTo.messageId.peer != _history->peer->id)) {
- return false;
- }
- const auto fullId = FullMsgId(
- _history->peer->id,
- (_field->isVisible()
- ? _replyTo.messageId.msg
- : _highlighter.latestSingleHighlightedMsgId()));
- if (const auto item = session().data().message(fullId)) {
- if (const auto view = item->mainView()) {
- if (const auto previousView = view->previousDisplayedInBlocks()) {
- const auto previous = previousView->data();
- controller()->showMessage(previous);
- if (_field->isVisible()) {
- replyToMessage(previous);
- }
- return true;
- }
- }
- } else if (const auto previousView = _history->findLastDisplayed()) {
- const auto previous = previousView->data();
- controller()->showMessage(previous);
- if (_field->isVisible()) {
- replyToMessage(previous);
- }
- return true;
- }
- return false;
- }
- bool HistoryWidget::replyToNextMessage() {
- if (!_history
- || _editMsgId
- || _history->isForum()
- || (_replyTo && _replyTo.messageId.peer != _history->peer->id)) {
- return false;
- }
- const auto fullId = FullMsgId(
- _history->peer->id,
- (_field->isVisible()
- ? _replyTo.messageId.msg
- : _highlighter.latestSingleHighlightedMsgId()));
- if (const auto item = session().data().message(fullId)) {
- if (const auto view = item->mainView()) {
- if (const auto nextView = view->nextDisplayedInBlocks()) {
- const auto next = nextView->data();
- controller()->showMessage(next);
- if (_field->isVisible()) {
- replyToMessage(next);
- }
- } else {
- _highlighter.clear();
- cancelReply(false);
- }
- return true;
- }
- }
- return false;
- }
- bool HistoryWidget::showSlowmodeError() {
- const auto text = [&] {
- if (const auto left = _peer->slowmodeSecondsLeft()) {
- return tr::lng_slowmode_enabled(
- tr::now,
- lt_left,
- Ui::FormatDurationWordsSlowmode(left));
- } else if (_peer->slowmodeApplied()) {
- if (const auto item = _history->latestSendingMessage()) {
- if (const auto view = item->mainView()) {
- animatedScrollToItem(item->id);
- enqueueMessageHighlight({ item });
- }
- return tr::lng_slowmode_no_many(tr::now);
- }
- }
- return QString();
- }();
- if (text.isEmpty()) {
- return false;
- }
- controller()->showToast(text);
- return true;
- }
- void HistoryWidget::fieldTabbed() {
- if (_supportAutocomplete) {
- _supportAutocomplete->activate(_field.data());
- }
- }
- void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
- if (!_peer || !_canSendMessages) {
- return;
- } else if (showSlowmodeError()) {
- return;
- } else if (const auto error = result.result->getErrorOnSend(_history)) {
- Data::ShowSendErrorToast(controller(), _peer, error);
- return;
- }
- const auto withPaymentApproved = [=](int approved) {
- auto copy = result;
- copy.options.starsApproved = approved;
- sendInlineResult(copy);
- };
- const auto checked = checkSendPayment(
- 1,
- result.options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return;
- }
- controller()->sendingAnimation().appendSending(
- result.messageSendingFrom);
- auto action = prepareSendAction(result.options);
- action.generateLocal = true;
- session().api().sendInlineResult(
- result.bot,
- result.result.get(),
- action,
- result.messageSendingFrom.localId);
- clearFieldText();
- _saveDraftText = true;
- _saveDraftStart = crl::now();
- saveDraft();
- auto &bots = cRefRecentInlineBots();
- const auto index = bots.indexOf(result.bot);
- if (index) {
- if (index > 0) {
- bots.removeAt(index);
- } else if (bots.size() >= RecentInlineBotsLimit) {
- bots.resize(RecentInlineBotsLimit - 1);
- }
- bots.push_front(result.bot);
- session().local().writeRecentHashtagsAndBots();
- }
- hideSelectorControlsAnimated();
- setInnerFocus();
- }
- void HistoryWidget::updatePinnedViewer() {
- if (_firstLoadRequest
- || _delayedShowAtRequest
- || _scroll->isHidden()
- || !_history
- || !_historyInited
- || !_pinnedTracker) {
- return;
- }
- const auto visibleBottom = _scroll->scrollTop() + _scroll->height();
- auto [view, offset] = _list->findViewForPinnedTracking(visibleBottom);
- const auto lessThanId = !view
- ? (ServerMaxMsgId - 1)
- : (view->history() != _history)
- ? (view->data()->id + (offset > 0 ? 1 : 0) - ServerMaxMsgId)
- : (view->data()->id + (offset > 0 ? 1 : 0));
- const auto lastClickedId = !_pinnedClickedId
- ? (ServerMaxMsgId - 1)
- : (!_migrated || peerIsChannel(_pinnedClickedId.peer))
- ? _pinnedClickedId.msg
- : (_pinnedClickedId.msg - ServerMaxMsgId);
- if (_pinnedClickedId
- && lessThanId <= lastClickedId
- && !_scrollToAnimation.animating()) {
- _pinnedClickedId = FullMsgId();
- }
- if (_pinnedClickedId && !_minPinnedId) {
- _minPinnedId = Data::ResolveMinPinnedId(
- _peer,
- MsgId(0), // topicRootId
- _migrated ? _migrated->peer.get() : nullptr);
- }
- if (_pinnedClickedId
- && _minPinnedId
- && (_minPinnedId >= _pinnedClickedId)) {
- // After click on the last pinned message we should the top one.
- _pinnedTracker->trackAround(ServerMaxMsgId - 1);
- } else {
- _pinnedTracker->trackAround(std::min(lessThanId, lastClickedId));
- }
- }
- void HistoryWidget::checkLastPinnedClickedIdReset(
- int wasScrollTop,
- int nowScrollTop) {
- if (_firstLoadRequest
- || _delayedShowAtRequest
- || _scroll->isHidden()
- || !_history
- || !_historyInited) {
- return;
- }
- if (wasScrollTop < nowScrollTop && _pinnedClickedId) {
- // User scrolled down.
- _pinnedClickedId = FullMsgId();
- _minPinnedId = std::nullopt;
- updatePinnedViewer();
- }
- }
- void HistoryWidget::setupTranslateBar() {
- Expects(_history != nullptr);
- _translateBar = std::make_unique<HistoryView::TranslateBar>(
- this,
- controller(),
- _history);
- controller()->adaptive().oneColumnValue(
- ) | rpl::start_with_next([=, raw = _translateBar.get()](bool one) {
- raw->setShadowGeometryPostprocess([=](QRect geometry) {
- if (!one) {
- geometry.setLeft(geometry.left() + st::lineWidth);
- }
- return geometry;
- });
- }, _translateBar->lifetime());
- _translateBarHeight = 0;
- _translateBar->heightValue(
- ) | rpl::start_with_next([=](int height) {
- _topDelta = _preserveScrollTop ? 0 : (height - _translateBarHeight);
- _translateBarHeight = height;
- updateHistoryGeometry();
- updateControlsGeometry();
- _topDelta = 0;
- }, _translateBar->lifetime());
- orderWidgets();
- if (_showAnimation) {
- _translateBar->hide();
- }
- }
- void HistoryWidget::setupPinnedTracker() {
- Expects(_history != nullptr);
- _pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history);
- _pinnedBar = nullptr;
- checkPinnedBarState();
- }
- void HistoryWidget::checkPinnedBarState() {
- Expects(_pinnedTracker != nullptr);
- Expects(_list != nullptr);
- const auto hiddenId = _peer->canPinMessages()
- ? MsgId(0)
- : session().settings().hiddenPinnedMessageId(_peer->id);
- const auto currentPinnedId = Data::ResolveTopPinnedId(
- _peer,
- MsgId(0), // topicRootId
- _migrated ? _migrated->peer.get() : nullptr);
- const auto universalPinnedId = !currentPinnedId
- ? int32(0)
- : (_migrated && !peerIsChannel(currentPinnedId.peer))
- ? (currentPinnedId.msg - ServerMaxMsgId)
- : currentPinnedId.msg;
- if (universalPinnedId == hiddenId) {
- if (_pinnedBar) {
- _pinnedBar->setContent(rpl::single(Ui::MessageBarContent()));
- _pinnedTracker->reset();
- _list->setShownPinned(nullptr);
- _hidingPinnedBar = base::take(_pinnedBar);
- const auto raw = _hidingPinnedBar.get();
- base::call_delayed(st::defaultMessageBar.duration, this, [=] {
- if (_hidingPinnedBar.get() == raw) {
- clearHidingPinnedBar();
- }
- });
- }
- return;
- }
- if (_pinnedBar || !universalPinnedId) {
- return;
- }
- clearHidingPinnedBar();
- _pinnedBar = std::make_unique<Ui::PinnedBar>(this, [=] {
- return controller()->isGifPausedAtLeastFor(
- Window::GifPauseReason::Any);
- }, controller()->gifPauseLevelChanged());
- auto pinnedRefreshed = Info::Profile::SharedMediaCountValue(
- _peer,
- MsgId(0), // topicRootId
- nullptr,
- Storage::SharedMediaType::Pinned
- ) | rpl::distinct_until_changed(
- ) | rpl::map([=](int count) {
- if (_pinnedClickedId) {
- _pinnedClickedId = FullMsgId();
- _minPinnedId = std::nullopt;
- updatePinnedViewer();
- }
- return (count > 1);
- }) | rpl::distinct_until_changed();
- auto markupRefreshed = HistoryView::PinnedBarItemWithReplyMarkup(
- &session(),
- _pinnedTracker->shownMessageId());
- rpl::combine(
- rpl::duplicate(pinnedRefreshed),
- rpl::duplicate(markupRefreshed)
- ) | rpl::start_with_next([=](bool many, HistoryItem *item) {
- refreshPinnedBarButton(many, item);
- }, _pinnedBar->lifetime());
- _pinnedBar->setContent(rpl::combine(
- HistoryView::PinnedBarContent(
- &session(),
- _pinnedTracker->shownMessageId(),
- [bar = _pinnedBar.get()] { bar->customEmojiRepaint(); }),
- std::move(pinnedRefreshed),
- std::move(markupRefreshed)
- ) | rpl::map([=](Ui::MessageBarContent &&content, bool, HistoryItem*) {
- const auto id = (!content.title.isEmpty() || !content.text.empty())
- ? _pinnedTracker->currentMessageId().message
- : FullMsgId();
- if (const auto list = _list.data()) {
- // Sometimes we get here with non-empty content and id of
- // message that is being deleted right now. We get here in
- // the moment when _itemRemoved was already fired (so in
- // the _list the _pinnedItem is already cleared) and the
- // MessageUpdate::Flag::Destroyed being fired right now,
- // so the message is still in Data::Session. So we need to
- // call data().message() async, otherwise we get a nearly-
- // destroyed message from it and save the pointer in _list.
- crl::on_main(list, [=] {
- list->setShownPinned(session().data().message(id));
- });
- }
- return std::move(content);
- }));
- controller()->adaptive().oneColumnValue(
- ) | rpl::start_with_next([=, raw = _pinnedBar.get()](bool one) {
- raw->setShadowGeometryPostprocess([=](QRect geometry) {
- if (!one) {
- geometry.setLeft(geometry.left() + st::lineWidth);
- }
- return geometry;
- });
- }, _pinnedBar->lifetime());
- _pinnedBar->barClicks(
- ) | rpl::start_with_next([=] {
- const auto id = _pinnedTracker->currentMessageId();
- if (const auto item = session().data().message(id.message)) {
- controller()->showPeerHistory(
- item->history()->peer,
- Window::SectionShow::Way::Forward,
- item->id);
- if (const auto group = session().data().groups().find(item)) {
- // Hack for the case when a non-first item of an album
- // is pinned and we still want the 'show last after first'.
- _pinnedClickedId = group->items.front()->fullId();
- } else {
- _pinnedClickedId = id.message;
- }
- _minPinnedId = std::nullopt;
- updatePinnedViewer();
- }
- }, _pinnedBar->lifetime());
- _pinnedBarHeight = 0;
- _pinnedBar->heightValue(
- ) | rpl::start_with_next([=](int height) {
- _topDelta = _preserveScrollTop ? 0 : (height - _pinnedBarHeight);
- _pinnedBarHeight = height;
- updateHistoryGeometry();
- updateControlsGeometry();
- _topDelta = 0;
- }, _pinnedBar->lifetime());
- orderWidgets();
- if (_showAnimation) {
- _pinnedBar->hide();
- }
- }
- void HistoryWidget::clearHidingPinnedBar() {
- if (!_hidingPinnedBar) {
- return;
- }
- if (const auto delta = -_pinnedBarHeight) {
- _pinnedBarHeight = 0;
- setGeometryWithTopMoved(geometry(), delta);
- }
- _hidingPinnedBar = nullptr;
- }
- void HistoryWidget::checkMessagesTTL() {
- if (!_peer || !_peer->messagesTTL()) {
- if (_ttlInfo) {
- _ttlInfo = nullptr;
- updateControlsGeometry();
- updateControlsVisibility();
- }
- } else if (!_ttlInfo || _ttlInfo->peer() != _peer) {
- _ttlInfo = std::make_unique<HistoryView::Controls::TTLButton>(
- this,
- controller()->uiShow(),
- _peer);
- orderWidgets();
- updateControlsGeometry();
- updateControlsVisibility();
- }
- }
- void HistoryWidget::setChooseReportMessagesDetails(
- Data::ReportInput reportInput,
- Fn<void(std::vector<MsgId>)> callback) {
- if (!callback) {
- const auto refresh = _chooseForReport && _chooseForReport->active;
- _chooseForReport = nullptr;
- if (_list) {
- _list->clearChooseReportReason();
- }
- if (refresh) {
- clearSelected();
- updateControlsVisibility();
- updateControlsGeometry();
- updateTopBarChooseForReport();
- }
- } else {
- _chooseForReport = std::make_unique<ChooseMessagesForReport>(
- ChooseMessagesForReport{
- .reportInput = reportInput,
- .callback = std::move(callback) });
- }
- }
- void HistoryWidget::refreshPinnedBarButton(bool many, HistoryItem *item) {
- if (!_pinnedBar) {
- return; // It can be in process of hiding.
- }
- const auto openSection = [=] {
- const auto id = _pinnedTracker
- ? _pinnedTracker->currentMessageId()
- : HistoryView::PinnedId();
- if (!id.message) {
- return;
- }
- controller()->showSection(
- std::make_shared<HistoryView::PinnedMemento>(
- _history,
- ((!_migrated || peerIsChannel(id.message.peer))
- ? id.message.msg
- : (id.message.msg - ServerMaxMsgId))));
- };
- if (const auto replyMarkup = item ? item->inlineReplyMarkup() : nullptr) {
- const auto &rows = replyMarkup->data.rows;
- if ((rows.size() == 1) && (rows.front().size() == 1)) {
- const auto text = rows.front().front().text;
- if (!text.isEmpty()) {
- const auto &st = st::historyPinnedBotButton;
- auto button = object_ptr<Ui::RoundButton>(
- this,
- rpl::never<QString>(),
- st);
- const auto label = Ui::CreateChild<Ui::FlatLabel>(
- button.data(),
- text,
- st::historyPinnedBotLabel);
- if (label->width() > st::historyPinnedBotButtonMaxWidth) {
- label->resizeToWidth(st::historyPinnedBotButtonMaxWidth);
- }
- button->setFullWidth(label->width()
- + st.padding.left()
- + st.padding.right()
- + st.height);
- label->moveToLeft(
- st.padding.left() + st.height / 2,
- (button->height() - label->height()) / 2);
- label->setTextColorOverride(st.textFg->c);
- label->setAttribute(Qt::WA_TransparentForMouseEvents);
- button->setTextTransform(
- Ui::RoundButton::TextTransform::NoTransform);
- button->setFullRadius(true);
- button->setClickedCallback([=] {
- Api::ActivateBotCommand(
- _list->prepareClickHandlerContext(item->fullId()),
- 0,
- 0);
- });
- struct State {
- base::unique_qptr<Ui::PopupMenu> menu;
- };
- const auto state = button->lifetime().make_state<State>();
- _pinnedBar->contextMenuRequested(
- ) | rpl::start_with_next([=, raw = button.data()] {
- state->menu = base::make_unique_q<Ui::PopupMenu>(raw);
- state->menu->addAction(
- tr::lng_settings_events_pinned(tr::now),
- openSection);
- state->menu->popup(QCursor::pos());
- }, button->lifetime());
- _pinnedBar->setRightButton(std::move(button));
- return;
- }
- }
- }
- const auto close = !many;
- auto button = object_ptr<Ui::IconButton>(
- this,
- close ? st::historyReplyCancel : st::historyPinnedShowAll);
- button->clicks(
- ) | rpl::start_with_next([=] {
- if (close) {
- hidePinnedMessage();
- } else {
- openSection();
- }
- }, button->lifetime());
- _pinnedBar->setRightButton(std::move(button));
- }
- void HistoryWidget::setupGroupCallBar() {
- Expects(_history != nullptr);
- const auto peer = _history->peer;
- if (!peer->isChannel() && !peer->isChat()) {
- _groupCallBar = nullptr;
- return;
- }
- _groupCallBar = std::make_unique<Ui::GroupCallBar>(
- this,
- HistoryView::GroupCallBarContentByPeer(
- peer,
- st::historyGroupCallUserpics.size,
- false),
- Core::App().appDeactivatedValue());
- controller()->adaptive().oneColumnValue(
- ) | rpl::start_with_next([=](bool one) {
- _groupCallBar->setShadowGeometryPostprocess([=](QRect geometry) {
- if (!one) {
- geometry.setLeft(geometry.left() + st::lineWidth);
- }
- return geometry;
- });
- }, _groupCallBar->lifetime());
- rpl::merge(
- _groupCallBar->barClicks(),
- _groupCallBar->joinClicks()
- ) | rpl::start_with_next([=] {
- const auto peer = _history->peer;
- if (peer->groupCall()) {
- controller()->startOrJoinGroupCall(peer, {});
- }
- }, _groupCallBar->lifetime());
- _groupCallBarHeight = 0;
- _groupCallBar->heightValue(
- ) | rpl::start_with_next([=](int height) {
- _topDelta = _preserveScrollTop ? 0 : (height - _groupCallBarHeight);
- _groupCallBarHeight = height;
- updateHistoryGeometry();
- updateControlsGeometry();
- _topDelta = 0;
- }, _groupCallBar->lifetime());
- orderWidgets();
- if (_showAnimation) {
- _groupCallBar->hide();
- }
- }
- void HistoryWidget::setupRequestsBar() {
- Expects(_history != nullptr);
- const auto peer = _history->peer;
- if (!peer->isChannel() && !peer->isChat()) {
- _requestsBar = nullptr;
- return;
- }
- _requestsBar = std::make_unique<Ui::RequestsBar>(
- this,
- HistoryView::RequestsBarContentByPeer(
- peer,
- st::historyRequestsUserpics.size,
- false));
- controller()->adaptive().oneColumnValue(
- ) | rpl::start_with_next([=](bool one) {
- _requestsBar->setShadowGeometryPostprocess([=](QRect geometry) {
- if (!one) {
- geometry.setLeft(geometry.left() + st::lineWidth);
- }
- return geometry;
- });
- }, _requestsBar->lifetime());
- _requestsBar->barClicks(
- ) | rpl::start_with_next([=] {
- RequestsBoxController::Start(controller(), _peer);
- }, _requestsBar->lifetime());
- _requestsBarHeight = 0;
- _requestsBar->heightValue(
- ) | rpl::start_with_next([=](int height) {
- _topDelta = _preserveScrollTop ? 0 : (height - _requestsBarHeight);
- _requestsBarHeight = height;
- updateHistoryGeometry();
- updateControlsGeometry();
- _topDelta = 0;
- }, _requestsBar->lifetime());
- orderWidgets();
- if (_showAnimation) {
- _requestsBar->hide();
- }
- }
- void HistoryWidget::requestMessageData(MsgId msgId) {
- if (!_peer) {
- return;
- }
- const auto peer = _peer;
- const auto callback = crl::guard(this, [=] {
- messageDataReceived(peer, msgId);
- });
- session().api().requestMessageData(_peer, msgId, callback);
- }
- bool HistoryWidget::checkSponsoredMessageBarVisibility() const {
- const auto h = _list->height()
- - (_kbScroll->isHidden() ? 0 : _kbScroll->height());
- return (h > _scroll->height());
- }
- void HistoryWidget::requestSponsoredMessageBar() {
- if (!_history || !session().sponsoredMessages().isTopBarFor(_history)) {
- return;
- }
- const auto checkState = [=, this] {
- using State = Data::SponsoredMessages::State;
- const auto state = session().sponsoredMessages().state(
- _history);
- _sponsoredMessagesStateKnown = (state != State::None);
- if (state == State::AppendToTopBar) {
- createSponsoredMessageBar();
- if (checkSponsoredMessageBarVisibility()) {
- _sponsoredMessageBar->toggle(true, anim::type::normal);
- } else {
- auto &lifetime = _sponsoredMessageBar->lifetime();
- const auto heightLifetime
- = lifetime.make_state<rpl::lifetime>();
- _list->heightValue(
- ) | rpl::start_with_next([=, this] {
- if (_sponsoredMessageBar->toggled()) {
- heightLifetime->destroy();
- } else if (checkSponsoredMessageBarVisibility()) {
- _sponsoredMessageBar->toggle(
- true,
- anim::type::normal);
- heightLifetime->destroy();
- }
- }, *heightLifetime);
- }
- }
- };
- const auto history = _history;
- session().sponsoredMessages().request(
- _history,
- crl::guard(this, [=, this] {
- if (history == _history) {
- checkState();
- }
- }));
- }
- void HistoryWidget::checkSponsoredMessageBar() {
- if (!_history || !session().sponsoredMessages().isTopBarFor(_history)) {
- return;
- }
- const auto state = session().sponsoredMessages().state(_history);
- if (state == Data::SponsoredMessages::State::AppendToTopBar) {
- if (checkSponsoredMessageBarVisibility()) {
- if (!_sponsoredMessageBar) {
- createSponsoredMessageBar();
- }
- _sponsoredMessageBar->toggle(true, anim::type::instant);
- }
- }
- }
- void HistoryWidget::createSponsoredMessageBar() {
- _sponsoredMessageBar = base::make_unique_q<Ui::SlideWrap<>>(
- this,
- object_ptr<Ui::RpWidget>(this));
- _sponsoredMessageBar->entity()->resizeToWidth(_scroll->width());
- const auto maybeFullId = session().sponsoredMessages().fillTopBar(
- _history,
- _sponsoredMessageBar->entity());
- session().sponsoredMessages().itemRemoved(
- maybeFullId
- ) | rpl::start_with_next([this] {
- _sponsoredMessageBar->toggle(false, anim::type::normal);
- _sponsoredMessageBar->shownValue() | rpl::filter(
- !rpl::mappers::_1
- ) | rpl::start_with_next([this] {
- _sponsoredMessageBar = nullptr;
- }, _sponsoredMessageBar->lifetime());
- }, _sponsoredMessageBar->lifetime());
- if (maybeFullId) {
- const auto viewLifetime
- = _sponsoredMessageBar->lifetime().make_state<rpl::lifetime>();
- rpl::combine(
- _sponsoredMessageBar->entity()->heightValue(),
- _sponsoredMessageBar->heightValue()
- ) | rpl::filter(
- rpl::mappers::_1 == rpl::mappers::_2
- ) | rpl::start_with_next([=] {
- session().sponsoredMessages().view(maybeFullId);
- viewLifetime->destroy();
- }, *viewLifetime);
- }
- _sponsoredMessageBarHeight = 0;
- _sponsoredMessageBar->heightValue(
- ) | rpl::start_with_next([=](int height) {
- _topDelta = _preserveScrollTop
- ? 0
- : (height - _sponsoredMessageBarHeight);
- _sponsoredMessageBarHeight = height;
- updateHistoryGeometry();
- updateControlsGeometry();
- _topDelta = 0;
- }, _sponsoredMessageBar->lifetime());
- _sponsoredMessageBar->toggle(false, anim::type::instant);
- }
- bool HistoryWidget::sendExistingDocument(
- not_null<DocumentData*> document,
- Api::MessageToSend messageToSend,
- std::optional<MsgId> localId) {
- const auto error = _peer
- ? Data::RestrictionError(_peer, ChatRestriction::SendStickers)
- : Data::SendError();
- if (error) {
- Data::ShowSendErrorToast(controller(), _peer, error);
- return false;
- } else if (!_peer
- || !_canSendMessages
- || showSlowmodeError()
- || ShowSendPremiumError(controller(), document)) {
- return false;
- }
- const auto withPaymentApproved = [=](int approved) {
- auto copy = messageToSend;
- copy.action.options.starsApproved = approved;
- sendExistingDocument(document, std::move(copy), localId);
- };
- const auto checked = checkSendPayment(
- 1,
- messageToSend.action.options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return false;
- }
- Api::SendExistingDocument(
- std::move(messageToSend),
- document,
- localId);
- if (_autocomplete && _autocomplete->stickersShown()) {
- clearFieldText();
- //_saveDraftText = true;
- //_saveDraftStart = crl::now();
- //saveDraft();
- // won't be needed if SendInlineBotResult will clear the cloud draft
- saveCloudDraft();
- }
- hideSelectorControlsAnimated();
- setInnerFocus();
- return true;
- }
- bool HistoryWidget::sendExistingPhoto(
- not_null<PhotoData*> photo,
- Api::SendOptions options) {
- const auto error = _peer
- ? Data::RestrictionError(_peer, ChatRestriction::SendPhotos)
- : Data::SendError();
- if (error) {
- Data::ShowSendErrorToast(controller(), _peer, error);
- return false;
- } else if (!_peer || !_canSendMessages) {
- return false;
- } else if (showSlowmodeError()) {
- return false;
- }
- const auto withPaymentApproved = [=](int approved) {
- auto copy = options;
- copy.starsApproved = approved;
- sendExistingPhoto(photo, copy);
- };
- const auto checked = checkSendPayment(
- 1,
- options.starsApproved,
- withPaymentApproved);
- if (!checked) {
- return false;
- }
- Api::SendExistingPhoto(
- Api::MessageToSend(prepareSendAction(options)),
- photo);
- hideSelectorControlsAnimated();
- setInnerFocus();
- return true;
- }
- void HistoryWidget::showInfoTooltip(
- const TextWithEntities &text,
- Fn<void()> hiddenCallback) {
- _topToast.show(
- _scroll.data(),
- &session(),
- text,
- std::move(hiddenCallback));
- }
- void HistoryWidget::showPremiumStickerTooltip(
- not_null<const HistoryView::Element*> view) {
- if (const auto media = view->data()->media()) {
- if (const auto document = media->document()) {
- showPremiumToast(document);
- }
- }
- }
- void HistoryWidget::showPremiumToast(not_null<DocumentData*> document) {
- if (!_stickerToast) {
- _stickerToast = std::make_unique<HistoryView::StickerToast>(
- controller(),
- this,
- [=] { _stickerToast = nullptr; });
- }
- _stickerToast->showFor(document);
- }
- void HistoryWidget::checkCharsCount() {
- _fieldCharsCountManager.setCount(Ui::ComputeFieldCharacterCount(_field));
- checkCharsLimitation();
- }
- void HistoryWidget::checkCharsLimitation() {
- if (!_history || !_editMsgId) {
- _charsLimitation = nullptr;
- return;
- }
- const auto item = session().data().message(_history->peer, _editMsgId);
- if (!item) {
- _charsLimitation = nullptr;
- return;
- }
- const auto hasMediaWithCaption = item->media()
- && item->media()->allowsEditCaption();
- const auto maxCaptionSize = !hasMediaWithCaption
- ? MaxMessageSize
- : Data::PremiumLimits(&session()).captionLengthCurrent();
- const auto remove = _fieldCharsCountManager.count() - maxCaptionSize;
- if (remove > 0) {
- if (!_charsLimitation) {
- _charsLimitation = base::make_unique_q<CharactersLimitLabel>(
- this,
- _send.get(),
- style::al_bottom);
- _charsLimitation->show();
- Data::AmPremiumValue(
- &session()
- ) | rpl::start_with_next([=] {
- checkCharsLimitation();
- }, _charsLimitation->lifetime());
- }
- _charsLimitation->setLeft(remove);
- } else {
- _charsLimitation = nullptr;
- }
- }
- void HistoryWidget::setFieldText(
- const TextWithTags &textWithTags,
- TextUpdateEvents events,
- FieldHistoryAction fieldHistoryAction) {
- _textUpdateEvents = events;
- _field->setTextWithTags(textWithTags, fieldHistoryAction);
- auto cursor = _field->textCursor();
- cursor.movePosition(QTextCursor::End);
- _field->setTextCursor(cursor);
- _textUpdateEvents = TextUpdateEvent::SaveDraft
- | TextUpdateEvent::SendTyping;
- checkCharsCount();
- if (_preview) {
- _preview->checkNow(false);
- }
- }
- void HistoryWidget::clearFieldText(
- TextUpdateEvents events,
- FieldHistoryAction fieldHistoryAction) {
- setFieldText(TextWithTags(), events, fieldHistoryAction);
- }
- void HistoryWidget::replyToMessage(FullReplyTo id) {
- if (const auto item = session().data().message(id.messageId)) {
- if (CanSendReply(item) && !base::IsCtrlPressed()) {
- replyToMessage(item, id.quote, id.quoteOffset);
- } else if (item->allowsForward()) {
- const auto show = controller()->uiShow();
- HistoryView::Controls::ShowReplyToChatBox(show, id);
- } else {
- controller()->showToast(
- tr::lng_error_cant_reply_other(tr::now));
- }
- }
- }
- void HistoryWidget::replyToMessage(
- not_null<HistoryItem*> item,
- TextWithEntities quote,
- int quoteOffset) {
- if (isJoinChannel()) {
- return;
- }
- _processingReplyTo = {
- .messageId = item->fullId(),
- .quote = quote,
- .quoteOffset = quoteOffset,
- };
- _processingReplyItem = item;
- processReply();
- }
- void HistoryWidget::processReply() {
- const auto processContinue = [=] {
- return crl::guard(_list, [=] {
- if (!_peer || !_processingReplyTo) {
- return;
- } else if (!_processingReplyItem) {
- _processingReplyItem = _peer->owner().message(
- _processingReplyTo.messageId);
- if (!_processingReplyItem) {
- _processingReplyTo = {};
- } else {
- processReply();
- }
- }
- });
- };
- const auto processCancel = [=] {
- _processingReplyTo = {};
- _processingReplyItem = nullptr;
- };
- if (!_peer || !_processingReplyTo) {
- return processCancel();
- } else if (!_processingReplyItem) {
- session().api().requestMessageData(
- session().data().peer(_processingReplyTo.messageId.peer),
- _processingReplyTo.messageId.msg,
- processContinue());
- return;
- #if 0 // Now we can "reply" to old legacy group messages.
- } else if (_processingReplyItem->history() == _migrated) {
- if (_processingReplyItem->isService()) {
- controller()->showToast(tr::lng_reply_cant(tr::now));
- } else {
- const auto itemId = _processingReplyItem->fullId();
- controller()->show(
- Ui::MakeConfirmBox({
- .text = tr::lng_reply_cant_forward(),
- .confirmed = crl::guard(this, [=] {
- controller()->content()->setForwardDraft(
- _history,
- { .ids = { 1, itemId } });
- }),
- .confirmText = tr::lng_selected_forward(),
- }));
- }
- return processCancel();
- #endif
- } else if (!_processingReplyItem->isRegular()) {
- return processCancel();
- } else if (const auto forum = _peer->forum()
- ; forum && _processingReplyItem->history() == _history) {
- const auto topicRootId = _processingReplyItem->topicRootId();
- if (forum->topicDeleted(topicRootId)) {
- return processCancel();
- } else if (const auto topic = forum->topicFor(topicRootId)) {
- if (!Data::CanSendAnything(topic)) {
- return processCancel();
- }
- } else {
- forum->requestTopic(topicRootId, processContinue());
- }
- } else if (!Data::CanSendAnything(_peer)) {
- return processCancel();
- }
- setReplyFieldsFromProcessing();
- }
- void HistoryWidget::setReplyFieldsFromProcessing() {
- if (!_processingReplyTo || !_processingReplyItem) {
- return;
- }
- if (_composeSearch) {
- _composeSearch->hideAnimated();
- }
- const auto id = base::take(_processingReplyTo);
- const auto item = base::take(_processingReplyItem);
- if (_editMsgId) {
- if (const auto localDraft = _history->localDraft({})) {
- localDraft->reply = id;
- } else {
- _history->setLocalDraft(std::make_unique<Data::Draft>(
- TextWithTags(),
- id,
- MessageCursor(),
- Data::WebPageDraft()));
- }
- } else {
- _replyEditMsg = item;
- _replyTo = id;
- updateReplyEditText(_replyEditMsg);
- updateCanSendMessage();
- updateBotKeyboard();
- updateReplyToName();
- updateControlsVisibility();
- updateControlsGeometry();
- updateField();
- refreshTopBarActiveChat();
- }
- _saveDraftText = true;
- _saveDraftStart = crl::now();
- saveDraft();
- setInnerFocus();
- }
- void HistoryWidget::editMessage(
- not_null<HistoryItem*> item,
- const TextSelection &selection) {
- if (_chooseTheme) {
- toggleChooseChatTheme(_peer);
- } else if (_voiceRecordBar->isActive()) {
- controller()->showToast(tr::lng_edit_caption_voice(tr::now));
- return;
- } else if (_composeSearch) {
- _composeSearch->hideAnimated();
- }
- if (isRecording()) {
- // Just fix some strange inconsistency.
- _send->clearState();
- }
- if (!_editMsgId) {
- if (_replyTo || !_field->empty()) {
- _history->setLocalDraft(std::make_unique<Data::Draft>(
- _field,
- _replyTo,
- _preview->draft()));
- } else {
- _history->clearLocalDraft({});
- }
- }
- const auto editData = PrepareEditText(item);
- const auto cursor = MessageCursor {
- int(editData.text.size()),
- int(editData.text.size()),
- Ui::kQFixedMax
- };
- const auto previewDraft = Data::WebPageDraft::FromItem(item);
- _history->setLocalEditDraft(std::make_unique<Data::Draft>(
- editData,
- FullReplyTo{ item->fullId() },
- cursor,
- previewDraft));
- applyDraft();
- updateBotKeyboard();
- if (fieldOrDisabledShown()) {
- _fieldBarCancel->show();
- }
- updateFieldPlaceholder();
- updateMouseTracking();
- updateReplyToName();
- updateControlsGeometry();
- updateField();
- SelectTextInFieldWithMargins(_field, selection);
- _saveDraftText = true;
- _saveDraftStart = crl::now();
- saveDraft();
- setInnerFocus();
- }
- void HistoryWidget::fillSenderUserpicMenu(
- not_null<Ui::PopupMenu*> menu,
- not_null<PeerData*> peer) {
- const auto inGroup = _peer && (_peer->isChat() || _peer->isMegagroup());
- Window::FillSenderUserpicMenu(
- controller(),
- peer,
- (inGroup && _canSendTexts) ? _field.data() : nullptr,
- inGroup ? _peer->owner().history(_peer) : Dialogs::Key(),
- Ui::Menu::CreateAddActionCallback(menu));
- }
- void HistoryWidget::hidePinnedMessage() {
- Expects(_pinnedBar != nullptr);
- const auto id = _pinnedTracker->currentMessageId();
- if (!id.message) {
- return;
- }
- if (_peer->canPinMessages()) {
- Window::ToggleMessagePinned(controller(), id.message, false);
- } else {
- const auto callback = [=] {
- if (_pinnedTracker) {
- checkPinnedBarState();
- }
- };
- Window::HidePinnedBar(
- controller(),
- _peer,
- MsgId(0), // topicRootId
- crl::guard(this, callback));
- }
- }
- bool HistoryWidget::lastForceReplyReplied(const FullMsgId &replyTo) const {
- return _peer
- && (replyTo.peer == _peer->id)
- && _keyboard->forceReply()
- && (_keyboard->forMsgId()
- == FullMsgId(_peer->id, _history->lastKeyboardId))
- && _keyboard->forMsgId().msg == replyTo.msg;
- }
- bool HistoryWidget::lastForceReplyReplied() const {
- return _peer
- && _keyboard->forceReply()
- && _keyboard->forMsgId() == replyTo().messageId
- && (_keyboard->forMsgId()
- == FullMsgId(_peer->id, _history->lastKeyboardId));
- }
- bool HistoryWidget::cancelReply(bool lastKeyboardUsed) {
- bool wasReply = false;
- if (_replyTo) {
- wasReply = true;
- _processingReplyItem = _replyEditMsg = nullptr;
- _processingReplyTo = _replyTo = FullReplyTo();
- mouseMoveEvent(0);
- if (!readyToForward()
- && !_previewDrawPreview
- && !_kbReplyTo) {
- _fieldBarCancel->hide();
- updateMouseTracking();
- }
- updateBotKeyboard();
- refreshTopBarActiveChat();
- updateCanSendMessage();
- updateControlsVisibility();
- updateControlsGeometry();
- update();
- } else if (const auto localDraft
- = (_history ? _history->localDraft({}) : nullptr)) {
- if (localDraft->reply) {
- if (localDraft->textWithTags.text.isEmpty()) {
- _history->clearLocalDraft({});
- } else {
- localDraft->reply = {};
- }
- }
- }
- if (wasReply) {
- _saveDraftText = true;
- _saveDraftStart = crl::now();
- saveDraft();
- }
- if (!_editMsgId
- && _keyboard->singleUse()
- && _keyboard->forceReply()
- && lastKeyboardUsed) {
- if (_kbReplyTo) {
- toggleKeyboard(false);
- }
- }
- return wasReply;
- }
- void HistoryWidget::cancelReplyAfterMediaSend(bool lastKeyboardUsed) {
- if (cancelReply(lastKeyboardUsed)) {
- saveCloudDraft();
- }
- }
- int HistoryWidget::countMembersDropdownHeightMax() const {
- auto result = height()
- - rect::m::sum::v(st::membersInnerDropdown.padding);
- result -= _tabbedSelectorToggle->height();
- accumulate_min(result, st::membersInnerHeightMax);
- return result;
- }
- void HistoryWidget::cancelEdit() {
- if (!_editMsgId) {
- return;
- }
- _canReplaceMedia = _canAddMedia = false;
- _photoEditMedia = nullptr;
- updateReplaceMediaButton();
- _replyEditMsg = nullptr;
- setEditMsgId(0);
- _history->clearLocalEditDraft({});
- applyDraft();
- if (_saveEditMsgRequestId) {
- _history->session().api().request(_saveEditMsgRequestId).cancel();
- _saveEditMsgRequestId = 0;
- }
- _saveDraftText = true;
- _saveDraftStart = crl::now();
- saveDraft();
- mouseMoveEvent(nullptr);
- if (!readyToForward()
- && !_previewDrawPreview
- && !replyTo()) {
- _fieldBarCancel->hide();
- updateMouseTracking();
- }
- auto old = _textUpdateEvents;
- _textUpdateEvents = 0;
- fieldChanged();
- _textUpdateEvents = old;
- updateControlsVisibility();
- updateBotKeyboard();
- updateFieldPlaceholder();
- updateControlsGeometry();
- update();
- }
- void HistoryWidget::cancelFieldAreaState() {
- controller()->hideLayer();
- if (_previewDrawPreview) {
- _preview->apply({ .removed = true });
- } else if (_editMsgId) {
- cancelEdit();
- } else if (_replyTo) {
- cancelReply();
- } else if (readyToForward()) {
- _history->setForwardDraft(MsgId(), {});
- } else if (_kbReplyTo) {
- toggleKeyboard();
- }
- }
- void HistoryWidget::fullInfoUpdated() {
- auto refresh = false;
- if (_list) {
- if (updateCanSendMessage()) {
- refresh = true;
- }
- if (_autocomplete) {
- _autocomplete->requestRefresh();
- }
- _list->refreshAboutView();
- _list->updateBotInfo();
- handlePeerUpdate();
- checkSuggestToGigagroup();
- const auto hasNonEmpty = _history->findFirstNonEmpty();
- const auto readyForBotStart = hasNonEmpty
- || (_history->loadedAtTop() && _history->loadedAtBottom());
- if (readyForBotStart && clearMaybeSendStart() && hasNonEmpty) {
- sendBotStartCommand();
- }
- refreshGiftToChannelShown();
- }
- if (updateCmdStartShown()) {
- refresh = true;
- } else if (!_scroll->isHidden() && _unblock->isHidden() == isBlocked()) {
- refresh = true;
- }
- if (refresh) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- }
- void HistoryWidget::handlePeerUpdate() {
- bool resize = false;
- updateSendRestriction();
- updateHistoryGeometry();
- if (_peer->isChat() && _peer->asChat()->noParticipantInfo()) {
- session().api().requestFullPeer(_peer);
- } else if (_peer->isUser()
- && ((_peer->asUser()->blockStatus() == UserData::BlockStatus::Unknown)
- || (_peer->asUser()->callsStatus()
- == UserData::CallsStatus::Unknown))) {
- session().api().requestFullPeer(_peer);
- } else if (auto channel = _peer->asMegagroup()) {
- if (!channel->mgInfo->botStatus) {
- session().api().chatParticipants().requestBots(channel);
- }
- if (!channel->mgInfo->adminsLoaded) {
- session().api().chatParticipants().requestAdmins(channel);
- }
- }
- if (!_showAnimation) {
- const auto blockChanged = (_unblock->isHidden() == isBlocked());
- if (blockChanged
- || (!isBlocked()
- && (_joinChannel->isHidden() == isJoinChannel()))) {
- resize = true;
- }
- if (updateCanSendMessage()) {
- resize = true;
- }
- if (blockChanged) {
- _list->refreshAboutView(true);
- _list->updateBotInfo();
- }
- updateControlsVisibility();
- if (resize) {
- updateControlsGeometry();
- }
- }
- }
- bool HistoryWidget::updateCanSendMessage() {
- if (!_peer) {
- return false;
- }
- const auto topic = resolveReplyToTopic();
- const auto allWithoutPolls = Data::AllSendRestrictions()
- & ~ChatRestriction::SendPolls;
- const auto newCanSendMessages = topic
- ? Data::CanSendAnyOf(topic, allWithoutPolls)
- : Data::CanSendAnyOf(_peer, allWithoutPolls);
- const auto newCanSendTexts = topic
- ? Data::CanSend(topic, ChatRestriction::SendOther)
- : Data::CanSend(_peer, ChatRestriction::SendOther);
- if (_canSendMessages == newCanSendMessages
- && _canSendTexts == newCanSendTexts) {
- return false;
- }
- _canSendMessages = newCanSendMessages;
- _canSendTexts = newCanSendTexts;
- if (!_canSendMessages) {
- cancelReply();
- }
- refreshScheduledToggle();
- refreshSilentToggle();
- return true;
- }
- void HistoryWidget::forwardSelected() {
- if (!_list) {
- return;
- }
- const auto weak = Ui::MakeWeak(this);
- Window::ShowForwardMessagesBox(controller(), getSelectedItems(), [=] {
- if (const auto strong = weak.data()) {
- strong->clearSelected();
- }
- });
- }
- void HistoryWidget::confirmDeleteSelected() {
- if (!_list) return;
- auto ids = _list->getSelectedItems();
- if (ids.empty()) {
- return;
- }
- const auto items = session().data().idsToItems(ids);
- if (CanCreateModerateMessagesBox(items)) {
- controller()->show(Box(
- CreateModerateMessagesBox,
- items,
- crl::guard(this, [=] { clearSelected(); })));
- } else {
- auto box = Box<DeleteMessagesBox>(&session(), std::move(ids));
- box->setDeleteConfirmedCallback(crl::guard(this, [=] {
- clearSelected();
- }));
- controller()->show(std::move(box));
- }
- }
- void HistoryWidget::escape() {
- if (_composeSearch) {
- if (_nonEmptySelection) {
- clearSelected();
- } else {
- _composeSearch->hideAnimated();
- }
- } else if (_chooseForReport) {
- controller()->clearChooseReportMessages();
- } else if (_nonEmptySelection && _list) {
- clearSelected();
- } else if (_isInlineBot) {
- cancelInlineBot();
- } else if (_editMsgId) {
- if (_replyEditMsg
- && EditTextChanged(_replyEditMsg, _field->getTextWithTags())) {
- controller()->show(Ui::MakeConfirmBox({
- .text = tr::lng_cancel_edit_post_sure(),
- .confirmed = crl::guard(this, [this](Fn<void()> &&close) {
- if (_editMsgId) {
- cancelEdit();
- close();
- }
- }),
- .confirmText = tr::lng_cancel_edit_post_yes(),
- .cancelText = tr::lng_cancel_edit_post_no(),
- }));
- } else {
- cancelEdit();
- }
- } else if (_autocomplete && !_autocomplete->isHidden()) {
- _autocomplete->hideAnimated();
- } else if (_replyTo && _field->getTextWithTags().text.isEmpty()) {
- cancelReply();
- } else if (auto &voice = _voiceRecordBar; voice->isActive()) {
- voice->showDiscardBox(nullptr, anim::type::normal);
- } else {
- _cancelRequests.fire({});
- }
- }
- void HistoryWidget::clearSelected() {
- if (_list) {
- _list->clearSelected();
- }
- }
- HistoryItem *HistoryWidget::getItemFromHistoryOrMigrated(
- MsgId genericMsgId) const {
- return (genericMsgId < 0 && -genericMsgId < ServerMaxMsgId && _migrated)
- ? session().data().message(_migrated->peer, -genericMsgId)
- : _peer
- ? session().data().message(_peer, genericMsgId)
- : nullptr;
- }
- MessageIdsList HistoryWidget::getSelectedItems() const {
- return _list ? _list->getSelectedItems() : MessageIdsList();
- }
- void HistoryWidget::updateTopBarChooseForReport() {
- if (_chooseForReport && _chooseForReport->active) {
- _topBar->showChooseMessagesForReport(
- _chooseForReport->reportInput);
- } else {
- _topBar->clearChooseMessagesForReport();
- }
- updateTopBarSelection();
- updateControlsVisibility();
- updateControlsGeometry();
- }
- void HistoryWidget::updateTopBarSelection() {
- if (!_list) {
- _topBar->showSelected(HistoryView::TopBarWidget::SelectedState {});
- return;
- }
- auto selectedState = _list->getSelectionState();
- _nonEmptySelection = (selectedState.count > 0)
- || selectedState.textSelected;
- _topBar->showSelected(selectedState);
- if ((selectedState.count > 0) && _composeSearch) {
- _composeSearch->hideAnimated();
- }
- const auto transparent = Qt::WA_TransparentForMouseEvents;
- if (selectedState.count == 0) {
- _reportMessages->clearState();
- _reportMessages->setAttribute(transparent);
- _reportMessages->setColorOverride(st::windowSubTextFg->c);
- } else if (_reportMessages->testAttribute(transparent)) {
- _reportMessages->setAttribute(transparent, false);
- _reportMessages->setColorOverride(std::nullopt);
- }
- _reportMessages->setText(Ui::Text::Upper(selectedState.count
- ? tr::lng_report_messages_count(
- tr::now,
- lt_count,
- selectedState.count)
- : tr::lng_report_messages_none(tr::now)));
- updateControlsVisibility();
- updateHistoryGeometry();
- if (!controller()->isLayerShown()
- && !Core::App().passcodeLocked()) {
- if (isSearching() && !_nonEmptySelection) {
- _composeSearch->setInnerFocus();
- } else if (_nonEmptySelection
- || (_list && _list->wasSelectedText())
- || isRecording()
- || isBotStart()
- || isBlocked()
- || (!_canSendTexts && !_editMsgId)) {
- _list->setFocus();
- } else {
- _field->setFocus();
- }
- }
- _topBar->update();
- update();
- }
- void HistoryWidget::messageDataReceived(
- not_null<PeerData*> peer,
- MsgId msgId) {
- if (!_peer || _peer != peer || !msgId) {
- return;
- } else if (_editMsgId == msgId
- || (_replyTo.messageId == FullMsgId(peer->id, msgId))) {
- updateReplyEditTexts(true);
- if (_editMsgId == msgId) {
- _preview->setDisabled(_editMsgId
- && _replyEditMsg
- && _replyEditMsg->media()
- && !_replyEditMsg->media()->webpage());
- }
- }
- }
- void HistoryWidget::updateReplyEditText(not_null<HistoryItem*> item) {
- const auto context = Core::TextContext({
- .session = &session(),
- .repaint = [=] { updateField(); },
- });
- _replyEditMsgText.setMarkedText(
- st::defaultTextStyle,
- ((_editMsgId || _replyTo.quote.empty())
- ? item->inReplyText()
- : _replyTo.quote),
- Ui::DialogTextOptions(),
- context);
- if (fieldOrDisabledShown() || isRecording()) {
- _fieldBarCancel->show();
- updateMouseTracking();
- }
- }
- void HistoryWidget::updateReplyEditTexts(bool force) {
- if (!force) {
- if (_replyEditMsg || (!_editMsgId && !_replyTo)) {
- return;
- }
- }
- if (!_replyEditMsg && _peer) {
- _replyEditMsg = session().data().message(
- _editMsgId ? _peer->id : _replyTo.messageId.peer,
- _editMsgId ? _editMsgId : _replyTo.messageId.msg);
- if (!_editMsgId) {
- updateFieldPlaceholder();
- }
- }
- if (_replyEditMsg) {
- const auto editMedia = _editMsgId
- ? _replyEditMsg->media()
- : nullptr;
- if (_editMsgId && _replyEditMsg) {
- _mediaEditManager.start(_replyEditMsg);
- }
- _canReplaceMedia = _editMsgId && _replyEditMsg->allowsEditMedia();
- if (editMedia && editMedia->allowsEditMedia()) {
- _canAddMedia = false;
- } else {
- _canAddMedia = base::take(_canReplaceMedia);
- }
- if (_canReplaceMedia || _canAddMedia) {
- // Invalidate the button, maybe icon has changed.
- _replaceMedia.destroy();
- }
- _photoEditMedia = (_canReplaceMedia
- && editMedia->photo()
- && !editMedia->photo()->isNull())
- ? editMedia->photo()->createMediaView()
- : nullptr;
- if (_photoEditMedia) {
- _photoEditMedia->wanted(
- Data::PhotoSize::Large,
- _replyEditMsg->fullId());
- }
- if (updateReplaceMediaButton()) {
- updateControlsVisibility();
- updateControlsGeometry();
- }
- updateReplyEditText(_replyEditMsg);
- updateBotKeyboard();
- updateReplyToName();
- updateField();
- } else if (force) {
- if (_editMsgId) {
- cancelEdit();
- } else {
- cancelReply();
- }
- }
- }
- void HistoryWidget::updateForwarding() {
- _forwardPanel->update(_history, _history
- ? _history->resolveForwardDraft(MsgId())
- : Data::ResolvedForwardDraft());
- updateControlsVisibility();
- updateControlsGeometry();
- }
- void HistoryWidget::updateReplyToName() {
- if (!_history || _editMsgId) {
- return;
- } else if (!_replyEditMsg && (_replyTo || !_kbReplyTo)) {
- return;
- }
- const auto context = Core::TextContext({
- .session = &_history->session(),
- .customEmojiLoopLimit = 1,
- });
- const auto to = _replyEditMsg ? _replyEditMsg : _kbReplyTo;
- const auto replyToQuote = _replyTo && !_replyTo.quote.empty();
- _replyToName.setMarkedText(
- st::fwdTextStyle,
- HistoryView::Reply::ComposePreviewName(_history, to, replyToQuote),
- Ui::NameTextOptions(),
- context);
- }
- void HistoryWidget::updateField() {
- if (_repaintFieldScheduled) {
- return;
- }
- _repaintFieldScheduled = true;
- const auto fieldAreaTop = _scroll->y() + _scroll->height();
- rtlupdate(0, fieldAreaTop, width(), height() - fieldAreaTop);
- }
- void HistoryWidget::drawField(Painter &p, const QRect &rect) {
- _repaintFieldScheduled = false;
- auto backy = _field->y() - st::historySendPadding;
- auto backh = fieldHeight() + 2 * st::historySendPadding;
- auto hasForward = readyToForward();
- auto drawMsgText = (_editMsgId || _replyTo) ? _replyEditMsg : _kbReplyTo;
- if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) {
- backy -= st::historyReplyHeight;
- backh += st::historyReplyHeight;
- } else if (hasForward) {
- backy -= st::historyReplyHeight;
- backh += st::historyReplyHeight;
- } else if (_previewDrawPreview) {
- backy -= st::historyReplyHeight;
- backh += st::historyReplyHeight;
- }
- p.setInactive(
- controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
- p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
- const auto media = (!_previewDrawPreview && drawMsgText)
- ? drawMsgText->media()
- : nullptr;
- const auto hasPreview = media && media->hasReplyPreview();
- const auto preview = _mediaEditManager
- ? _mediaEditManager.mediaPreview()
- : hasPreview
- ? media->replyPreview()
- : nullptr;
- const auto spoilered = _mediaEditManager.spoilered();
- if (!spoilered) {
- _replySpoiler = nullptr;
- } else if (!_replySpoiler) {
- _replySpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
- updateField();
- });
- }
- if (_previewDrawPreview) {
- st::historyLinkIcon.paint(
- p,
- st::historyReplyIconPosition + QPoint(0, backy),
- width());
- const auto textTop = backy + st::msgReplyPadding.top();
- auto previewLeft = st::historyReplySkip;
- const auto to = QRect(
- previewLeft,
- backy + (st::historyReplyHeight - st::historyReplyPreview) / 2,
- st::historyReplyPreview,
- st::historyReplyPreview);
- if (_previewDrawPreview(p, to)) {
- previewLeft += st::historyReplyPreview + st::msgReplyBarSkip;
- }
- p.setPen(st::historyReplyNameFg);
- const auto elidedWidth = width()
- - previewLeft
- - _fieldBarCancel->width()
- - st::msgReplyPadding.right();
- _previewTitle.drawElided(
- p,
- previewLeft,
- textTop,
- elidedWidth);
- p.setPen(st::historyComposeAreaFg);
- _previewDescription.drawElided(
- p,
- previewLeft,
- textTop + st::msgServiceNameFont->height,
- elidedWidth);
- } else if (_editMsgId || _replyTo || (!hasForward && _kbReplyTo)) {
- const auto now = crl::now();
- const auto paused = p.inactive();
- const auto pausedSpoiler = paused || On(PowerSaving::kChatSpoiler);
- auto replyLeft = st::historyReplySkip;
- (_editMsgId
- ? st::historyEditIcon
- : (_replyTo && !_replyTo.quote.empty())
- ? st::historyQuoteIcon
- : st::historyReplyIcon).paint(
- p,
- st::historyReplyIconPosition + QPoint(0, backy),
- width());
- if (drawMsgText) {
- if (hasPreview) {
- if (preview) {
- const auto overEdit = _photoEditMedia
- ? _inPhotoEditOver.value(_inPhotoEdit ? 1. : 0.)
- : 0.;
- auto to = QRect(
- replyLeft,
- (st::historyReplyHeight - st::historyReplyPreview) / 2
- + backy,
- st::historyReplyPreview,
- st::historyReplyPreview);
- p.drawPixmap(to.x(), to.y(), preview->pixSingle(
- preview->size() / style::DevicePixelRatio(),
- {
- .options = Images::Option::RoundSmall,
- .outer = to.size(),
- }));
- if (_replySpoiler) {
- if (overEdit > 0.) {
- p.setOpacity(1. - overEdit);
- }
- Ui::FillSpoilerRect(
- p,
- to,
- Ui::DefaultImageSpoiler().frame(
- _replySpoiler->index(now, pausedSpoiler)));
- }
- if (overEdit > 0.) {
- p.setOpacity(overEdit);
- p.fillRect(to, st::historyEditMediaBg);
- st::historyEditMedia.paintInCenter(p, to);
- p.setOpacity(1.);
- }
- }
- replyLeft += st::historyReplyPreview + st::msgReplyBarSkip;
- }
- p.setPen(st::historyReplyNameFg);
- if (_editMsgId) {
- paintEditHeader(p, rect, replyLeft, backy);
- } else {
- _replyToName.drawElided(
- p,
- replyLeft,
- backy + st::msgReplyPadding.top(),
- width()
- - replyLeft
- - _fieldBarCancel->width()
- - st::msgReplyPadding.right());
- }
- p.setPen(st::historyComposeAreaFg);
- _replyEditMsgText.draw(p, {
- .position = QPoint(
- replyLeft,
- st::msgReplyPadding.top()
- + st::msgServiceNameFont->height
- + backy),
- .availableWidth = width()
- - replyLeft
- - _fieldBarCancel->width()
- - st::msgReplyPadding.right(),
- .palette = &st::historyComposeAreaPalette,
- .spoiler = Ui::Text::DefaultSpoilerCache(),
- .now = now,
- .pausedEmoji = paused || On(PowerSaving::kEmojiChat),
- .pausedSpoiler = pausedSpoiler,
- .elisionLines = 1,
- });
- } else {
- p.setFont(st::msgDateFont);
- p.setPen(st::historyComposeAreaFgService);
- p.drawText(
- replyLeft,
- backy
- + (st::historyReplyHeight - st::msgDateFont->height) / 2
- + st::msgDateFont->ascent,
- st::msgDateFont->elided(
- tr::lng_profile_loading(tr::now),
- width()
- - replyLeft
- - _fieldBarCancel->width()
- - st::msgReplyPadding.right()));
- }
- } else if (hasForward) {
- st::historyForwardIcon.paint(
- p,
- st::historyReplyIconPosition + QPoint(0, backy), width());
- const auto x = st::historyReplySkip;
- const auto available = width()
- - x
- - _fieldBarCancel->width()
- - st::msgReplyPadding.right();
- _forwardPanel->paint(p, x, backy, available, width());
- }
- }
- void HistoryWidget::paintEditHeader(
- Painter &p,
- const QRect &rect,
- int left,
- int top) const {
- if (!rect.intersects(
- myrtlrect(left, top, width() - left, st::normalFont->height))) {
- return;
- }
- p.setFont(st::msgServiceNameFont);
- p.drawTextLeft(
- left,
- top + st::msgReplyPadding.top(),
- width(),
- tr::lng_edit_message(tr::now));
- if (!_replyEditMsg
- || _replyEditMsg->history()->peer->canEditMessagesIndefinitely()) {
- return;
- }
- auto editTimeLeftText = QString();
- auto updateIn = int(-1);
- auto timeSinceMessage = ItemDateTime(_replyEditMsg).msecsTo(
- QDateTime::currentDateTime());
- auto editTimeLeft = (session().serverConfig().editTimeLimit * 1000LL)
- - timeSinceMessage;
- if (editTimeLeft < 2) {
- editTimeLeftText = u"0:00"_q;
- } else if (editTimeLeft > kDisplayEditTimeWarningMs) {
- updateIn = static_cast<int>(qMin(
- editTimeLeft - kDisplayEditTimeWarningMs,
- qint64(kFullDayInMs)));
- } else {
- updateIn = static_cast<int>(editTimeLeft % 1000);
- if (!updateIn) {
- updateIn = 1000;
- }
- ++updateIn;
- editTimeLeft = (editTimeLeft - 1) / 1000; // seconds
- editTimeLeftText = u"%1:%2"_q
- .arg(editTimeLeft / 60)
- .arg(editTimeLeft % 60, 2, 10, QChar('0'));
- }
- // Restart timer only if we are sure that we've painted the whole timer.
- if (rect.contains(
- myrtlrect(left, top, width() - left, st::normalFont->height))
- && (updateIn > 0)) {
- _updateEditTimeLeftDisplay.callOnce(updateIn);
- }
- if (!editTimeLeftText.isEmpty()) {
- p.setFont(st::normalFont);
- p.setPen(st::historyComposeAreaFgService);
- p.drawText(
- left
- + st::msgServiceNameFont->width(tr::lng_edit_message(tr::now))
- + st::normalFont->spacew,
- top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent,
- editTimeLeftText);
- }
- }
- bool HistoryWidget::paintShowAnimationFrame() {
- if (_showAnimation) {
- auto p = QPainter(this);
- _showAnimation->paintContents(p);
- return true;
- }
- return false;
- }
- void HistoryWidget::paintEvent(QPaintEvent *e) {
- if (paintShowAnimationFrame()
- || controller()->contentOverlapped(this, e)) {
- return;
- }
- if (hasPendingResizedItems()) {
- updateListSize();
- }
- Window::SectionWidget::PaintBackground(
- controller(),
- controller()->currentChatTheme(),
- this,
- e->rect());
- Painter p(this);
- const auto clip = e->rect();
- if (_list) {
- const auto restrictionHidden = fieldOrDisabledShown()
- || isRecording();
- if (restrictionHidden
- || replyTo()
- || readyToForward()
- || _kbShown) {
- if (!isSearching()) {
- drawField(p, clip);
- }
- }
- } else {
- const auto w = 0
- + st::msgServiceFont->width(tr::lng_willbe_history(tr::now))
- + st::msgPadding.left()
- + st::msgPadding.right();
- const auto h = st::msgServiceFont->height
- + st::msgServicePadding.top()
- + st::msgServicePadding.bottom();
- const auto tr = QRect(
- (width() - w) / 2,
- st::msgServiceMargin.top() + (height()
- - fieldHeight()
- - 2 * st::historySendPadding
- - h
- - st::msgServiceMargin.top()
- - st::msgServiceMargin.bottom()) / 2,
- w,
- h);
- const auto st = controller()->chatStyle();
- HistoryView::ServiceMessagePainter::PaintBubble(p, st, tr);
- p.setPen(st->msgServiceFg());
- p.setFont(st::msgServiceFont->f);
- p.drawTextLeft(
- tr.left() + st::msgPadding.left(),
- tr.top() + st::msgServicePadding.top(),
- width(),
- tr::lng_willbe_history(tr::now));
- }
- }
- QPoint HistoryWidget::clampMousePosition(QPoint point) {
- if (point.x() < 0) {
- point.setX(0);
- } else if (point.x() >= _scroll->width()) {
- point.setX(_scroll->width() - 1);
- }
- if (point.y() < _scroll->scrollTop()) {
- point.setY(_scroll->scrollTop());
- } else if (point.y() >= _scroll->scrollTop() + _scroll->height()) {
- point.setY(_scroll->scrollTop() + _scroll->height() - 1);
- }
- return point;
- }
- bool HistoryWidget::touchScroll(const QPoint &delta) {
- const auto scTop = _scroll->scrollTop();
- const auto scMax = _scroll->scrollTopMax();
- const auto scNew = std::clamp(scTop - delta.y(), 0, scMax);
- if (scNew == scTop) {
- return false;
- }
- _scroll->scrollToY(scNew);
- return true;
- }
- void HistoryWidget::synteticScrollToY(int y) {
- _synteticScrollEvent = true;
- if (_scroll->scrollTop() == y) {
- visibleAreaUpdated();
- } else {
- _scroll->scrollToY(y);
- }
- _synteticScrollEvent = false;
- }
- HistoryWidget::~HistoryWidget() {
- if (_history) {
- // Saving a draft on account switching.
- saveFieldToHistoryLocalDraft();
- session().api().saveDraftToCloudDelayed(_history);
- setHistory(nullptr);
- session().data().itemVisibilitiesUpdated();
- }
- setTabbedPanel(nullptr);
- }
|