EditorPersonal.swift 494 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111
  1. //
  2. // EditorPersonal.swift
  3. // Qmera
  4. //
  5. // Created by Akhmad Al Qindi Irsyam on 31/08/21.
  6. //
  7. import UIKit
  8. import AVKit
  9. import AVFoundation
  10. import QuickLook
  11. import NotificationBannerSwift
  12. import Photos
  13. import nuSDKService
  14. import SwiftLinkPreview
  15. import SDWebImage
  16. import PhotosUI
  17. public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate {
  18. @IBOutlet var wallpaperView: UIImageView!
  19. @IBOutlet var viewButton: UIView!
  20. @IBOutlet var constraintViewTextField: NSLayoutConstraint!
  21. @IBOutlet var buttonVoice: UIButton!
  22. @IBOutlet var buttonSendImage: UIButton!
  23. @IBOutlet var buttonSendPhoto: UIButton!
  24. @IBOutlet var buttonSendSticker: UIButton!
  25. @IBOutlet var buttonSendFile: UIButton!
  26. @IBOutlet var textFieldSend: CustomTextView!
  27. @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
  28. @IBOutlet var buttonSendChat: UIButton!
  29. @IBOutlet var tableChatView: UITableView!
  30. @IBOutlet var constraintTopTextField: NSLayoutConstraint!
  31. @IBOutlet var constraintBottomAttachment: NSLayoutConstraint!
  32. @IBOutlet var viewTextfield: UIView!
  33. @IBOutlet weak var buttonAckConfidential: UIButton!
  34. @IBOutlet weak var constraintLeftTextField: NSLayoutConstraint!
  35. @IBOutlet weak var constraintBottomTableViewWithTextfield: NSLayoutConstraint!
  36. @IBOutlet weak var viewAttachment: UIStackView!
  37. public var dataPerson: [String: String?] = [:]
  38. var dataMessages: [[String: Any?]] = []
  39. var dataDates: [String] = []
  40. var users: [User] = []
  41. public var dataMessageForward: [[String: Any?]]?
  42. var imageVideoPicker: ImageVideoPicker!
  43. var documentPicker: DocumentPicker!
  44. var currentIndexpath: IndexPath?
  45. var previewItem: NSURL?
  46. var reffId: String?
  47. var stickers = [String]()
  48. public var unique_l_pin = ""
  49. public var isContactCenter = false
  50. public var isRequestContactCenter = true
  51. public var fromNotification = false
  52. public var onGoingCC = false
  53. public var fPinContacCenter = ""
  54. public var complaintId = ""
  55. public var referenceMessageId = ""
  56. public var referenceChatDate = ""
  57. var channelContactCenter = ""
  58. var counter = 0
  59. var dateStartCC = ""
  60. var markerCounter: String?
  61. var buttonScrollToBottom = UIButton()
  62. let indicatorCounterBSTB = UIView()
  63. let labelCounter = UILabel()
  64. var copySession = false
  65. var forwardSession = false
  66. var deleteSession = false
  67. var isSearching = false
  68. let containerMultpileSelectSession = UIView()
  69. let containerAction = UIView()
  70. var removed = false
  71. var isConfidential = false
  72. var isAck = false
  73. var isSecret = false
  74. let viewSticker = UIView()
  75. let containerLink = UIView()
  76. let containerPreviewReply = UIView()
  77. var bottomAnchorPreviewReply = NSLayoutConstraint()
  78. var blocking = ""
  79. var timeoutCC = Timer()
  80. var nowSelectedCategoryCC = ""
  81. var showToastTwiceClick = false
  82. var showToast30s = false
  83. var allowTyping = true
  84. var hapticSwipeLeft = false
  85. var listTimerCredential: [String: Int] = [:]
  86. var timerCredential: [String: Timer] = [:]
  87. let contactChatNav = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
  88. var searchBar: UISearchBar!
  89. var constraintBottomContainerMultpileSelectSession = NSLayoutConstraint()
  90. var titleSearchMatches: UILabel!
  91. var textSearch = ""
  92. var countMatchesSearch = 0
  93. var lastScrollIdxSearch = 0
  94. var buttonUp: UIButton!
  95. var buttonDown: UIButton!
  96. var multipleOffsetUp = 1
  97. var lastOffsetDown = 1
  98. var gettingDataMessage = true
  99. var showingLink = ""
  100. var isAlwaysHideLinkPreview = false
  101. var timerCheckLink: Timer?
  102. var timerFakeProgress: Timer?
  103. var showMenuContext = false
  104. var touchedSubview = UIView()
  105. var listViewOnSection: [UIView] = []
  106. var fromVCAC = false
  107. var serviceIdCC = ""
  108. var isDirectCC = false
  109. var fakeProgMultip = 0
  110. let maxFakeProgMultip = 2
  111. var groupImages: [String:[ImageGrouping]] = [:]
  112. var titleText: String!
  113. var lastY: CGFloat = 0
  114. var editVC = UIViewController()
  115. var editTextView = CustomTextView()
  116. var isEditingMessage = false
  117. var constraintBottomeditTextView: NSLayoutConstraint!
  118. var constraintHeighteditTextView: NSLayoutConstraint!
  119. var constraintBottomSendEditTV: NSLayoutConstraint!
  120. let locationManager = CLLocationManager()
  121. var longitude = ""
  122. var latitude = ""
  123. var isBlackCancelButton = false
  124. let buttonSendEdit = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
  125. var audioPlayers: [IndexPath: AVAudioPlayer] = [:]
  126. var timers: [IndexPath: Timer] = [:]
  127. var playingIndexPath: IndexPath?
  128. var timerSearch: Timer?
  129. func offset() -> CGFloat{
  130. guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
  131. return CGFloat(fontSize)
  132. }
  133. public override func viewDidDisappear(_ animated: Bool) {
  134. if self.isMovingFromParent {
  135. removeAllObjectBeforeDismissVC()
  136. }
  137. }
  138. public override func viewDidAppear(_ animated: Bool) {
  139. let navBarAppearance = UINavigationBarAppearance()
  140. navBarAppearance.configureWithOpaqueBackground()
  141. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  142. navigationController?.navigationBar.standardAppearance = navBarAppearance
  143. navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
  144. navigationController?.navigationBar.isTranslucent = false
  145. navigationController?.navigationBar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  146. navigationController?.navigationBar.tintColor = .white
  147. navigationController?.navigationBar.overrideUserInterfaceStyle = .dark
  148. navigationController?.navigationBar.barStyle = .black
  149. if self.navigationController?.isNavigationBarHidden ?? false {
  150. self.navigationController?.setNavigationBarHidden(false, animated: false)
  151. }
  152. updateProfile()
  153. gettingDataMessage = false
  154. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  155. if indexPath != nil && currentIndexpath != nil {
  156. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  157. let isPinned = headerRect.origin.y <= tableChatView.contentOffset.y
  158. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  159. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  160. let headerView = listViewOnSection[sect]
  161. headerView.isHidden = true
  162. }
  163. }
  164. }
  165. public override func viewDidLoad() {
  166. super.viewDidLoad()
  167. // navigationController?.navigationBar.topItem?.title = ""
  168. Utils.addBackground(view: contactChatNav.view)
  169. if let dataWall = UserDefaults.standard.data(forKey: "chatWallpaper") {
  170. wallpaperView.image = UIImage(data: UserDefaults.standard.data(forKey: "chatWallpaper")!)
  171. }
  172. else {
  173. wallpaperView.isHidden = true
  174. }
  175. if Nexilis.fromMAB {
  176. Nexilis.floatingButton.isHidden = true
  177. }
  178. viewButton.layer.shadowColor = self.traitCollection.userInterfaceStyle == .dark ? UIColor.white.cgColor : UIColor.gray.cgColor
  179. viewButton.layer.shadowOpacity = 1
  180. viewButton.layer.shadowOffset = .zero
  181. viewButton.layer.shadowRadius = 3
  182. viewButton.addTopBorder(with: UIColor.lightGray, andWidth: 1.0)
  183. // buttonVoice.setImage(resizeImage(image: UIImage(named: "Voice-Record", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)), for: .normal)
  184. viewAttachment.backgroundColor = .white
  185. buttonSendImage.setImage(resizeImage(image: UIImage(named: "Send-Image", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor), for: .normal)
  186. buttonSendPhoto.setImage(resizeImage(image: UIImage(named: "Camera", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor), for: .normal)
  187. buttonSendSticker.setImage(resizeImage(image: UIImage(named: "Sticker---Emoji", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor), for: .normal)
  188. buttonSendFile.setImage(resizeImage(image: UIImage(named: "File---Documents", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withTintColor(self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor), for: .normal)
  189. buttonSendChat.setImage(resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.blackDarkMode) : UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal)
  190. buttonSendChat.circle()
  191. buttonSendChat.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
  192. buttonSendChat.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  193. if isContactCenter {
  194. buttonAckConfidential.isHidden = true
  195. constraintLeftTextField.constant = 20
  196. } else {
  197. buttonAckConfidential.circle()
  198. buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
  199. buttonAckConfidential.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  200. buttonAckConfidential.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  201. }
  202. textFieldSend.backgroundColor = .white
  203. textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
  204. textFieldSend.layer.borderWidth = 1.0
  205. textFieldSend.text = "Send message".localized()
  206. textFieldSend.textColor = UIColor.lightGray
  207. textFieldSend.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  208. textFieldSend.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  209. textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  210. textFieldSend.font = UIFont.systemFont(ofSize: 12 + offset())
  211. textFieldSend.delegate = self
  212. textFieldSend.customDelegate = self
  213. textFieldSend.allowsEditingTextAttributes = true
  214. navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
  215. imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self)
  216. documentPicker = DocumentPicker(presentationController: self, delegate: self)
  217. let fm = FileManager.default
  218. if Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") != nil {
  219. let path = Bundle.resourceBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  220. let items = try! fm.contentsOfDirectory(atPath: path)
  221. for item in items {
  222. if item.hasPrefix("sticker") {
  223. stickers.append(item)
  224. }
  225. }
  226. } else {
  227. let path = Bundle.resourcesMediaBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  228. let items = try! fm.contentsOfDirectory(atPath: path)
  229. for item in items {
  230. if item.hasPrefix("sticker") {
  231. stickers.append(item)
  232. }
  233. }
  234. }
  235. tableChatView.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditorPersonal")
  236. loadData()
  237. setRightButtonItem()
  238. let center: NotificationCenter = NotificationCenter.default
  239. center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  240. center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  241. center.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil)
  242. center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil)
  243. center.addObserver(self, selector: #selector(onUploadChat(notification:)), name: NSNotification.Name(rawValue: "onUploadChat"), object: nil)
  244. center.addObserver(self, selector: #selector(onUnfriend(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
  245. center.addObserver(self, selector: #selector(onTyping(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerTypingChat), object: nil)
  246. center.addObserver(self, selector: #selector(onFailedSendMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.failedSendMessage), object: nil)
  247. locationManager.delegate = self
  248. locationManager.requestWhenInUseAuthorization()
  249. DispatchQueue.global().async { [self] in
  250. // Check if location services are enabled
  251. if CLLocationManager.locationServicesEnabled() {
  252. locationManager.desiredAccuracy = kCLLocationAccuracyBest
  253. locationManager.startUpdatingLocation()
  254. } else {
  255. print("Location services are not enabled.")
  256. }
  257. }
  258. if dataMessageForward != nil {
  259. for i in 0..<dataMessageForward!.count {
  260. let isForwarded = (dataMessageForward![i][TypeDataMessage.is_forwarded] as? Int) ?? 0
  261. sendChat(message_scope_id: "3", status: "2", message_text: dataMessageForward![i]["message_text"] as? String ?? "", credential: "0", attachment_flag: dataMessageForward![i]["attachment_flag"] as? String ?? "", ex_blog_id: "", message_large_text: "", ex_format: "", image_id: dataMessageForward![i]["image_id"] as? String ?? "", audio_id: dataMessageForward![i]["audio_id"] as? String ?? "", video_id: dataMessageForward![i]["video_id"] as? String ?? "", file_id: dataMessageForward![i]["file_id"] as? String ?? "", thumb_id: dataMessageForward![i]["thumb_id"] as? String ?? "", reff_id: "", read_receipts: dataMessageForward![i]["read_receipts"] as? String ?? "", chat_id: "", is_call_center: "0", call_center_id: "", viewController: self, gif_id: dataMessageForward![i][TypeDataMessage.gif_id] as? String ?? "", is_forwarded: isForwarded + 1, is_secret: (dataMessageForward![i][TypeDataMessage.is_secret] as? Int) ?? 0)
  262. }
  263. dataMessageForward = nil
  264. }
  265. if isContactCenter && !isRequestContactCenter && !onGoingCC {
  266. var companyName = ""
  267. Database.shared.database?.inTransaction({ fmdb, rollback in
  268. do {
  269. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT first_name, last_name FROM BUDDY where official_account = '1'"), cursor.next() {
  270. companyName = cursor.string(forColumnIndex: 0)! + " " + cursor.string(forColumnIndex: 1)!
  271. companyName = companyName.trimmingCharacters(in: .whitespaces)
  272. cursor.close()
  273. }
  274. } catch {
  275. rollback.pointee = true
  276. print("Access database error: \(error.localizedDescription)")
  277. }
  278. })
  279. self.dateStartCC = "\(Date().currentTimeMillis())"
  280. let myName = User.getData(pin: User.getMyPin() as String?)
  281. sendChat(message_text: "Hi \(dataPerson["name"]!!), thank you for contacting \(companyName). My name is \(myName!.fullName.trimmingCharacters(in: .whitespaces)), how can I help you?".localized(), ex_format: "1", is_call_center: "1", call_center_id: complaintId, viewController: self, isAutoSendCC: true)
  282. if channelContactCenter == "1" {
  283. if let pin = dataPerson["f_pin"] {
  284. let controller = QmeraAudioViewController()
  285. controller.user = User.getData(pin: pin)
  286. controller.isOutgoing = true
  287. controller.modalPresentationStyle = .overCurrentContext
  288. present(controller, animated: true, completion: nil)
  289. }
  290. } else if channelContactCenter == "2" {
  291. let videoVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
  292. videoVC.dataPerson.append(dataPerson)
  293. self.show(videoVC, sender: nil)
  294. }
  295. }
  296. if isDirectCC {
  297. directCC()
  298. }
  299. // else if isContactCenter {
  300. // let buttonId = UIButton()
  301. // if channelContactCenter == "0" {
  302. // buttonId.tag = 0
  303. // ccAction(sender: buttonId)
  304. // } else if channelContactCenter == "1" {
  305. // buttonId.tag = 1
  306. // ccAction(sender: buttonId)
  307. // } else if channelContactCenter == "2" {
  308. // buttonId.tag = 2
  309. // ccAction(sender: buttonId)
  310. // }
  311. // }
  312. }
  313. public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  314. guard let location = locations.last else { return }
  315. latitude = "\(location.coordinate.latitude)"
  316. longitude = "\(location.coordinate.longitude)"
  317. locationManager.stopUpdatingLocation()
  318. }
  319. public func afterUnfriend() {
  320. DispatchQueue.main.async {
  321. for timer in self.timerCredential.values {
  322. timer.invalidate()
  323. }
  324. self.timeoutCC.invalidate()
  325. SecureUserDefaults.shared.removeValue(forKey: "inEditorPersonal")
  326. NotificationCenter.default.removeObserver(self)
  327. }
  328. }
  329. private func setRightButtonItem() {
  330. navigationItem.rightBarButtonItems = nil
  331. let actionDelete = UIAction(title: "Delete Conversation".localized(), handler: {(_) in
  332. if !self.isContactCenter {
  333. let alert = LibAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
  334. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.default, handler: nil))
  335. alert.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: {(_) in
  336. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  337. do {
  338. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "(f_pin='\(self.dataPerson["f_pin"]!!)' or l_pin='\(self.dataPerson["f_pin"]!!)') and (message_scope_id='3' or message_scope_id='18') and is_call_center = 0")
  339. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(self.dataPerson["f_pin"]!!)'")
  340. let l_pin = self.dataPerson["f_pin"]!!
  341. SecureUserDefaults.shared.removeValue(forKey: "new_saved_\(l_pin)")
  342. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  343. if self.fromNotification {
  344. self.didTapExit()
  345. } else {
  346. self.navigationController?.popViewController(animated: true)
  347. }
  348. } catch {
  349. rollback.pointee = true
  350. print("Access database error: \(error.localizedDescription)")
  351. }
  352. })
  353. }))
  354. self.present(alert, animated: true, completion: nil)
  355. }
  356. })
  357. let actionSearch = UIAction(title: "Search".localized(), handler: {(_) in
  358. self.isSearching = true
  359. if self.reffId != nil {
  360. self.deleteReplyView()
  361. }
  362. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  363. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  364. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  365. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  366. self.navigationItem.rightBarButtonItems = nil
  367. }
  368. self.navigationItem.rightBarButtonItem = cancelButton
  369. if self.isContactCenter || self.fromNotification {
  370. self.navigationItem.leftBarButtonItem = nil
  371. }
  372. self.changeAppBar()
  373. self.addMultipleSelectSession()
  374. }
  375. })
  376. let actionUnblock = UIAction(title: "Unblock".localized(), handler: {(_) in
  377. if !self.isContactCenter {
  378. DispatchQueue.global().async {
  379. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: self.dataPerson["f_pin"]!!)) {
  380. if !response.isOk() {
  381. DispatchQueue.main.async {
  382. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  383. imageView.tintColor = .white
  384. let banner = FloatingNotificationBanner(title: "Unable to complete action".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  385. banner.show()
  386. }
  387. } else {
  388. DispatchQueue.main.async { [self] in
  389. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  390. do {
  391. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  392. "ex_block" : "0"
  393. ], _where: "f_pin = '\(self.dataPerson["f_pin"]!!)'")
  394. } catch {
  395. rollback.pointee = true
  396. print("Access database error: \(error.localizedDescription)")
  397. }
  398. })
  399. containerAction.subviews.forEach({ $0.removeFromSuperview() })
  400. containerAction.removeFromSuperview()
  401. setRightButtonItem()
  402. changeAppBar()
  403. }
  404. }
  405. } else {
  406. DispatchQueue.main.async {
  407. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  408. imageView.tintColor = .white
  409. let banner = FloatingNotificationBanner(title: "Unable to access servers".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  410. banner.show()
  411. }
  412. }
  413. }
  414. }
  415. })
  416. let actionBlock = UIAction(title: "Block".localized(), handler: {(_) in
  417. if !self.isContactCenter {
  418. DispatchQueue.global().async {
  419. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: self.dataPerson["f_pin"]!!)) {
  420. if !response.isOk() {
  421. DispatchQueue.main.async {
  422. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  423. imageView.tintColor = .white
  424. let banner = FloatingNotificationBanner(title: "Unable to complete action".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  425. banner.show()
  426. }
  427. } else {
  428. DispatchQueue.main.async { [self] in
  429. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  430. do {
  431. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  432. "ex_block" : "1"
  433. ], _where: "f_pin = '\(self.dataPerson["f_pin"]!!)'")
  434. } catch {
  435. rollback.pointee = true
  436. print("Access database error: \(error.localizedDescription)")
  437. }
  438. })
  439. setRightButtonItem()
  440. changeAppBar()
  441. }
  442. }
  443. } else {
  444. DispatchQueue.main.async {
  445. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  446. imageView.tintColor = .white
  447. let banner = FloatingNotificationBanner(title: "Unable to access servers".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  448. banner.show()
  449. }
  450. }
  451. }
  452. }
  453. })
  454. var menu = UIMenu(title: "", children: [
  455. actionSearch,
  456. actionDelete
  457. ])
  458. let exblock = User.getDataCanNil(pin: self.dataPerson["f_pin"]!!)?.ex_block
  459. blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
  460. if blocking == "1" && self.dataPerson["f_pin"]!! != "-999" {
  461. menu = UIMenu(title: "", children: [
  462. actionSearch,
  463. actionUnblock,
  464. actionDelete
  465. ])
  466. blockedView(blocked: "1")
  467. } else if blocking == "0" {
  468. if self.dataPerson["f_pin"]!! != "-999" && complaintId.isEmpty {
  469. menu = UIMenu(title: "", children: [
  470. actionSearch,
  471. actionBlock,
  472. actionDelete
  473. ])
  474. } else if !complaintId.isEmpty{
  475. menu = UIMenu(title: "", children: [
  476. actionSearch
  477. ])
  478. }
  479. else {
  480. menu = UIMenu(title: "", children: [
  481. actionSearch,
  482. actionDelete
  483. ])
  484. }
  485. if containerAction.isDescendant(of: self.view) {
  486. containerAction.subviews.forEach({ $0.removeFromSuperview() })
  487. containerAction.removeFromSuperview()
  488. }
  489. } else {
  490. blockedView(blocked: "-1")
  491. changeAppBar()
  492. }
  493. let moreIcon = UIBarButtonItem(image: UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), menu: menu)
  494. let buttonAudioCall = UIBarButtonItem(image: UIImage(systemName: "phone", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  495. // let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  496. buttonAudioCall.tag = 0
  497. let buttonVideoCall = UIBarButtonItem(image: UIImage(systemName: "video", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  498. buttonVideoCall.tag = 1
  499. let buttonAddRoom = UIBarButtonItem(image: UIImage(systemName: "plus.message", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(addRoom(sender:)))
  500. if dataPerson["f_pin"] != "-999" && !isContactCenter && blocking == "0" {
  501. navigationItem.rightBarButtonItems = [moreIcon,buttonAudioCall,buttonVideoCall]
  502. } else if !isContactCenter {
  503. navigationItem.rightBarButtonItem = moreIcon
  504. } else if !complaintId.isEmpty { //!complaintId.isEmpty
  505. navigationItem.rightBarButtonItems = [moreIcon,buttonAddRoom]
  506. }
  507. }
  508. func loadData() {
  509. if (unique_l_pin != "" || isContactCenter) {
  510. getDataProfile(fPin: unique_l_pin)
  511. if isContactCenter && !isRequestContactCenter && users.count == 0 {
  512. if !unique_l_pin.isEmpty {
  513. users.append(User.getData(pin: unique_l_pin) ?? User(pin: ""))
  514. }
  515. }
  516. }
  517. if onGoingCC {
  518. SecureUserDefaults.shared.set(self.fPinContacCenter, forKey: "inEditorPersonal")
  519. } else {
  520. SecureUserDefaults.shared.set(dataPerson["f_pin"]!, forKey: "inEditorPersonal")
  521. }
  522. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [dataPerson["f_pin"]!!])
  523. if isContactCenter || fromNotification {
  524. let imageButton = UIImageView(frame: CGRect(x: -16, y: 0, width: 20, height: 44))
  525. imageButton.image = UIImage(systemName: "chevron.backward", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default))?.withTintColor(.white)
  526. imageButton.contentMode = .left
  527. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapExit))
  528. imageButton.isUserInteractionEnabled = true
  529. imageButton.addGestureRecognizer(tapGestureRecognizer)
  530. let leftItem = UIBarButtonItem(customView: imageButton)
  531. self.navigationItem.leftBarButtonItem = leftItem
  532. }
  533. if dataPerson["f_pin"] == "-999" {
  534. chatbot()
  535. }
  536. let exblock = User.getData(pin: self.dataPerson["f_pin"]!!)?.ex_block
  537. blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
  538. changeAppBar()
  539. getData()
  540. tableChatView.alpha = 0
  541. tableChatView.delegate = self
  542. tableChatView.dataSource = self
  543. tableChatView.keyboardDismissMode = .interactive
  544. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  545. tapGesture.cancelsTouchesInView = false
  546. tableChatView.addGestureRecognizer(tapGesture)
  547. if !isContactCenter {
  548. getCounter()
  549. if counter > 0 && dataMessages.count >= counter {
  550. markerCounter = dataMessages[dataMessages.count - counter]["message_id"] as? String
  551. }
  552. if !referenceMessageId.isEmpty {
  553. if dataMessages.firstIndex(where: {$0["message_id"] as? String == referenceMessageId} ) != 0 {
  554. DispatchQueue.main.async {
  555. let section = self.dataDates.firstIndex(of: self.referenceChatDate)
  556. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.referenceChatDate}).firstIndex(where: { $0["message_id"] as? String == self.referenceMessageId})
  557. if row != nil && section != nil {
  558. let indexPath = IndexPath(row: row!, section: section!)
  559. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: false)
  560. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .yellow
  561. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  562. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .clear
  563. })
  564. }
  565. }
  566. }
  567. } else if counter != 0 && dataMessages.count >= counter {
  568. if dataMessages.firstIndex(where: {$0["message_id"] as? String == markerCounter} ) != 0 {
  569. DispatchQueue.main.async {
  570. let data = self.dataMessages.filter({ $0["message_id"] as? String == self.markerCounter })
  571. if data.count > 0 {
  572. let section = self.dataDates.firstIndex(of: data[0]["chat_date"] as? String ?? "")
  573. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == data[0]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.markerCounter})
  574. self.tableChatView.scrollToRow(at: IndexPath(row: row!, section: section!), at: .bottom, animated: false)
  575. }
  576. }
  577. } else {
  578. tableChatView.scrollToTop()
  579. }
  580. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in
  581. if currentIndexpath == nil && counter != 0 {
  582. let idMe = User.getMyPin() as String?
  583. if let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == markerCounter}) {
  584. for i in idx..<dataMessages.count {
  585. if dataMessages[i]["f_pin"] as? String != idMe {
  586. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: "3", message_id: dataMessages[i]["message_id"] as? String ?? "")
  587. }
  588. }
  589. counter = 0
  590. updateCounter(counter: counter)
  591. }
  592. }
  593. }
  594. } else {
  595. let l_pin = self.dataPerson["f_pin"] as? String ?? ""
  596. if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
  597. let data = dataSaved
  598. if let jsonData = data.data(using: .utf8),
  599. let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
  600. let last_m = dataJson["text"] ?? ""
  601. let last_r = dataJson["reffId"] ?? ""
  602. if !last_m.isEmpty {
  603. textFieldSend.attributedText = last_m.richText(isEditing: true)
  604. textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  605. }
  606. if !last_r.isEmpty {
  607. handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
  608. }
  609. }
  610. }
  611. tableChatView.scrollToBottom(isAnimated: false)
  612. }
  613. } else if isContactCenter && onGoingCC {
  614. let idMe = User.getMyPin() as String?
  615. for i in 0..<dataMessages.count {
  616. if dataMessages[i]["f_pin"] as? String != idMe {
  617. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: "3", message_id: dataMessages[i]["message_id"] as? String ?? "")
  618. }
  619. }
  620. tableChatView.scrollToBottom(isAnimated: false)
  621. } else {
  622. tableChatView.scrollToBottom(isAnimated: false)
  623. }
  624. if tableChatView.alpha != 1 {
  625. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  626. UIView.animate(withDuration: 0.5, animations: {
  627. self.tableChatView.alpha = 1.0
  628. })
  629. })
  630. }
  631. for data in listTimerCredential {
  632. if data.value > 0 {
  633. var second = data.value
  634. var timer = Timer()
  635. timerCredential[data.key] = timer
  636. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  637. second -= 1
  638. self.listTimerCredential[data.key] = second
  639. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key })
  640. if (idx != nil) {
  641. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  642. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[idx!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  643. if second == 0 {
  644. timer.invalidate()
  645. self.listTimerCredential.removeValue(forKey: data.key)
  646. self.timerCredential.removeValue(forKey: data.key)
  647. SecureUserDefaults.shared.removeValue(forKey: data.key)
  648. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key})
  649. if idx != nil {
  650. self.dataMessages[idx!]["lock"] = "2"
  651. self.dataMessages[idx!]["reff_id"] = ""
  652. }
  653. DispatchQueue.global().async {
  654. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  655. do {
  656. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  657. "lock" : "2"
  658. ], _where: "message_id = '\(data.key)'")
  659. } catch {
  660. rollback.pointee = true
  661. print("Access database error: \(error.localizedDescription)")
  662. }
  663. })
  664. }
  665. }
  666. if row != nil && section != nil {
  667. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  668. }
  669. }
  670. })
  671. }
  672. }
  673. }
  674. private func chatbot() {
  675. let containerChatbot = UIView()
  676. self.view.addSubview(containerChatbot)
  677. containerChatbot.translatesAutoresizingMaskIntoConstraints = false
  678. NSLayoutConstraint.activate([
  679. containerChatbot.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  680. containerChatbot.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  681. containerChatbot.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  682. containerChatbot.heightAnchor.constraint(equalToConstant: 120)
  683. ])
  684. containerChatbot.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  685. let labelChatbot = UILabel()
  686. containerChatbot.addSubview(labelChatbot)
  687. labelChatbot.translatesAutoresizingMaskIntoConstraints = false
  688. NSLayoutConstraint.activate([
  689. labelChatbot.centerYAnchor.constraint(equalTo: containerChatbot.centerYAnchor),
  690. labelChatbot.centerXAnchor.constraint(equalTo: containerChatbot.centerXAnchor),
  691. ])
  692. labelChatbot.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  693. labelChatbot.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  694. labelChatbot.text = "Interactive chatbot. Coming soon".localized()
  695. }
  696. private func changeAppBar() {
  697. let viewAppBar = UIView()
  698. viewAppBar.frame.size = CGSize(width: self.view.frame.size.width, height: 44)
  699. if !isSearching {
  700. let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  701. imageProfile.circle()
  702. imageProfile.clipsToBounds = true
  703. let pictureImage = dataPerson["picture"]!
  704. var count = 0
  705. if isContactCenter {
  706. if fPinContacCenter.isEmpty && isRequestContactCenter {
  707. getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  708. imageProfile.image = image
  709. }
  710. viewAppBar.addSubview(imageProfile)
  711. } else {
  712. if users.count == 1 {
  713. viewAppBar.addSubview(imageProfile)
  714. getImage(name: users[0].thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  715. imageProfile.image = image
  716. imageProfile.contentMode = .scaleAspectFit
  717. }
  718. } else {
  719. for user in users {
  720. if count == 3 {
  721. count += 1
  722. continue
  723. }
  724. if count == 0 {
  725. let pictures = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  726. pictures.circle()
  727. pictures.clipsToBounds = true
  728. viewAppBar.addSubview(pictures)
  729. getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  730. pictures.image = image
  731. pictures.contentMode = .scaleAspectFit
  732. }
  733. } else {
  734. let pictures = UIImageView(frame: CGRect(x: count * 20 , y: 7, width: 30, height: 30))
  735. pictures.circle()
  736. pictures.clipsToBounds = true
  737. viewAppBar.addSubview(pictures)
  738. getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  739. pictures.image = image
  740. pictures.contentMode = .scaleAspectFit
  741. }
  742. }
  743. count += 1
  744. }
  745. }
  746. }
  747. } else if dataPerson["f_pin"]!! == "-999" {
  748. viewAppBar.addSubview(imageProfile)
  749. if !Utils.getIconDock().isEmpty {
  750. let urlString = Utils.getUrlDock()!
  751. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  752. let imageData = cachedImage
  753. imageProfile.image = imageData
  754. } else {
  755. DispatchQueue.global().async{
  756. Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
  757. guard let data = data, error == nil else { return }
  758. DispatchQueue.main.async() {
  759. if UIImage(data: data) != nil {
  760. let imageData = UIImage(data: data)!
  761. imageProfile.image = imageData
  762. ImageCache.shared.save(image: imageData, forKey: urlString)
  763. }
  764. }
  765. }
  766. }
  767. }
  768. } else {
  769. imageProfile.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  770. }
  771. imageProfile.contentMode = .scaleAspectFit
  772. }
  773. else if (pictureImage != "" && pictureImage != nil) {
  774. viewAppBar.addSubview(imageProfile)
  775. imageProfile.setImage(name: pictureImage!)
  776. imageProfile.contentMode = .scaleAspectFill
  777. } else {
  778. viewAppBar.addSubview(imageProfile)
  779. imageProfile.image = UIImage(systemName: "person")
  780. imageProfile.contentMode = .scaleAspectFit
  781. imageProfile.backgroundColor = .lightGray
  782. }
  783. var titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 250, height: 44))
  784. if blocking == "-1" || blocking == "1" {
  785. titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
  786. } else if isContactCenter {
  787. titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
  788. if users.count > 0 {
  789. titleNavigation = UILabel(frame: CGRect(x: 35 * (CGFloat(users.count)) - (CGFloat((users.count - 1) * 15)), y: 0, width: viewAppBar.frame.size.width - 150 - (35 * (CGFloat(users.count - 1)) - (CGFloat((users.count - 1) * 15))), height: 44))
  790. }
  791. }
  792. viewAppBar.addSubview(titleNavigation)
  793. if ((User.isOfficial(official_account: (dataPerson["isOfficial"] ?? "")!) || User.isOfficialRegular(official_account: (dataPerson["isOfficial"] ?? "")!)) && !isContactCenter) || ((User.isOfficial(official_account: (dataPerson["isOfficial"] ?? "")!) || User.isOfficialRegular(official_account: (dataPerson["isOfficial"] ?? "")!)) && fPinContacCenter.isEmpty) {
  794. var name = dataPerson["name"]!!
  795. if (isContactCenter) {
  796. name = name + " " + "Contact Center".localized()
  797. titleNavigation.text = name
  798. } else {
  799. titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  800. }
  801. } else if User.isVerified(official_account: (dataPerson["isOfficial"] ?? "")!) && !isContactCenter {
  802. let name = dataPerson["name"]!!
  803. titleNavigation.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  804. } else if User.isInternal(userType: (dataPerson["user_type"] ?? "")!) && !isContactCenter {
  805. let name = dataPerson["name"]!!
  806. titleNavigation.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  807. } else {
  808. if !isContactCenter {
  809. titleNavigation.text = dataPerson["name"] as? String
  810. } else {
  811. if users.count == 1 {
  812. titleNavigation.text = users[0].fullName
  813. } else {
  814. var stringName = ""
  815. for user in users {
  816. if stringName.isEmpty {
  817. stringName = user.fullName
  818. } else {
  819. stringName += ", \(user.fullName)"
  820. }
  821. }
  822. titleNavigation.text = stringName
  823. }
  824. }
  825. }
  826. titleNavigation.textColor = .white
  827. titleNavigation.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  828. navigationItem.titleView = viewAppBar
  829. titleText = titleNavigation.text
  830. } else {
  831. searchBar = UISearchBar()
  832. searchBar.autocapitalizationType = .none
  833. searchBar.delegate = self
  834. searchBar.searchTextField.tintColor = .mainColor
  835. searchBar.searchTextField.textColor = .mainColor
  836. searchBar.showsCancelButton = false
  837. // searchBar.setMagnifyingGlassColorTo(color: .white)
  838. // searchBar.updateHeight(height: 36, radius: 18)
  839. searchBar.setImage(UIImage(), for: .search, state: .normal)
  840. searchBar.setPositionAdjustment(UIOffset(horizontal: 10, vertical: 0), for: .search)
  841. searchBar.setCustomBackgroundImage(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
  842. navigationItem.titleView = searchBar
  843. self.definesPresentationContext = true
  844. }
  845. if copySession || forwardSession || deleteSession || isSearching {
  846. navigationItem.hidesBackButton = true
  847. navigationController?.interactivePopGestureRecognizer?.isEnabled = false
  848. } else {
  849. navigationItem.hidesBackButton = false
  850. navigationController?.interactivePopGestureRecognizer?.isEnabled = true
  851. }
  852. viewAppBar.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(seeProfileTapped)))
  853. }
  854. private func getDataProfile(fPin: String) {
  855. var query = "SELECT f_pin, first_name || ' ' || last_name, official_account, image_id, device_id, offline_mode, user_type FROM BUDDY where f_pin = '\(fPin)'"
  856. if (isContactCenter && isRequestContactCenter) {
  857. query = "SELECT group_id, f_name, official, image_id FROM GROUPZ where group_type = 1 AND official = 1"
  858. }
  859. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  860. do {
  861. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  862. if cursorData.next() {
  863. dataPerson["f_pin"] = cursorData.string(forColumnIndex: 0)
  864. dataPerson["name"] = cursorData.string(forColumnIndex: 1)?.trimmingCharacters(in: .whitespaces)
  865. dataPerson["picture"] = cursorData.string(forColumnIndex: 3)
  866. dataPerson["isOfficial"] = cursorData.string(forColumnIndex: 2)
  867. if isContactCenter && isRequestContactCenter {
  868. dataPerson["user_type"] = "0"
  869. } else {
  870. dataPerson["user_type"] = cursorData.string(forColumnIndex: 6)
  871. }
  872. } else {
  873. dataPerson["f_pin"] = "-999"
  874. dataPerson["name"] = "Bot"
  875. dataPerson["picture"] = ""
  876. dataPerson["isOfficial"] = ""
  877. dataPerson["deviceId"] = ""
  878. dataPerson["isOffline"] = "0"
  879. dataPerson["user_type"] = "0"
  880. }
  881. cursorData.close()
  882. }
  883. } catch {
  884. rollback.pointee = true
  885. print("Access database error: \(error.localizedDescription)")
  886. }
  887. })
  888. }
  889. private func addDataMessage() {
  890. multipleOffsetUp += 1
  891. let queryCount = "SELECT COUNT(*) FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0"
  892. let query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin, last_edited, gif_id, is_forwarded_message FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20*\(multipleOffsetUp-1) ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
  893. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  894. do {
  895. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  896. var tempData: [[String: Any?]] = []
  897. while cursorData.next() {
  898. var row: [String: Any?] = [:]
  899. row["message_id"] = cursorData.string(forColumnIndex: 0)
  900. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  901. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  902. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  903. row["server_date"] = cursorData.string(forColumnIndex: 4)
  904. row["status"] = cursorData.string(forColumnIndex: 5)
  905. row["message_text"] = cursorData.string(forColumnIndex: 6)
  906. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  907. row["video_id"] = cursorData.string(forColumnIndex: 8)
  908. row["image_id"] = cursorData.string(forColumnIndex: 9)
  909. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  910. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  911. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  912. row["file_id"] = cursorData.string(forColumnIndex: 13)
  913. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  914. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  915. row["lock"] = cursorData.string(forColumnIndex: 16)
  916. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  917. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  918. row["credential"] = cursorData.string(forColumnIndex: 19)
  919. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  920. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  921. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  922. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  923. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  924. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  925. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
  926. while cursorStatus.next() {
  927. row["status"] = cursorStatus.string(forColumnIndex: 0)
  928. }
  929. cursorStatus.close()
  930. }
  931. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  932. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  933. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  934. if let dirPath = paths.first {
  935. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  936. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  937. if ((row["video_id"] as? String ?? "") != "") {
  938. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
  939. row["progress"] = 100.0
  940. } else {
  941. row["progress"] = 0.0
  942. }
  943. } else {
  944. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent){
  945. row["progress"] = 100.0
  946. } else {
  947. row["progress"] = 0.0
  948. }
  949. }
  950. }
  951. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  952. row["isSelected"] = false
  953. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  954. let idMe = User.getMyPin()!
  955. if row["f_pin"] as? String ?? "" == idMe {
  956. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  957. if second > 60 {
  958. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  959. row["lock"] = "2"
  960. row["reff_id"] = ""
  961. DispatchQueue.global().async {
  962. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  963. do {
  964. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  965. "lock" : "2"
  966. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  967. } catch {
  968. rollback.pointee = true
  969. print("Access database error: \(error.localizedDescription)")
  970. }
  971. })
  972. }
  973. } else {
  974. let second = 60 - second
  975. listTimerCredential[row["message_id"] as? String ?? ""] = second
  976. }
  977. } else {
  978. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  979. if hasMessageId != nil {
  980. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  981. if second > 60 {
  982. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  983. row["lock"] = "2"
  984. row["reff_id"] = ""
  985. DispatchQueue.global().async {
  986. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  987. do {
  988. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  989. "lock" : "2"
  990. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  991. } catch {
  992. rollback.pointee = true
  993. print("Access database error: \(error.localizedDescription)")
  994. }
  995. })
  996. }
  997. } else {
  998. let second = 60 - second
  999. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1000. }
  1001. } else {
  1002. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1003. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1004. }
  1005. }
  1006. }
  1007. tempData.append(row)
  1008. }
  1009. if tempData.count != 0 && (dataMessages.firstIndex(where: { $0["message_id"] as? String == tempData[0]["message_id"] as? String }) == nil) {
  1010. let lastIndex = tempData.count - 1
  1011. for i in 0..<tempData.count {
  1012. tableChatView.beginUpdates()
  1013. dataMessages.insert(tempData[lastIndex - i], at: 0)
  1014. if dataMessages.firstIndex(where: { $0["chat_date"] as? String == tempData[lastIndex - i]["chat_date"] as? String }) != nil {
  1015. tableChatView.insertRows(at: [IndexPath(row: 0, section: currentIndexpath!.section)], with: .top)
  1016. } else {
  1017. tableChatView.insertSections(IndexSet(integer: 0), with: .top)
  1018. tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
  1019. }
  1020. tableChatView.endUpdates()
  1021. }
  1022. }
  1023. cursorData.close()
  1024. gettingDataMessage = false
  1025. }
  1026. } catch {
  1027. rollback.pointee = true
  1028. print("Access database error: \(error.localizedDescription)")
  1029. }
  1030. })
  1031. }
  1032. private func getData() {
  1033. // let queryCount = "SELECT COUNT(*) FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0"
  1034. // var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20 ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
  1035. var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin, last_edited, gif_id, is_forwarded_message FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 order by server_date asc"
  1036. if isContactCenter {
  1037. if complaintId.isEmpty {
  1038. query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND message_scope_id = '5' AND broadcast_flag = 0 AND is_call_center = 1 order by server_date asc"
  1039. } else {
  1040. query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where message_scope_id = '5' AND broadcast_flag = 0 AND is_call_center = 1 AND call_center_id = '\(complaintId)' order by server_date asc"
  1041. }
  1042. if isRequestContactCenter && !isDirectCC {
  1043. viewButton.isHidden = true
  1044. viewTextfield.isHidden = true
  1045. var row: [String: Any?] = [:]
  1046. row["f_pin"] = nil
  1047. row["message_id"] = ""
  1048. row["chat_date"] = "Today".localized()
  1049. let listStringName: [String] = ["Messaging".localized(), "Secure SMS".localized(), "VoIP Call".localized(), "Email".localized(), "Video Call".localized(), "GSM Call".localized(), "GPT Chatbot".localized(), "WhatsApp"]
  1050. var data : [CategoryCC] = []
  1051. let channels : [String] = ["0", "4", "1", "3", "2", "5", "7", "6"]
  1052. if Utils.getDefaultCC() == "No" {
  1053. let category = CategoryCC.getDatafromParent(parent: CategoryCC.default_parent)
  1054. for i in 0..<category.count {
  1055. data.append(CategoryCC(id: "level0_\(i)", service_id: category[i].service_id, service_name: category[i].service_name, parent: category[i].parent, description: category[i].description, is_tablet: "0"))
  1056. }
  1057. } else {
  1058. for i in 0..<listStringName.count {
  1059. data.append(CategoryCC(id: "level0_\(channels[i])", service_id: "", service_name: listStringName[i], parent: "\(i)", description: "", is_tablet: "0"))
  1060. }
  1061. row["attachment_flag"] = "503"
  1062. }
  1063. row["category_cc"] = data
  1064. dataDates.append("Today".localized())
  1065. dataMessages.append(row)
  1066. } else if isDirectCC {
  1067. dataDates.append("Today".localized())
  1068. }
  1069. }
  1070. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1071. do {
  1072. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  1073. var tempImages: [ImageGrouping] = []
  1074. while cursorData.next() {
  1075. var row: [String: Any?] = [:]
  1076. row["message_id"] = cursorData.string(forColumnIndex: 0)
  1077. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  1078. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  1079. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  1080. row["server_date"] = cursorData.string(forColumnIndex: 4)
  1081. row["status"] = cursorData.string(forColumnIndex: 5)
  1082. row["message_text"] = cursorData.string(forColumnIndex: 6)
  1083. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  1084. row["video_id"] = cursorData.string(forColumnIndex: 8)
  1085. row["image_id"] = cursorData.string(forColumnIndex: 9)
  1086. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  1087. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  1088. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  1089. row["file_id"] = cursorData.string(forColumnIndex: 13)
  1090. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  1091. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  1092. row["lock"] = cursorData.string(forColumnIndex: 16)
  1093. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  1094. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  1095. row["credential"] = cursorData.string(forColumnIndex: 19)
  1096. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  1097. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  1098. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  1099. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  1100. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  1101. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  1102. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
  1103. while cursorStatus.next() {
  1104. row["status"] = cursorStatus.string(forColumnIndex: 0)
  1105. }
  1106. cursorStatus.close()
  1107. }
  1108. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  1109. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  1110. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  1111. if let dirPath = paths.first {
  1112. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  1113. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  1114. if ((row["video_id"] as? String ?? "") != "") {
  1115. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
  1116. row["progress"] = 100.0
  1117. } else {
  1118. row["progress"] = 0.0
  1119. }
  1120. } else {
  1121. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent){
  1122. row["progress"] = 100.0
  1123. } else {
  1124. row["progress"] = 0.0
  1125. }
  1126. }
  1127. }
  1128. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  1129. row["isSelected"] = false
  1130. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1131. let idMe = User.getMyPin()!
  1132. if row["f_pin"] as? String ?? "" == idMe {
  1133. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  1134. if second > 60 {
  1135. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1136. row["lock"] = "2"
  1137. row["reff_id"] = ""
  1138. DispatchQueue.global().async {
  1139. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1140. do {
  1141. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1142. "lock" : "2"
  1143. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1144. } catch {
  1145. rollback.pointee = true
  1146. print("Access database error: \(error.localizedDescription)")
  1147. }
  1148. })
  1149. }
  1150. } else {
  1151. let second = 60 - second
  1152. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1153. }
  1154. } else {
  1155. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  1156. if hasMessageId != nil {
  1157. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  1158. if second > 60 {
  1159. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1160. row["lock"] = "2"
  1161. row["reff_id"] = ""
  1162. DispatchQueue.global().async {
  1163. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1164. do {
  1165. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1166. "lock" : "2"
  1167. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1168. } catch {
  1169. rollback.pointee = true
  1170. print("Access database error: \(error.localizedDescription)")
  1171. }
  1172. })
  1173. }
  1174. } else {
  1175. let second = 60 - second
  1176. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1177. }
  1178. } else {
  1179. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1180. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1181. }
  1182. }
  1183. }
  1184. if (dataMessages.count == 0 || dataMessages.last!["f_pin"] as? String ?? "" == row["f_pin"] as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"] as? String ?? "").isEmpty && (row["message_text"] as? String ?? "").isEmpty && (row["reff_id"] as? String ?? "").isEmpty && (row["credential"] as? String ?? "") != "1" && (row["read_receipts"] as? String ?? "") != "8" {
  1185. if tempImages.count != 0 && getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(tempImages.last!.time)!), end: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!))/60 >= 11 {
  1186. if tempImages.count >= 4 {
  1187. groupImages[tempImages[0].messageId] = tempImages
  1188. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1189. for _ in 1..<tempImages.count {
  1190. dataMessages.remove(at: idxTemp + 1)
  1191. }
  1192. }
  1193. }
  1194. tempImages.removeAll()
  1195. }
  1196. tempImages.append(ImageGrouping(messageId: row["message_id"] as? String ?? "", thumbId: row["thumb_id"] as? String ?? "", imageId: row["image_id"] as? String ?? "", status: row["status"] as? String ?? "", time: row["server_date"] as? String ?? "", lPin: row["l_pin"] as? String ?? "", dataMessage: row, dataPerson: dataPerson, dataGroup: [:], dataTopic: [:]))
  1197. } else if tempImages.count >= 4 {
  1198. groupImages[tempImages[0].messageId] = tempImages
  1199. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1200. for _ in 1..<tempImages.count {
  1201. dataMessages.remove(at: idxTemp + 1)
  1202. }
  1203. }
  1204. tempImages.removeAll()
  1205. } else if tempImages.count != 0 {
  1206. tempImages.removeAll()
  1207. }
  1208. dataMessages.append(row)
  1209. }
  1210. if tempImages.count >= 4 {
  1211. if tempImages.count > 30 {
  1212. tempImages.removeSubrange(30..<tempImages.count)
  1213. }
  1214. groupImages[tempImages[0].messageId] = tempImages
  1215. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1216. for _ in 1..<tempImages.count {
  1217. dataMessages.remove(at: idxTemp + 1)
  1218. }
  1219. }
  1220. }
  1221. cursorData.close()
  1222. }
  1223. } catch {
  1224. rollback.pointee = true
  1225. print("Access database error: \(error.localizedDescription)")
  1226. }
  1227. })
  1228. }
  1229. func getSecondsDifferenceFromTwoDates(start: Date, end: Date) -> Int {
  1230. let diff = Int(end.timeIntervalSince1970 - start.timeIntervalSince1970)
  1231. let hours = diff / 3600
  1232. let seconds = (diff - hours * 3600)
  1233. return seconds
  1234. }
  1235. func chatDate(stringDate: String) -> String {
  1236. let date = Date(milliseconds: Int64(stringDate)!)
  1237. let calendar = Calendar.current
  1238. if (calendar.isDateInToday(date)) {
  1239. if !dataDates.contains("Today".localized()){
  1240. dataDates.append("Today".localized())
  1241. }
  1242. return "Today".localized()
  1243. } else {
  1244. let startOfNow = calendar.startOfDay(for: Date())
  1245. let startOfTimeStamp = calendar.startOfDay(for: date)
  1246. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  1247. let day = -(components.day!)
  1248. if day == 1{
  1249. if !dataDates.contains("Yesterday".localized()){
  1250. dataDates.append("Yesterday".localized())
  1251. }
  1252. return "Yesterday".localized()
  1253. } else if day < 7 {
  1254. let formatter = DateFormatter()
  1255. formatter.dateFormat = "EEEE"
  1256. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  1257. if lang == "id" {
  1258. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1259. }
  1260. if !dataDates.contains(formatter.string(from: date)){
  1261. dataDates.append(formatter.string(from: date))
  1262. }
  1263. return formatter.string(from: date)
  1264. } else {
  1265. let formatter = DateFormatter()
  1266. formatter.dateFormat = "EE, dd MMM"
  1267. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  1268. if lang == "id" {
  1269. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1270. }
  1271. let stringFormat = formatter.string(from: date as Date)
  1272. if !dataDates.contains(stringFormat){
  1273. dataDates.append(stringFormat)
  1274. }
  1275. return stringFormat
  1276. }
  1277. }
  1278. }
  1279. func updateProgress(_ data: [AnyHashable : Any]){
  1280. var isImage = false
  1281. var idx = dataMessages.lastIndex(where: { $0["video_id"] as? String == data["name"] as? String || $0["video_id"] as? String == data["video_id"] as? String })
  1282. if (idx == nil) {
  1283. idx = dataMessages.lastIndex(where: { $0["image_id"] as? String == data["name"] as? String || $0["image_id"] as? String == data["image_id"] as? String })
  1284. isImage = true
  1285. }
  1286. if (idx != nil) {
  1287. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  1288. if section == nil {
  1289. return
  1290. }
  1291. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == dataMessages[idx!]["message_id"] as? String})
  1292. if row == nil {
  1293. return
  1294. }
  1295. DispatchQueue.main.async {
  1296. let indexPath = IndexPath(row: row!, section: section!)
  1297. if(self.fakeProgMultip < self.maxFakeProgMultip){
  1298. self.fakeProgMultip = self.fakeProgMultip + 1
  1299. }
  1300. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  1301. let progress = max(data["progress"] as! Double, fakeProgress)
  1302. if(data["progress"] as! Double == 100.0){
  1303. self.fakeProgMultip = 0
  1304. }
  1305. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1306. for view in cell.contentView.subviews {
  1307. if !(view is UITextView) && !(view is UIImageView) {
  1308. for viewInContainer in view.subviews {
  1309. if viewInContainer is UIImageView {
  1310. if viewInContainer.subviews.count == 0 {
  1311. return
  1312. }
  1313. var containerView : UIView?
  1314. if (isImage) {
  1315. containerView = viewInContainer.subviews[0]
  1316. } else if viewInContainer.subviews.count > 1 {
  1317. containerView = viewInContainer.subviews[1]
  1318. }
  1319. if let loading = containerView?.layer.sublayers?[1] as? CAShapeLayer {
  1320. loading.strokeEnd = CGFloat(progress / 100)
  1321. if (progress == 100.0) {
  1322. self.dataMessages[idx!]["progress"] = progress
  1323. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1324. }
  1325. }
  1326. }
  1327. }
  1328. }
  1329. }
  1330. }
  1331. }
  1332. } else {
  1333. idx = dataMessages.lastIndex(where: { $0["file_id"] as? String == data["name"] as? String || $0["file_id"] as? String == data["file_id"] as? String })
  1334. if (idx != nil) {
  1335. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  1336. if section == nil {
  1337. return
  1338. }
  1339. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == dataMessages[idx!]["message_id"] as? String})
  1340. if row == nil {
  1341. return
  1342. }
  1343. DispatchQueue.main.async {
  1344. let indexPath = IndexPath(row: row!, section: section!)
  1345. if(self.fakeProgMultip < self.maxFakeProgMultip){
  1346. self.fakeProgMultip = self.fakeProgMultip + 1
  1347. }
  1348. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  1349. let progress = max(data["progress"] as! Double, fakeProgress)
  1350. if(data["progress"] as! Double == 100.0){
  1351. self.fakeProgMultip = 0
  1352. }
  1353. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1354. for view in cell.contentView.subviews {
  1355. if !(view is UITextView) && !(view is UIImageView) {
  1356. for viewSubviews in view.subviews {
  1357. if !(viewSubviews is UITextView) {
  1358. for viewInContainer in viewSubviews.subviews {
  1359. if !(viewInContainer is UITextView) && !(viewInContainer is UIImageView) {
  1360. if let cont = viewInContainer.layer.sublayers {
  1361. if cont.count < 2 {
  1362. return
  1363. }
  1364. }
  1365. if let layers = viewInContainer.layer.sublayers {
  1366. if let loading = layers [1] as? CAShapeLayer {
  1367. loading.strokeEnd = CGFloat(progress / 100)
  1368. if (progress == 100.0) {
  1369. self.dataMessages[idx!]["progress"] = progress
  1370. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1371. }
  1372. }
  1373. }
  1374. }
  1375. }
  1376. }
  1377. }
  1378. }
  1379. }
  1380. }
  1381. }
  1382. }
  1383. }
  1384. }
  1385. @objc func onUploadChat(notification: NSNotification) {
  1386. let data:[AnyHashable : Any] = notification.userInfo!
  1387. updateProgress(data)
  1388. }
  1389. @objc func onReceiveMessage(notification: NSNotification) {
  1390. DispatchQueue.main.async { [self] in
  1391. let data:[AnyHashable : Any] = notification.userInfo!
  1392. if let dataMessage = data["message"] as? TMessage {
  1393. let chatData = dataMessage.mBodies
  1394. if (dataMessage.getCode() == CoreMessage_TMessageCode.PUSH_MEMBER_ROOM_CONTACT_CENTER && isContactCenter) {
  1395. let data = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  1396. if !data.isEmpty {
  1397. if let jsonArray = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
  1398. var members = ""
  1399. let idMe = User.getMyPin()!
  1400. var user : [User] = []
  1401. for json in jsonArray {
  1402. if "\(json)" != idMe {
  1403. if members.isEmpty {
  1404. members = "\(json)"
  1405. } else {
  1406. members += ",\(json)"
  1407. }
  1408. if let userData = User.getData(pin: "\(json)") {
  1409. user.append(userData)
  1410. } else {
  1411. Nexilis.addFriend (fpin: "\(json)") { result in
  1412. DispatchQueue.main.async {
  1413. if result {
  1414. let userData = User.getData(pin: "\(json)")!
  1415. user.append(userData)
  1416. } else {
  1417. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  1418. imageView.tintColor = .white
  1419. let banner = FloatingNotificationBanner(title: "Server busy, please try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  1420. banner.show()
  1421. }
  1422. }
  1423. }
  1424. }
  1425. }
  1426. }
  1427. self.users = user
  1428. self.fPinContacCenter = members
  1429. self.changeAppBar()
  1430. SecureUserDefaults.shared.set(members, forKey: "inEditorPersonal")
  1431. }
  1432. }
  1433. } else if (dataMessage.getCode() == CoreMessage_TMessageCode.ACCEPT_CALL_CENTER) {
  1434. if !self.isRequestContactCenter || !isContactCenter {
  1435. return
  1436. }
  1437. SecureUserDefaults.shared.set(dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN), forKey: "inEditorPersonal")
  1438. let date = Date()
  1439. let formatter = DateFormatter()
  1440. formatter.dateFormat = "HH:mm"
  1441. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1442. self.fPinContacCenter = dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN)
  1443. self.complaintId = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  1444. self.channelContactCenter = dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL)
  1445. var row: [String: Any?] = [:]
  1446. row["category_cc"] = "You are connecting with ".localized() + dataMessage.getBody(key: CoreMessage_TMessageKey.F_DISPLAY_NAME).trimmingCharacters(in: .whitespaces) + " at ".localized() + formatter.string(from: date as Date) + ".\n" + "In order to improve our service, all conversations will be recorded\naccording to state regulations".localized()
  1447. row["message_id"] = ""
  1448. row["message_text"] = "You are connecting with ".localized() + dataMessage.getBody(key: CoreMessage_TMessageKey.F_DISPLAY_NAME).trimmingCharacters(in: .whitespaces) + " at ".localized() + formatter.string(from: date as Date) + ".\n" + "In order to improve our service, all conversations will be recorded\naccording to state regulations".localized()
  1449. row["chat_date"] = "Today".localized()
  1450. self.dataMessages.append(row)
  1451. self.users.append(User.getData(pin: dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN))!)
  1452. self.changeAppBar()
  1453. self.setRightButtonItem()
  1454. self.dateStartCC = "\(Date().currentTimeMillis())"
  1455. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  1456. self.tableChatView.scrollToBottom()
  1457. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  1458. if dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL) != "0" {
  1459. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: "startTimeCC")
  1460. SecureUserDefaults.shared.set(dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL), forKey: "channelCC")
  1461. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  1462. self.dismiss(animated: true, completion: {
  1463. self.removeAllObjectBeforeDismissVC()
  1464. })
  1465. })
  1466. } else {
  1467. viewButton.isHidden = false
  1468. viewTextfield.isHidden = false
  1469. }
  1470. } else if (dataMessage.getCode() == CoreMessage_TMessageCode.INVITE_END_CONTACT_CENTER || dataMessage.getCode() == CoreMessage_TMessageCode.END_CALL_CENTER || dataMessage.getCode() == CoreMessage_TMessageCode.INVITE_EXIT_CONTACT_CENTER) && !fromVCAC {
  1471. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1472. if onGoingCC.isEmpty || !isContactCenter {
  1473. return
  1474. }
  1475. let requester = onGoingCC.components(separatedBy: ",")[0]
  1476. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1477. let fPin = dataMessage.getCode() == CoreMessage_TMessageCode.END_CALL_CENTER ? chatData[CoreMessage_TMessageKey.F_PIN] : dataMessage.getPIN()
  1478. if fPin == officer || fPin == requester {
  1479. DispatchQueue.global().async {
  1480. let date = "\(Date().currentTimeMillis())"
  1481. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1482. do {
  1483. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  1484. "type" : self.channelContactCenter,
  1485. "title" : "Contact Center".localized(),
  1486. "time" : self.dateStartCC,
  1487. "f_pin" : officer,
  1488. "data" : self.complaintId,
  1489. "time_end" : date,
  1490. "complaint_id" : self.complaintId,
  1491. "members" : "",
  1492. "requester": requester
  1493. ], replace: true)
  1494. } catch {
  1495. rollback.pointee = true
  1496. print("Access database error: \(error.localizedDescription)")
  1497. }
  1498. })
  1499. }
  1500. self.dismissKeyboard()
  1501. self.disableEditor()
  1502. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  1503. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  1504. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  1505. DispatchQueue.main.async {
  1506. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1507. imageView.tintColor = .white
  1508. let banner = FloatingNotificationBanner(title: "Call Center Session has ended".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  1509. banner.show()
  1510. }
  1511. timeoutCC.invalidate()
  1512. DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
  1513. if !(self.presentedViewController is EditorPersonal) {
  1514. self.dismiss(animated: true, completion: {
  1515. self.removeAllObjectBeforeDismissVC()
  1516. })
  1517. }
  1518. DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
  1519. self.dismiss(animated: true, completion: {
  1520. self.removeAllObjectBeforeDismissVC()
  1521. })
  1522. }
  1523. })
  1524. } else {
  1525. var members = ""
  1526. self.users.removeAll(where: {$0.pin == fPin})
  1527. for user in self.users {
  1528. if members.isEmpty {
  1529. members = "\(user.pin)"
  1530. } else {
  1531. members = ",\(user.pin)"
  1532. }
  1533. }
  1534. SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
  1535. self.fPinContacCenter = members
  1536. self.changeAppBar()
  1537. }
  1538. }
  1539. else if (chatData[CoreMessage_TMessageKey.F_PIN] == self.dataPerson["f_pin"]!! && !self.isContactCenter && (chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == "3")) || (self.isContactCenter && chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == "5" && self.complaintId == chatData[CoreMessage_TMessageKey.CALL_CENTER_ID]) {
  1540. if chatData[CoreMessage_TMessageKey.F_PIN] == nil {
  1541. return
  1542. }
  1543. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]})
  1544. if idx != nil {
  1545. self.dataMessages[idx!][TypeDataMessage.message_text] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1546. self.dataMessages[idx!][TypeDataMessage.last_edit] = Int64(chatData[CoreMessage_TMessageKey.LAST_EDIT]!)
  1547. self.dataMessages[idx!][TypeDataMessage.status] = chatData[CoreMessage_TMessageKey.STATUS]
  1548. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1549. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[idx!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  1550. if row != nil && section != nil {
  1551. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1552. }
  1553. return
  1554. }
  1555. var row: [String: Any?] = [:]
  1556. row["message_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_ID]
  1557. row["f_pin"] = chatData[CoreMessage_TMessageKey.F_PIN]
  1558. row["l_pin"] = chatData[CoreMessage_TMessageKey.L_PIN]
  1559. row["message_scope_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]
  1560. row["server_date"] = chatData[CoreMessage_TMessageKey.SERVER_DATE]
  1561. row["status"] = chatData[CoreMessage_TMessageKey.STATUS]
  1562. row["message_text"] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1563. if (chatData.keys.contains(CoreMessage_TMessageKey.AUDIO_ID)) {
  1564. row["audio_id"] = chatData[CoreMessage_TMessageKey.AUDIO_ID]
  1565. } else {
  1566. row["audio_id"] = ""
  1567. }
  1568. if (chatData.keys.contains(CoreMessage_TMessageKey.GIF_ID)) {
  1569. row["gif_id"] = chatData[CoreMessage_TMessageKey.GIF_ID]
  1570. } else {
  1571. row["gif_id"] = ""
  1572. }
  1573. if (chatData.keys.contains(CoreMessage_TMessageKey.VIDEO_ID)) {
  1574. row["video_id"] = chatData[CoreMessage_TMessageKey.VIDEO_ID]
  1575. } else {
  1576. row["video_id"] = ""
  1577. }
  1578. if (chatData.keys.contains(CoreMessage_TMessageKey.IMAGE_ID)) {
  1579. row["image_id"] = chatData[CoreMessage_TMessageKey.IMAGE_ID]
  1580. } else {
  1581. row["image_id"] = ""
  1582. }
  1583. if (chatData.keys.contains(CoreMessage_TMessageKey.THUMB_ID)) {
  1584. row["thumb_id"] = chatData[CoreMessage_TMessageKey.THUMB_ID]
  1585. } else {
  1586. row["thumb_id"] = ""
  1587. }
  1588. if (chatData.keys.contains(CoreMessage_TMessageKey.READ_RECEIPTS)) {
  1589. row["read_receipts"] = chatData[CoreMessage_TMessageKey.READ_RECEIPTS]
  1590. } else {
  1591. row["read_receipts"] = ""
  1592. }
  1593. if (chatData.keys.contains(CoreMessage_TMessageKey.CREDENTIAL)) {
  1594. row["credential"] = chatData[CoreMessage_TMessageKey.CREDENTIAL]
  1595. } else {
  1596. row["credential"] = ""
  1597. }
  1598. row["chat_id"] = ""
  1599. if (chatData.keys.contains(CoreMessage_TMessageKey.FILE_ID)) {
  1600. row["file_id"] = chatData[CoreMessage_TMessageKey.FILE_ID]
  1601. } else {
  1602. row["file_id"] = ""
  1603. }
  1604. row["progress"] = 0.0
  1605. row["attachment_flag"] = chatData[CoreMessage_TMessageKey.ATTACHMENT_FLAG]
  1606. row["reff_id"] = chatData[CoreMessage_TMessageKey.REF_ID] ?? ""
  1607. row["lock"] = ""
  1608. row["is_stared"] = "0"
  1609. row[TypeDataMessage.is_forwarded] = Int(chatData[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] ?? "0")
  1610. row["isSelected"] = false
  1611. if !self.dataDates.contains("Today".localized()) {
  1612. self.dataDates.append("Today".localized())
  1613. self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
  1614. }
  1615. row["chat_date"] = "Today".localized()
  1616. row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
  1617. self.counter += 1
  1618. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1619. self.listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1620. }
  1621. self.dataMessages.append(row)
  1622. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
  1623. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1624. var timer = Timer()
  1625. var minute = 60
  1626. self.timerCredential[row["message_id"] as? String ?? ""] = timer
  1627. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1628. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  1629. minute -= 1
  1630. self.listTimerCredential[row["message_id"] as? String ?? ""] = minute
  1631. if minute == 0 {
  1632. timer.invalidate()
  1633. self.listTimerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1634. self.timerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1635. SecureUserDefaults.shared.removeValue(forKey: row["message_id"] as? String ?? "")
  1636. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == row["message_id"] as? String})
  1637. if idx != nil {
  1638. self.dataMessages[idx!]["lock"] = "2"
  1639. self.dataMessages[idx!]["reff_id"] = ""
  1640. }
  1641. DispatchQueue.global().async {
  1642. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1643. do {
  1644. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1645. "lock" : "2"
  1646. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1647. } catch {
  1648. rollback.pointee = true
  1649. print("Access database error: \(error.localizedDescription)")
  1650. }
  1651. })
  1652. }
  1653. }
  1654. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  1655. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).firstIndex(where: { $0["message_id"] as? String == row["message_id"] as? String})
  1656. if row != nil && section != nil{
  1657. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1658. }
  1659. })
  1660. }
  1661. if self.isContactCenter {
  1662. let idMe = User.getMyPin()!
  1663. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1664. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1665. if officer == idMe {
  1666. self.timeoutCC.invalidate()
  1667. } else if !fromVCAC {
  1668. if !self.showToast30s {
  1669. self.view.makeToast("Please reply within 30 seconds so the call center session doesn't end.".localized(), duration: 3)
  1670. sendTyping(l_pin: fPinContacCenter, isTyping: true)
  1671. self.showToast30s = true
  1672. }
  1673. }
  1674. }
  1675. if chatData[CoreMessage_TMessageKey.FORMAT] == "1" {
  1676. self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1677. self.tableChatView.scrollToBottom()
  1678. } else if self.currentIndexpath?.row == (self.dataMessages.count - 2) {
  1679. if (self.viewIfLoaded?.window != nil) {
  1680. self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1681. }
  1682. self.tableChatView.scrollToBottom()
  1683. if ( self.currentIndexpath!.section <= self.dataDates.count - 1 && self.currentIndexpath!.row <= self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1) {
  1684. self.counter = 0
  1685. self.updateCounter(counter: self.counter)
  1686. }
  1687. let lastMarkerCounter = markerCounter
  1688. if self.markerCounter != nil {
  1689. self.markerCounter = nil
  1690. }
  1691. self.tableChatView.beginUpdates()
  1692. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  1693. if indexMessage != nil {
  1694. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1695. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[indexMessage!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[indexMessage!]["message_id"] as? String })
  1696. if row != nil && section != nil {
  1697. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1698. }
  1699. }
  1700. self.tableChatView.endUpdates()
  1701. }
  1702. else if self.currentIndexpath == nil {
  1703. self.counter = 0
  1704. self.updateCounter(counter: self.counter)
  1705. if (self.viewIfLoaded?.window != nil) {
  1706. self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1707. }
  1708. }
  1709. else if self.counter != 0 {
  1710. if !self.indicatorCounterBSTB.isDescendant(of: self.view) && self.buttonScrollToBottom.isDescendant(of: self.view) {
  1711. self.markerCounter = row["message_id"] as? String
  1712. self.addCounterAtButttonScrollToBottom()
  1713. self.tableChatView.beginUpdates()
  1714. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.markerCounter })
  1715. if indexMessage != nil {
  1716. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1717. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[indexMessage!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[indexMessage!]["message_id"] as? String })
  1718. if row != nil && section != nil {
  1719. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1720. }
  1721. }
  1722. self.tableChatView.endUpdates()
  1723. } else if self.indicatorCounterBSTB.isDescendant(of: self.view) {
  1724. self.labelCounter.text = "\(self.counter)"
  1725. }
  1726. }
  1727. }
  1728. } else if !self.isContactCenter {
  1729. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1730. }
  1731. }
  1732. }
  1733. private func disableEditor() {
  1734. view.addSubview(containerAction)
  1735. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1736. NSLayoutConstraint.activate([
  1737. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1738. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1739. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1740. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1741. ])
  1742. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1743. let labelDisable = UILabel()
  1744. containerAction.addSubview(labelDisable)
  1745. labelDisable.translatesAutoresizingMaskIntoConstraints = false
  1746. NSLayoutConstraint.activate([
  1747. labelDisable.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1748. labelDisable.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1749. ])
  1750. labelDisable.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1751. labelDisable.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1752. labelDisable.text = "Call center session is over".localized()
  1753. }
  1754. @objc func onStatusChat(notification: NSNotification) {
  1755. DispatchQueue.main.async {
  1756. let data:[AnyHashable : Any] = notification.userInfo!
  1757. if let dataMessage = data["message"] as? TMessage {
  1758. let chatData = dataMessage.mBodies
  1759. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1760. let requester = onGoingCC.components(separatedBy: ",")[0]
  1761. let idMe = User.getMyPin()!
  1762. if chatData[CoreMessage_TMessageKey.F_PIN] == self.dataPerson["f_pin"]!! || chatData[CoreMessage_TMessageKey.L_PIN] == self.dataPerson["f_pin"]!! || chatData[CoreMessage_TMessageKey.L_PIN] == self.fPinContacCenter || requester == idMe {
  1763. if (chatData.keys.contains(CoreMessage_TMessageKey.MESSAGE_ID) && !(chatData[CoreMessage_TMessageKey.MESSAGE_ID]!).contains("-2,")) {
  1764. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! })
  1765. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) }) {
  1766. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) {
  1767. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1768. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1769. }
  1770. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1771. }
  1772. if (idx != nil) {
  1773. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1774. self.updateStatusDelete(idx: idx, chatData: chatData)
  1775. } else {
  1776. self.updateStatusMessage(idx: idx, chatData: chatData)
  1777. }
  1778. }
  1779. }
  1780. else if (chatData.keys.contains("message_id")) {
  1781. var idMessage = dataMessage.getBody(key: "message_id")
  1782. if idMessage.contains("'") {
  1783. idMessage = idMessage.replacingOccurrences(of: "'", with: "")
  1784. }
  1785. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == idMessage })
  1786. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == idMessage }) }) {
  1787. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == idMessage }) {
  1788. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1789. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1790. }
  1791. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1792. }
  1793. if (idx != nil) {
  1794. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1795. self.updateStatusDelete(idx: idx, chatData: chatData)
  1796. } else {
  1797. self.updateStatusMessage(idx: idx, chatData: chatData)
  1798. }
  1799. }
  1800. }
  1801. else {
  1802. let listMessageId = chatData[CoreMessage_TMessageKey.MESSAGE_ID]!.split(separator: ",")
  1803. for i in 1..<listMessageId.count {
  1804. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == listMessageId[i] })
  1805. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == listMessageId[i] }) }) {
  1806. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == listMessageId[i] }) {
  1807. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1808. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1809. }
  1810. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1811. }
  1812. if (idx != nil) {
  1813. self.updateStatusMessage(idx: idx, chatData: chatData)
  1814. }
  1815. }
  1816. }
  1817. }
  1818. }
  1819. }
  1820. }
  1821. @objc func onFailedSendMessage(notification: NSNotification) {
  1822. DispatchQueue.main.async {
  1823. let data:[AnyHashable : Any] = notification.userInfo!
  1824. let messageId = data["message_id"] as? String ?? ""
  1825. let status = data["status"] as? String ?? ""
  1826. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  1827. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  1828. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  1829. self.groupImages[idxMessageIdParent].value[idxInImages].status = status
  1830. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = status
  1831. }
  1832. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1833. }
  1834. if (idx != nil) {
  1835. do {
  1836. self.dataMessages[idx!]["status"] = status
  1837. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1838. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[idx!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  1839. if row != nil && section != nil {
  1840. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1841. }
  1842. } catch {
  1843. }
  1844. }
  1845. }
  1846. }
  1847. private func updateStatusDelete(idx: Int?, chatData: [String: String]) {
  1848. do {
  1849. if self.dataMessages[idx!]["lock"] != nil && self.dataMessages[idx!]["lock"] as? String ?? "" == "1" {
  1850. return
  1851. }
  1852. self.dataMessages[idx!]["lock"] = "1"
  1853. self.dataMessages[idx!]["reff_id"] = ""
  1854. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1855. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[idx!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  1856. if row != nil && section != nil {
  1857. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1858. }
  1859. if self.listTimerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""] != nil {
  1860. self.listTimerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1861. self.timerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""]?.invalidate()
  1862. self.timerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1863. SecureUserDefaults.shared.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1864. }
  1865. if self.reffId != nil && self.reffId == chatData["message_id"]! {
  1866. self.deleteReplyView()
  1867. }
  1868. } catch {
  1869. }
  1870. }
  1871. private func updateStatusMessage(idx: Int?, chatData: [String: String]) {
  1872. do {
  1873. if Int(self.dataMessages[idx!]["status"] as? String ?? "")! > Int(chatData[CoreMessage_TMessageKey.STATUS]!)! {
  1874. return
  1875. }
  1876. self.dataMessages[idx!]["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1877. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1878. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[idx!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  1879. if row != nil && section != nil {
  1880. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1881. }
  1882. } catch {
  1883. }
  1884. }
  1885. @objc func onTyping(notification: NSNotification) {
  1886. DispatchQueue.main.async { [self] in
  1887. let data:[AnyHashable : Any] = notification.userInfo!
  1888. let message: TMessage = data["message"] as! TMessage
  1889. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1890. if !onGoingCC.isEmpty {
  1891. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1892. if message.getBody(key: CoreMessage_TMessageKey.F_PIN) != officer {
  1893. //print("RESET TIMER")
  1894. // timeoutCC.invalidate()
  1895. // timeoutCC = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: {_ in
  1896. // let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1897. // imageView.tintColor = .white
  1898. // let banner = FloatingNotificationBanner(title: "Customer doesn't respond in 30 second, so call center session will be ended automatically.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  1899. // banner.show()
  1900. // self.endCallCenter()
  1901. // })
  1902. }
  1903. } else {
  1904. }
  1905. }
  1906. }
  1907. @objc func onUnfriend(notification: NSNotification) {
  1908. let data:[AnyHashable : Any] = notification.userInfo!
  1909. DispatchQueue.main.async { [self] in
  1910. if data["state"] as! Int == 99 && (data["message"] as? String ?? "").components(separatedBy: ",")[0] == "delete_buddy" {
  1911. removed = true
  1912. if forwardSession || copySession || deleteSession || isSearching {
  1913. cancelAction()
  1914. }
  1915. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {[self] in
  1916. navigationItem.rightBarButtonItem = nil
  1917. navigationItem.rightBarButtonItems = nil
  1918. changeAppBar()
  1919. view.addSubview(containerAction)
  1920. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1921. NSLayoutConstraint.activate([
  1922. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1923. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1924. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1925. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1926. ])
  1927. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1928. let labelUnfriend = UILabel()
  1929. containerAction.addSubview(labelUnfriend)
  1930. labelUnfriend.translatesAutoresizingMaskIntoConstraints = false
  1931. NSLayoutConstraint.activate([
  1932. labelUnfriend.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1933. labelUnfriend.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1934. ])
  1935. labelUnfriend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1936. labelUnfriend.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1937. labelUnfriend.text = "You have unfriended this user".localized()
  1938. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1939. if contactChatNav.viewIfLoaded?.window != nil {
  1940. contactChatNav.dismiss(animated: true)
  1941. }
  1942. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  1943. if self.fromNotification {
  1944. self.didTapExit()
  1945. } else {
  1946. self.navigationController?.popViewController(animated: true)
  1947. }
  1948. })
  1949. })
  1950. } else if data["state"] as! Int == 01 {
  1951. if let dataMessage = try! JSONSerialization.jsonObject(with: (data["message"] as? String ?? "").data(using: .utf8)!, options: []) as? [String: String] {
  1952. if(dataMessage["l_pin"] == dataPerson["f_pin"]!){
  1953. if let block = dataMessage["block"] {
  1954. if(block == "-1"){
  1955. dismissKeyboard()
  1956. }
  1957. blockedView(blocked: block)
  1958. if contactChatNav.viewIfLoaded?.window != nil {
  1959. contactChatNav.dismiss(animated: true)
  1960. }
  1961. cancelAction()
  1962. }
  1963. }
  1964. setRightButtonItem()
  1965. }
  1966. }
  1967. }
  1968. }
  1969. func blockedView(blocked: String) {
  1970. dismissKeyboard()
  1971. view.addSubview(containerAction)
  1972. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1973. NSLayoutConstraint.activate([
  1974. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1975. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1976. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1977. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1978. ])
  1979. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1980. let labelBlocked = UILabel()
  1981. containerAction.addSubview(labelBlocked)
  1982. labelBlocked.translatesAutoresizingMaskIntoConstraints = false
  1983. NSLayoutConstraint.activate([
  1984. labelBlocked.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1985. labelBlocked.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1986. ])
  1987. labelBlocked.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1988. labelBlocked.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1989. if blocked == "1" {
  1990. labelBlocked.text = "You blocked this user".localized()
  1991. } else {
  1992. labelBlocked.text = "You have been blocked by this user".localized()
  1993. }
  1994. }
  1995. @objc func seeProfileTapped() {
  1996. if dataPerson["f_pin"] == "-999" || dataPerson["isOfficial"] == "1" || removed || copySession || forwardSession || deleteSession || isContactCenter {
  1997. return
  1998. }
  1999. dismissKeyboard()
  2000. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
  2001. controller.data = dataPerson["f_pin"]!!
  2002. controller.checkReadMessage = {
  2003. self.dataPerson.removeAll()
  2004. self.getDataProfile(fPin: self.unique_l_pin)
  2005. self.changeAppBar()
  2006. if self.currentIndexpath == nil {
  2007. var listData = self.dataMessages
  2008. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2009. if listData.count != 0 && !self.isContactCenter {
  2010. let idMe = User.getMyPin() as String?
  2011. for i in 0...listData.count - 1 {
  2012. if listData[i]["f_pin"] as? String != idMe {
  2013. self.sendReadMessageStatus(chat_id: "", f_pin: self.dataPerson["f_pin"]!!, message_scope_id: "3", message_id: listData[i]["message_id"] as? String ?? "")
  2014. }
  2015. }
  2016. }
  2017. } else {
  2018. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.currentIndexpath!.section] })
  2019. var listData = dataMessages
  2020. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2021. if listData.count != 0 && !self.isContactCenter {
  2022. let idMe = User.getMyPin() as String?
  2023. for i in 0...listData.count - 1 {
  2024. if listData[i]["f_pin"] as? String != idMe {
  2025. self.sendReadMessageStatus(chat_id: "", f_pin: self.dataPerson["f_pin"]!!, message_scope_id: "3", message_id: listData[i]["message_id"] as? String ?? "")
  2026. }
  2027. }
  2028. }
  2029. }
  2030. }
  2031. navigationController?.show(controller, sender: nil)
  2032. }
  2033. @IBAction func voiceTapped(_ sender: UIButton) {
  2034. if (self.constraintBottomAttachment.constant != 0.0) {
  2035. constraintBottomAttachment.constant = 0.0
  2036. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2037. self.viewSticker.removeFromSuperview()
  2038. }
  2039. }
  2040. @IBAction func imageTapped(_ sender: UIButton) {
  2041. if (self.constraintBottomAttachment.constant != 0.0) {
  2042. constraintBottomAttachment.constant = 0.0
  2043. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2044. self.viewSticker.removeFromSuperview()
  2045. }
  2046. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2047. return
  2048. }
  2049. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  2050. if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
  2051. alertController.addAction(action)
  2052. }
  2053. if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
  2054. alertController.addAction(action)
  2055. }
  2056. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  2057. self.present(alertController, animated: true)
  2058. }
  2059. private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
  2060. return UIAlertAction(title: title, style: .default) { [unowned self] _ in
  2061. switch type {
  2062. case "image":
  2063. var config = PHPickerConfiguration()
  2064. config.filter = .images
  2065. let picker = PHPickerViewController(configuration: config)
  2066. picker.delegate = self
  2067. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  2068. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  2069. }
  2070. if !isBlackCancelButton {
  2071. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2072. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2073. }
  2074. present(picker, animated: true, completion: nil)
  2075. case "video":
  2076. var config = PHPickerConfiguration()
  2077. config.filter = .videos
  2078. let picker = PHPickerViewController(configuration: config)
  2079. picker.delegate = self
  2080. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  2081. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  2082. }
  2083. if !isBlackCancelButton {
  2084. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2085. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2086. }
  2087. present(picker, animated: true, completion: nil)
  2088. case "imageCamera":
  2089. if isContactCenter && channelContactCenter == "2" {
  2090. self.view.makeToast("You can't take photo when Video Call".localized(), duration: 3)
  2091. return
  2092. }
  2093. imageVideoPicker.present(source: .imageCamera)
  2094. case "videoCamera":
  2095. if isContactCenter && channelContactCenter == "2" {
  2096. self.view.makeToast("You can't take video when Video Call".localized(), duration: 3)
  2097. return
  2098. }
  2099. imageVideoPicker.present(source: .videoCamera)
  2100. default:
  2101. break
  2102. }
  2103. }
  2104. }
  2105. @IBAction func photoTapped(_ sender: UIButton) {
  2106. if (self.constraintBottomAttachment.constant != 0.0) {
  2107. constraintBottomAttachment.constant = 0.0
  2108. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2109. self.viewSticker.removeFromSuperview()
  2110. }
  2111. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2112. return
  2113. }
  2114. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  2115. if let action = self.actionImageVideo(for: "imageCamera", title: "Take Photo".localized()) {
  2116. alertController.addAction(action)
  2117. }
  2118. if let action = self.actionImageVideo(for: "videoCamera", title: "Take Video".localized()) {
  2119. alertController.addAction(action)
  2120. }
  2121. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  2122. self.present(alertController, animated: true)
  2123. }
  2124. @IBAction func stickerTapped(_ sender: UIButton) {
  2125. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2126. return
  2127. }
  2128. DispatchQueue.main.async {
  2129. if (self.constraintBottomAttachment.constant == 0.0) {
  2130. self.constraintBottomAttachment.constant = 200.0
  2131. self.view.addSubview(self.viewSticker)
  2132. self.viewSticker.translatesAutoresizingMaskIntoConstraints = false
  2133. NSLayoutConstraint.activate([
  2134. self.viewSticker.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  2135. self.viewSticker.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  2136. self.viewSticker.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  2137. self.viewSticker.heightAnchor.constraint(equalToConstant: 200)
  2138. ])
  2139. let layout = UICollectionViewFlowLayout()
  2140. layout.scrollDirection = .vertical
  2141. let collectionSticker = UICollectionView(frame: .zero, collectionViewLayout: layout)
  2142. collectionSticker.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellSticker")
  2143. collectionSticker.delegate = self
  2144. collectionSticker.dataSource = self
  2145. collectionSticker.backgroundColor = .clear
  2146. self.viewSticker.addSubview(collectionSticker)
  2147. collectionSticker.translatesAutoresizingMaskIntoConstraints = false
  2148. NSLayoutConstraint.activate([
  2149. collectionSticker.topAnchor.constraint(equalTo: self.viewSticker.topAnchor, constant: 20),
  2150. collectionSticker.bottomAnchor.constraint(equalTo: self.viewSticker.bottomAnchor, constant: -20),
  2151. collectionSticker.leadingAnchor.constraint(equalTo: self.viewSticker.leadingAnchor, constant: 20),
  2152. collectionSticker.trailingAnchor.constraint(equalTo: self.viewSticker.trailingAnchor, constant: -20)
  2153. ])
  2154. if (self.currentIndexpath != nil) {
  2155. DispatchQueue.main.async {
  2156. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  2157. }
  2158. } else {
  2159. self.tableChatView.scrollToBottom()
  2160. }
  2161. } else {
  2162. self.constraintBottomAttachment.constant = 0.0
  2163. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2164. self.viewSticker.removeFromSuperview()
  2165. }
  2166. }
  2167. }
  2168. @IBAction func fileTapped(_ sender: UIButton) {
  2169. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2170. return
  2171. }
  2172. if (self.constraintBottomAttachment.constant != 0.0) {
  2173. constraintBottomAttachment.constant = 0.0
  2174. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2175. self.viewSticker.removeFromSuperview()
  2176. }
  2177. documentPicker.present()
  2178. }
  2179. @objc func sendTapped() {
  2180. sendChat(message_text: textFieldSend.text!, viewController: self)
  2181. }
  2182. @objc func showChooserACKConfidential() {
  2183. let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
  2184. let imageConfidential = resizeImage(image: UIImage(named: "pb_icon_conf_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2185. let imageAck = resizeImage(image: UIImage(named: "pb_icon_ack_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2186. let imageSecret = resizeImage(image: UIImage(named: "pb_icon_secret_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2187. let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
  2188. self.isConfidential = !self.isConfidential
  2189. if self.isConfidential {
  2190. self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
  2191. } else {
  2192. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2193. }
  2194. if self.isAck {
  2195. self.isAck = false
  2196. }
  2197. if self.isSecret {
  2198. self.isSecret = false
  2199. }
  2200. })
  2201. let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
  2202. self.isAck = !self.isAck
  2203. if self.isAck {
  2204. self.buttonAckConfidential.setImage(imageAck, for: .normal)
  2205. } else {
  2206. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2207. }
  2208. if self.isConfidential {
  2209. self.isConfidential = false
  2210. }
  2211. if self.isSecret {
  2212. self.isSecret = false
  2213. }
  2214. })
  2215. let secretAction = UIAlertAction(title: "Secret Message".localized(), style: .default, handler: { (UIAlertAction) in
  2216. self.isSecret = !self.isSecret
  2217. if self.isSecret {
  2218. self.buttonAckConfidential.setImage(imageSecret, for: .normal)
  2219. } else {
  2220. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2221. }
  2222. if self.isConfidential {
  2223. self.isConfidential = false
  2224. }
  2225. if self.isAck {
  2226. self.isAck = false
  2227. }
  2228. })
  2229. confidentialAction.setValue(imageConfidential, forKey: "image")
  2230. ackAction.setValue(imageAck, forKey: "image")
  2231. secretAction.setValue(imageSecret, forKey: "image")
  2232. alertController.addAction(confidentialAction)
  2233. alertController.addAction(ackAction)
  2234. alertController.addAction(secretAction)
  2235. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
  2236. self.isConfidential = false
  2237. self.isAck = false
  2238. self.isSecret = false
  2239. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2240. }))
  2241. self.present(alertController, animated: true, completion: nil)
  2242. }
  2243. public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
  2244. self.isConfidential = isConfidential
  2245. self.isAck = isAck
  2246. let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2247. let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2248. if isAck {
  2249. buttonAckConfidential.setImage(imageAck, for: .normal)
  2250. } else if isConfidential {
  2251. buttonAckConfidential.setImage(imageConfidential, for: .normal)
  2252. } else {
  2253. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2254. }
  2255. }
  2256. @objc func addRoom(sender: UIBarButtonItem) {
  2257. let controller = QmeraCallContactViewController()
  2258. controller.isDismiss = { user in
  2259. DispatchQueue.global().async {
  2260. _ = Nexilis.write(message: CoreMessage_TMessageBank.getCCRoomInvite(l_pin: user.pin, ticket_id: self.complaintId, channel: self.channelContactCenter))
  2261. }
  2262. }
  2263. controller.selectedUser.append(contentsOf: users)
  2264. controller.isInviteCC = true
  2265. self.navigationController?.show(controller, sender: nil)
  2266. }
  2267. @objc func audioVideoCall(sender: UIBarButtonItem) {
  2268. if sender.tag == 0 {
  2269. if !Nexilis.checkingAccess(key: "audio_call") {
  2270. self.view.makeToast("Feature disabled..".localized(), duration: 3)
  2271. return
  2272. }
  2273. let goAudioCall = Nexilis.checkMicPermission()
  2274. if !goAudioCall{
  2275. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2276. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2277. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2278. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2279. }
  2280. }))
  2281. self.navigationController?.present(alert, animated: true, completion: nil)
  2282. return
  2283. }
  2284. if let pin = dataPerson["f_pin"] {
  2285. if !CheckConnection.isConnectedToNetwork() {
  2286. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2287. imageView.tintColor = .white
  2288. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  2289. banner.show()
  2290. return
  2291. }
  2292. let controller = QmeraAudioViewController()
  2293. controller.user = User.getData(pin: pin)
  2294. controller.isOutgoing = true
  2295. controller.modalPresentationStyle = .overCurrentContext
  2296. present(controller, animated: true, completion: nil)
  2297. }
  2298. } else {
  2299. if !Nexilis.checkingAccess(key: "video_call") {
  2300. self.view.makeToast("Feature disabled..".localized(), duration: 3)
  2301. return
  2302. }
  2303. let goAudioCall = Nexilis.checkMicPermission()
  2304. let goVideoCall = Nexilis.checkCameraPermission()
  2305. if goVideoCall == 0 {
  2306. let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
  2307. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2308. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2309. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2310. }
  2311. }))
  2312. self.navigationController?.present(alert, animated: true, completion: nil)
  2313. return
  2314. } else if goVideoCall == -1 {
  2315. return
  2316. }
  2317. if !CheckConnection.isConnectedToNetwork() {
  2318. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2319. imageView.tintColor = .white
  2320. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  2321. banner.show()
  2322. return
  2323. }
  2324. let videoVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
  2325. videoVC.dataPerson.append(dataPerson)
  2326. self.show(videoVC, sender: nil)
  2327. }
  2328. }
  2329. @objc func dismissKeyboard() {
  2330. if isSearching {
  2331. searchBar.resignFirstResponder()
  2332. } else {
  2333. textFieldSend.resignFirstResponder() // dismiss keyoard
  2334. if (self.constraintBottomAttachment.constant != 0.0) {
  2335. constraintBottomAttachment.constant = 0.0
  2336. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2337. self.viewSticker.removeFromSuperview()
  2338. }
  2339. }
  2340. }
  2341. @objc func didTapExit() {
  2342. if complaintId.isEmpty || fromVCAC {
  2343. self.dismiss(animated: true, completion: {
  2344. self.removeAllObjectBeforeDismissVC()
  2345. })
  2346. } else if !complaintId.isEmpty {
  2347. let alert = LibAlertController(title: "Interaction with Call Center is in progress".localized(), message: "Are you sure you want to end the Call Center?".localized(), preferredStyle: .alert)
  2348. alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
  2349. alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  2350. self.endCallCenter()
  2351. }))
  2352. self.present(alert, animated: true, completion: nil)
  2353. }
  2354. }
  2355. private func removeAllObjectBeforeDismissVC() {
  2356. for timer in self.timerCredential.values {
  2357. timer.invalidate()
  2358. }
  2359. self.timeoutCC.invalidate()
  2360. SecureUserDefaults.shared.removeValue(forKey: "inEditorPersonal")
  2361. NotificationCenter.default.removeObserver(self)
  2362. self.removeFromParent()
  2363. if !self.isContactCenter {
  2364. let l_pin = self.dataPerson["f_pin"]!!
  2365. let data: [String: String] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
  2366. if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
  2367. let jsonString = String(data: jsonData, encoding: .utf8) {
  2368. SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
  2369. }
  2370. }
  2371. }
  2372. public func endCallCenter() {
  2373. timeoutCC.invalidate()
  2374. let complaintId = self.complaintId
  2375. let idMe = User.getMyPin()!
  2376. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  2377. let requester = onGoingCC.components(separatedBy: ",")[0]
  2378. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  2379. DispatchQueue.global().async {
  2380. let date = "\(Date().currentTimeMillis())"
  2381. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2382. do {
  2383. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  2384. "type" : self.channelContactCenter,
  2385. "title" : "Contact Center".localized(),
  2386. "time" : self.dateStartCC,
  2387. "f_pin" : officer,
  2388. "data" : self.complaintId,
  2389. "time_end" : date,
  2390. "complaint_id" : self.complaintId,
  2391. "members" : "",
  2392. "requester": requester
  2393. ], replace: true)
  2394. } catch {
  2395. rollback.pointee = true
  2396. print("Access database error: \(error.localizedDescription)")
  2397. }
  2398. })
  2399. if officer == idMe {
  2400. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: requester))
  2401. } else {
  2402. if requester == idMe {
  2403. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: officer))
  2404. } else {
  2405. _ = Nexilis.write(message: CoreMessage_TMessageBank.leaveCCRoomInvite(ticket_id: complaintId))
  2406. }
  2407. }
  2408. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  2409. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  2410. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  2411. }
  2412. self.dismiss(animated: true, completion: {
  2413. self.removeAllObjectBeforeDismissVC()
  2414. })
  2415. }
  2416. @objc func keyboardWillHide(notification: NSNotification) {
  2417. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  2418. let info:NSDictionary = notification.userInfo! as NSDictionary
  2419. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2420. self.constraintViewTextField.constant = 0
  2421. self.constraintBottomContainerMultpileSelectSession.constant = 0
  2422. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2423. self.view.layoutIfNeeded()
  2424. })
  2425. }
  2426. }
  2427. @objc func keyboardWillShow(notification: NSNotification) {
  2428. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  2429. if (self.constraintBottomAttachment.constant != 0.0) {
  2430. self.constraintBottomAttachment.constant = 0.0
  2431. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2432. self.viewSticker.removeFromSuperview()
  2433. }
  2434. let info:NSDictionary = notification.userInfo! as NSDictionary
  2435. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  2436. let keyboardHeight: CGFloat = keyboardSize.height
  2437. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2438. if self.constraintViewTextField.constant != keyboardHeight - 60 {
  2439. self.constraintViewTextField.constant = keyboardHeight - 60
  2440. if isSearching {
  2441. self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
  2442. self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
  2443. }
  2444. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2445. self.view.layoutIfNeeded()
  2446. })
  2447. if isSearching {
  2448. self.tableChatView.scrollToBottom()
  2449. } else {
  2450. if (self.currentIndexpath != nil) {
  2451. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  2452. } else {
  2453. self.tableChatView.scrollToBottom()
  2454. }
  2455. }
  2456. }
  2457. } else if isEditingMessage {
  2458. let info:NSDictionary = notification.userInfo! as NSDictionary
  2459. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  2460. let keyboardHeight: CGFloat = keyboardSize.height
  2461. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2462. let constant: CGFloat = 0 - keyboardHeight - 15
  2463. constraintBottomeditTextView.constant = constant
  2464. constraintBottomSendEditTV.constant = constant
  2465. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2466. self.view.layoutIfNeeded()
  2467. })
  2468. }
  2469. }
  2470. private func sendChat(message_scope_id:String = "3", status:String = "1", message_text:String = "", credential:String = "0", attachment_flag: String = "0", ex_blog_id: String = "", message_large_text: String = "", ex_format: String = "", image_id: String = "", audio_id: String = "", video_id: String = "", file_id: String = "", thumb_id: String = "", reff_id: String = "", read_receipts: String = "4", chat_id: String = "", is_call_center: String = "0", call_center_id: String = "", viewController: UIViewController, isAutoSendCC : Bool = false, gif_id: String = "", is_forwarded: Int = 0, is_secret: Int = 0) {
  2471. if viewController is EditorPersonal && file_id == "" && dataMessageForward == nil && !isAutoSendCC{
  2472. if ((textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray && attachment_flag != "11") || textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ) {
  2473. dismissKeyboard()
  2474. viewController.view.makeToast("Write Messages".localized(), duration: 3)
  2475. if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized()) {
  2476. textFieldSend.text = ""
  2477. }
  2478. if (self.heightTextFieldSend.constant != 40) {
  2479. self.heightTextFieldSend.constant = 40
  2480. }
  2481. return
  2482. }
  2483. }
  2484. var reff_id = reff_id
  2485. if (reffId != nil) {
  2486. reff_id = reffId!
  2487. }
  2488. var is_call_center = is_call_center
  2489. var call_center_id = call_center_id
  2490. var l_pin = dataPerson["f_pin"]!!
  2491. var message_scope_id = message_scope_id
  2492. var chat_id = chat_id
  2493. if (isContactCenter) {
  2494. if fPinContacCenter.isEmpty && isRequestContactCenter {
  2495. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintViewTextField.constant == 0 {
  2496. textFieldSend.text = "Send message".localized()
  2497. textFieldSend.textColor = UIColor.lightGray
  2498. } else if constraintViewTextField.constant != 0 {
  2499. textFieldSend.text = ""
  2500. }
  2501. dismissKeyboard()
  2502. self.view.makeToast("Unable to send message. Waiting for the officer to accept your request".localized(), duration: 3)
  2503. return
  2504. }
  2505. is_call_center = "1"
  2506. call_center_id = complaintId
  2507. l_pin = fPinContacCenter
  2508. message_scope_id = "5"
  2509. chat_id = complaintId
  2510. if isAutoSendCC {
  2511. timeoutCC = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: {_ in
  2512. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  2513. imageView.tintColor = .white
  2514. let banner = FloatingNotificationBanner(title: "Customer doesn't respond in 30 second, so call center session will be ended automatically.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  2515. banner.show()
  2516. self.endCallCenter()
  2517. })
  2518. }
  2519. }
  2520. var message_text = message_text
  2521. let bulletPoint = " • "
  2522. let numberPattern = #" \d+\.\ "#
  2523. let firstLine = message_text.components(separatedBy: .newlines).first ?? ""
  2524. // Check if text contains bullet points or numbered list using regex
  2525. if !message_text.isEmpty && !firstLine.contains(bulletPoint) && firstLine.range(of: numberPattern, options: .regularExpression) == nil {
  2526. message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
  2527. }
  2528. let idMe = User.getMyPin() as String?
  2529. var opposite_pin = ""
  2530. if isContactCenter {
  2531. opposite_pin = ""
  2532. } else {
  2533. opposite_pin = idMe ?? ""
  2534. }
  2535. var credential = credential
  2536. if isConfidential {
  2537. credential = "1"
  2538. }
  2539. var read_receipts = read_receipts
  2540. if isAck {
  2541. read_receipts = "8"
  2542. }
  2543. var is_secret = is_secret
  2544. if isSecret {
  2545. is_secret = 1
  2546. }
  2547. sendTyping(l_pin: l_pin, isTyping: true)
  2548. let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)", isSecret: "\(is_secret)")
  2549. Nexilis.addQueueMessage(message: message)
  2550. let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
  2551. if credential == "1" {
  2552. self.listTimerCredential[messageId] = 60
  2553. }
  2554. var row: [String: Any?] = [:]
  2555. row["message_id"] = messageId
  2556. row["f_pin"] = idMe
  2557. row["l_pin"] = dataPerson["f_pin"]!!
  2558. row["message_scope_id"] = message_scope_id
  2559. row["server_date"] = "\(Date().currentTimeMillis())"
  2560. row["status"] = status
  2561. row["message_text"] = message_text
  2562. row["audio_id"] = audio_id
  2563. row["video_id"] = video_id
  2564. row["image_id"] = image_id
  2565. row["thumb_id"] = thumb_id
  2566. row["read_receipts"] = read_receipts
  2567. row["credential"] = credential
  2568. row["chat_id"] = chat_id
  2569. row["file_id"] = file_id
  2570. row["blog_id"] = ex_blog_id
  2571. row["attachment_flag"] = attachment_flag
  2572. row["reff_id"] = reff_id
  2573. row["progress"] = 0.0
  2574. row["lock"] = "0"
  2575. row["is_stared"] = "0"
  2576. row["gif_id"] = gif_id
  2577. row[TypeDataMessage.is_forwarded] = is_forwarded
  2578. row["isSelected"] = false
  2579. row[TypeDataMessage.is_call_center] = is_call_center
  2580. row[TypeDataMessage.call_center_id] = call_center_id
  2581. row[TypeDataMessage.opposite_pin] = opposite_pin
  2582. if !dataDates.contains("Today".localized()) {
  2583. dataDates.append("Today".localized())
  2584. tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .none)
  2585. }
  2586. row["chat_date"] = "Today".localized()
  2587. dataMessages.append(row)
  2588. tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
  2589. if credential == "1" {
  2590. var timer = Timer()
  2591. var minute = 60
  2592. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  2593. minute -= 1
  2594. self.listTimerCredential[messageId] = minute
  2595. if minute == 0 {
  2596. timer.invalidate()
  2597. self.listTimerCredential.removeValue(forKey: messageId)
  2598. self.timerCredential.removeValue(forKey: messageId)
  2599. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
  2600. if idx != nil {
  2601. self.dataMessages[idx!]["lock"] = "2"
  2602. self.dataMessages[idx!]["reff_id"] = ""
  2603. }
  2604. DispatchQueue.global().async {
  2605. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2606. do {
  2607. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  2608. "lock" : "2"
  2609. ], _where: "message_id = '\(messageId)'")
  2610. } catch {
  2611. rollback.pointee = true
  2612. print("Access database error: \(error.localizedDescription)")
  2613. }
  2614. })
  2615. }
  2616. }
  2617. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  2618. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).firstIndex(where: { $0["message_id"] as? String == messageId})
  2619. if row != nil && section != nil{
  2620. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2621. }
  2622. })
  2623. self.timerCredential[messageId] = timer
  2624. }
  2625. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintViewTextField.constant == 0 {
  2626. textFieldSend.text = "Send message".localized()
  2627. textFieldSend.textColor = UIColor.lightGray
  2628. } else if constraintViewTextField.constant != 0 {
  2629. textFieldSend.text = ""
  2630. heightTextFieldSend.constant = 40
  2631. }
  2632. deleteReplyView()
  2633. deleteLinkPreview()
  2634. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  2635. self.tableChatView.scrollToBottom()
  2636. if self.markerCounter != nil {
  2637. let lastMarkerCounter = self.markerCounter
  2638. self.markerCounter = nil
  2639. self.tableChatView.beginUpdates()
  2640. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  2641. if indexMessage != nil {
  2642. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  2643. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[indexMessage!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[indexMessage!]["message_id"] as? String })
  2644. if row != nil && section != nil {
  2645. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2646. }
  2647. }
  2648. self.tableChatView.endUpdates()
  2649. }
  2650. // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  2651. // self.timerFakeProgress = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
  2652. // self.updateProgress(row as [AnyHashable : Any])
  2653. // if self.fakeProgMultip == self.maxFakeProgMultip {
  2654. // self.timerFakeProgress?.invalidate()
  2655. // self.fakeProgMultip = 0
  2656. // }
  2657. // }
  2658. // }
  2659. }
  2660. @objc func ccAction(sender: UIButton) {
  2661. if self.nowSelectedCategoryCC == "CantReturn" {
  2662. if sender.tag == 503 {
  2663. self.view.makeToast("You can't request Call Center more than one".localized(), duration: 3)
  2664. } else if sender.tag == 504 {
  2665. busyCCAction(sender: sender)
  2666. }
  2667. return
  2668. }
  2669. if self.nowSelectedCategoryCC == "endCC" {
  2670. return
  2671. }
  2672. let id = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  2673. let service_id = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  2674. let level = id!.substring(from: 5, to: 5)
  2675. let levelNow = self.nowSelectedCategoryCC.substring(from: 5, to: 5)
  2676. var isRequest = false
  2677. var channel = 0
  2678. var row: [String: Any?] = [:]
  2679. if nowSelectedCategoryCC.isEmpty || level > levelNow {
  2680. if Utils.getDefaultCC() == "No" && !showToastTwiceClick {
  2681. self.view.makeToast("You can press your choice again to change category".localized(), duration: 3)
  2682. showToastTwiceClick = true
  2683. }
  2684. row["message_id"] = ""
  2685. row["chat_date"] = "Today".localized()
  2686. let dataChat: [CategoryCC] = CategoryCC.getDatafromParent(parent: service_id!)
  2687. if dataChat.count != 0 {
  2688. var data : [CategoryCC] = []
  2689. for i in 0..<dataChat.count {
  2690. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(i)", service_id: dataChat[i].service_id, service_name: dataChat[i].service_name, parent: id!, description: dataChat[i].description, is_tablet: dataChat[i].is_tablet))
  2691. }
  2692. row["category_cc"] = data
  2693. } else if dataMessages[Int(level)!]["attachment_flag"] == nil {
  2694. let listStringName: [String] = ["Informasi Umum Produk Call 1500046", "Informasi Spesifik Produk"]
  2695. var data : [CategoryCC] = []
  2696. for i in 0..<listStringName.count {
  2697. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(i)", service_id: service_id!, service_name: listStringName[i], parent: id!, description: "", is_tablet: "0"))
  2698. }
  2699. row["category_cc"] = data
  2700. row["attachment_flag"] = "502"
  2701. } else if dataMessages[Int(level)!]["attachment_flag"] != nil && dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" == "502" {
  2702. if id == "level\(Int(level)!)_0" {
  2703. if let url = URL(string: "tel://1500046") {
  2704. UIApplication.shared.open(url)
  2705. }
  2706. return
  2707. } else {
  2708. let listStringName: [String] = ["Messaging".localized(), "Secure SMS".localized(), "VoIP Call".localized(), "Email".localized(), "Video Call".localized(), "GSM Call".localized(), "GPT Chatbot".localized(), "WhatsApp"]
  2709. // let listStringName: [String] = ["Chat with a Representative".localized(), "Video Call a Representative".localized(), "Call a Representative".localized()]
  2710. var data : [CategoryCC] = []
  2711. let channels : [String] = ["0", "4", "1", "3", "2", "5", "7", "6"]
  2712. for i in 0..<listStringName.count {
  2713. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(channels[i])", service_id: service_id!, service_name: listStringName[i], parent: id!, description: "", is_tablet: "0"))
  2714. }
  2715. row["category_cc"] = data
  2716. row["attachment_flag"] = "503"
  2717. }
  2718. } else {
  2719. channel = Int((id?.components(separatedBy: "_")[1])!)!
  2720. if channel == 1 || channel == 2 {
  2721. if channel == 2 {
  2722. let goAudioCall = Nexilis.checkMicPermission()
  2723. let goVideoCall = Nexilis.checkCameraPermission()
  2724. if goVideoCall == 0 {
  2725. let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 && channel == 2 ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
  2726. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2727. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2728. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2729. }
  2730. }))
  2731. self.navigationController?.present(alert, animated: true, completion: nil)
  2732. return
  2733. } else if goVideoCall == -1 {
  2734. return
  2735. }
  2736. } else if channel == 1 {
  2737. let goAudioCall = Nexilis.checkMicPermission()
  2738. if !goAudioCall{
  2739. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2740. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2741. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2742. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2743. }
  2744. }))
  2745. self.navigationController?.present(alert, animated: true, completion: nil)
  2746. return
  2747. }
  2748. }
  2749. } else if channel == 3 {
  2750. requestEmailContactCenter(channel)
  2751. return
  2752. } else if channel == 4 {
  2753. requestSMSContactCenter(channel)
  2754. return
  2755. } else if channel == 5 {
  2756. requestGSMCallContactCenter(channel)
  2757. return
  2758. } else if channel == 6 {
  2759. requestWhatsappContactCenter(channel)
  2760. return
  2761. } else if channel == 7 {
  2762. APIS.openSmartChatbot()
  2763. return
  2764. }
  2765. row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
  2766. isRequest = true
  2767. }
  2768. if dataMessages[Int(level)!]["attachment_flag"] == nil || dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" != "503" {
  2769. dataMessages.append(row)
  2770. self.nowSelectedCategoryCC = id!
  2771. tableChatView.insertRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
  2772. }
  2773. } else {
  2774. if id == self.nowSelectedCategoryCC {
  2775. if level == "0" {
  2776. self.nowSelectedCategoryCC = ""
  2777. } else {
  2778. let categoryCC = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
  2779. self.nowSelectedCategoryCC = categoryCC[0].parent
  2780. }
  2781. tableChatView.beginUpdates()
  2782. tableChatView.deleteRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
  2783. dataMessages.remove(at: dataMessages.count - 1)
  2784. tableChatView.endUpdates()
  2785. } else {
  2786. return
  2787. }
  2788. }
  2789. if Utils.getDefaultCC() == "No" {
  2790. if sender.backgroundColor != .orangeBNI {
  2791. var button = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
  2792. if dataMessages[Int(level)!]["attachment_flag"] != nil && dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" == "503" {
  2793. button = dataMessages[dataMessages.count - 1]["category_cc"] as! [CategoryCC]
  2794. }
  2795. for i in button {
  2796. if i.id == id! {
  2797. i.isActive = true
  2798. break
  2799. }
  2800. }
  2801. sender.backgroundColor = .orangeBNI
  2802. dataMessages[dataMessages.count - 2]["category_cc"] = button
  2803. } else {
  2804. let button = dataMessages[dataMessages.count - 1]["category_cc"] as! [CategoryCC]
  2805. for i in button {
  2806. if i.id == id! {
  2807. i.isActive = false
  2808. break
  2809. }
  2810. }
  2811. sender.backgroundColor = .clear
  2812. dataMessages[dataMessages.count - 1]["category_cc"] = button
  2813. }
  2814. }
  2815. if isRequest {
  2816. requestContactCenter(channel: channel, service_id: service_id!, row: row)
  2817. } else {
  2818. self.tableChatView.scrollToBottom()
  2819. }
  2820. }
  2821. private func directCC() {
  2822. if channelContactCenter == "1" || channelContactCenter == "2" {
  2823. if channelContactCenter == "2" {
  2824. let goAudioCall = Nexilis.checkMicPermission()
  2825. let goVideoCall = Nexilis.checkCameraPermission()
  2826. if goVideoCall == 0 {
  2827. let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 && channelContactCenter == "2" ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
  2828. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2829. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2830. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2831. }
  2832. }))
  2833. self.navigationController?.present(alert, animated: true, completion: nil)
  2834. return
  2835. } else if goVideoCall == -1 {
  2836. return
  2837. }
  2838. } else if channelContactCenter == "1" {
  2839. let goAudioCall = Nexilis.checkMicPermission()
  2840. if !goAudioCall{
  2841. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2842. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2843. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2844. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2845. }
  2846. }))
  2847. self.navigationController?.present(alert, animated: true, completion: nil)
  2848. return
  2849. }
  2850. }
  2851. }
  2852. var row: [String: Any?] = [:]
  2853. row["message_id"] = ""
  2854. row["chat_date"] = "Today".localized()
  2855. row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
  2856. dataMessages.append(row)
  2857. nowSelectedCategoryCC = "CantReturn"
  2858. tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .none)
  2859. requestContactCenter(channel: Int(channelContactCenter)!, service_id: serviceIdCC, row: row)
  2860. }
  2861. @objc func busyCCAction(sender: UIButton) {
  2862. let id = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  2863. let service_id = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  2864. let level = id!.substring(from: 5, to: 5)
  2865. var row: [String: Any?] = [:]
  2866. if id == "level\(Int(level)!)_0" {
  2867. SecureUserDefaults.shared.set(true, forKey: "waitingRequestCC")
  2868. DispatchQueue.global().async {
  2869. let message = CoreMessage_TMessageBank.getQueuingCallCenter(p_channel: Int(self.channelContactCenter)!)
  2870. message.mBodies[CoreMessage_TMessageKey.CATEGORY_ID] = "\(service_id!)"
  2871. _ = Nexilis.writeSync(message: message, timeout: 30 * 1000)
  2872. }
  2873. row["category_cc"] = "Thank you for contacting us,\none of our officers will contact you soon".localized()
  2874. } else {
  2875. row["category_cc"] = "Thank you for being awesome,\nhave a great day!".localized()
  2876. }
  2877. row["message_id"] = ""
  2878. row["chat_date"] = "Today".localized()
  2879. self.nowSelectedCategoryCC = "endCC"
  2880. dataMessages.append(row)
  2881. tableChatView.insertRows(at: [IndexPath(row: Int(level)!, section: 0)], with: .none)
  2882. self.tableChatView.scrollToBottom()
  2883. // DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
  2884. // self.dismiss(animated: true)
  2885. // })
  2886. }
  2887. func requestEmailContactCenter(_ channel: Int){
  2888. let idMe = User.getMyPin() as String?
  2889. let complaintId = "CMP_\(idMe!)_\(String(Date().currentTimeMillis()))EML"
  2890. let message = CoreMessage_TMessageBank.getRequestEmailCallCenter(p_channel: channel)
  2891. if let response = Nexilis.writeSync(message: message) {
  2892. if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") {
  2893. DispatchQueue.main.async {
  2894. let email = response.getBody(key: CoreMessage_TMessageKey.EMAIL, default_value: "")
  2895. let officer = response.getBody(key: CoreMessage_TMessageKey.L_PIN, default_value: "")
  2896. if email.isEmpty {
  2897. self.view.makeToast("Invalid Email Address".localized(), duration: 3)
  2898. return
  2899. }
  2900. // TODO: check if mail available
  2901. Nexilis.openmailAction(to: email)
  2902. }
  2903. }
  2904. }
  2905. }
  2906. func requestSMSContactCenter(_ channel: Int){
  2907. // let idMe = User.getMyPin()!
  2908. // let complaintId = "CMP_\(idMe)_\(String(Date().currentTimeMillis()))SMS"
  2909. // isRequestContactCenter = true
  2910. var phone = Utils.getSMSCenter()
  2911. if phone.substring(from: 0, to: 0) == "0" {
  2912. phone = "+62" + phone.substring(from: 1, to: phone.count)
  2913. }
  2914. APIS.sendSMS(phoneNumber: phone)
  2915. // let tmessage = TMessage()
  2916. // tmessage.mCode = CoreMessage_TMessageCode.ACCEPT_CALL_CENTER
  2917. // tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
  2918. // tmessage.mPIN = idMe
  2919. // tmessage.mBodies[CoreMessage_TMessageKey.F_PIN] = me
  2920. // tmessage.mBodies[CoreMessage_TMessageKey.UPLINE_PIN] = me
  2921. // tmessage.mBodies[CoreMessage_TMessageKey.CHANNEL] = channel
  2922. // tmessage.mBodies[CoreMessage_TMessageKey.CALL_CENTER_ID] = complaint_id
  2923. }
  2924. func requestGSMCallContactCenter(_ channel: Int){
  2925. var phone = Utils.getCallCenter()
  2926. if phone.substring(from: 0, to: 0) == "0" {
  2927. phone = "+62" + phone.substring(from: 1, to: phone.count)
  2928. }
  2929. if let url = URL(string: "tel://\(phone)") {
  2930. UIApplication.shared.open(url)
  2931. }
  2932. }
  2933. func requestWhatsappContactCenter(_ channel: Int){
  2934. var phone = Utils.getWhatsappCenter()
  2935. if phone.substring(from: 0, to: 0) == "0" {
  2936. phone = "+62" + phone.substring(from: 1, to: phone.count)
  2937. }
  2938. APIS.sendWhatsapp(phoneNumber: phone)
  2939. }
  2940. func requestContactCenter(channel: Int, service_id: String, row: [String: Any?]) {
  2941. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  2942. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2943. imageView.tintColor = .white
  2944. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  2945. banner.show()
  2946. return
  2947. }
  2948. DispatchQueue.global().async {
  2949. let message = CoreMessage_TMessageBank.getRequestCallCenter(p_channel: channel, category_id: service_id)
  2950. if let response = Nexilis.writeSync(message: message) {
  2951. if !self.isDirectCC {
  2952. DispatchQueue.main.async {
  2953. self.dataMessages.append(row)
  2954. self.nowSelectedCategoryCC = "CantReturn"
  2955. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  2956. self.tableChatView.scrollToBottom()
  2957. }
  2958. }
  2959. if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") {
  2960. DispatchQueue.main.async {
  2961. SecureUserDefaults.shared.set(true, forKey: "waitingRequestCC")
  2962. let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "")
  2963. if data.isEmpty {
  2964. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  2965. var row: [String: Any?] = [:]
  2966. row["message_id"] = ""
  2967. row["chat_date"] = "Today".localized()
  2968. row["attachment_flag"] = "504"
  2969. let listStringName: [String] = ["Yes".localized(), "No".localized()]
  2970. var data : [CategoryCC] = []
  2971. for i in 0..<listStringName.count {
  2972. data.append(CategoryCC(id: "level\(self.dataMessages.count + 1)_\(i)", service_id: service_id, service_name: listStringName[i], parent: "", description: "", is_tablet: "0"))
  2973. }
  2974. row["category_cc"] = data
  2975. self.dataMessages.append(row)
  2976. self.channelContactCenter = "\(channel)"
  2977. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  2978. self.tableChatView.scrollToBottom()
  2979. } else {
  2980. self.fPinContacCenter = data
  2981. }
  2982. }
  2983. } else {
  2984. DispatchQueue.main.async {
  2985. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  2986. var row: [String: Any?] = [:]
  2987. row["message_id"] = ""
  2988. row["chat_date"] = "Today".localized()
  2989. row["attachment_flag"] = "504"
  2990. let listStringName: [String] = ["Yes".localized(), "No".localized()]
  2991. var data : [CategoryCC] = []
  2992. for i in 0..<listStringName.count {
  2993. data.append(CategoryCC(id: "level\(self.dataMessages.count + 1)_\(i)", service_id: service_id, service_name: listStringName[i], parent: "", description: "", is_tablet: "0"))
  2994. }
  2995. row["category_cc"] = data
  2996. self.dataMessages.append(row)
  2997. self.channelContactCenter = "\(channel)"
  2998. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  2999. self.tableChatView.scrollToBottom()
  3000. }
  3001. }
  3002. }
  3003. }
  3004. }
  3005. private func sendReadMessageStatus(chat_id: String, f_pin: String, message_scope_id: String, message_id: String) {
  3006. let message = CoreMessage_TMessageBank.getUpdateRead(p_chat_id: chat_id, p_f_pin: f_pin, p_scope_id: message_scope_id, qty: 1)
  3007. let fPin = message.getBody(key: CoreMessage_TMessageKey.F_PIN)
  3008. let scope = message.getBody(key: CoreMessage_TMessageKey.SCOPE_ID)
  3009. message.mBodies[CoreMessage_TMessageKey.SERVER_DATE] = String(Date().currentTimeMillis())
  3010. if (fPin.elementsEqual("-999") || scope.elementsEqual("16") || scope.elementsEqual("15")){
  3011. return
  3012. }
  3013. DispatchQueue.global().async {
  3014. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3015. let valueListGroupImages = listGroupImages.value
  3016. for i in 0..<valueListGroupImages.count {
  3017. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3018. do {
  3019. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3020. "status" : "4"
  3021. ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
  3022. } catch {
  3023. rollback.pointee = true
  3024. print("Access database error: \(error.localizedDescription)")
  3025. }
  3026. })
  3027. message.mStatus = CoreMessage_TMessageUtil.getTID()
  3028. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  3029. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
  3030. }
  3031. } else {
  3032. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3033. do {
  3034. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3035. "status" : "4"
  3036. ], _where: "message_id = '\(message_id)'")
  3037. } catch {
  3038. rollback.pointee = true
  3039. print("Access database error: \(error.localizedDescription)")
  3040. }
  3041. })
  3042. message.mStatus = CoreMessage_TMessageUtil.getTID()
  3043. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  3044. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
  3045. }
  3046. _ = Nexilis.write(message: message)
  3047. }
  3048. if let index = dataMessages.firstIndex(where: {$0["message_id"] as? String == message_id}) {
  3049. dataMessages[index]["status"] = "4"
  3050. let auto: Bool = SecureUserDefaults.shared.value(forKey: "autoDownload") ?? false
  3051. if auto {
  3052. if dataMessages[index]["image_id"] as? String != nil && !((dataMessages[index]["image_id"] as? String)!.isEmpty) {
  3053. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3054. let valueListGroupImages = listGroupImages.value
  3055. for i in 0..<valueListGroupImages.count {
  3056. Download().startHTTP(forKey:valueListGroupImages[i].imageId) { (name, progress) in
  3057. guard progress == 100 else {
  3058. return
  3059. }
  3060. do {
  3061. let secureName = try FileEncryption.shared.writeSecure(filename: valueListGroupImages[i].imageId)?[0]
  3062. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3063. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3064. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3065. if let dirPath = paths.first {
  3066. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(valueListGroupImages[i].imageId)
  3067. if FileManager.default.fileExists(atPath: imageURL.path) {
  3068. let image = UIImage(contentsOfFile: imageURL.path)
  3069. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3070. if save {
  3071. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3072. }
  3073. }
  3074. else if FileEncryption.shared.isSecureExists(filename: valueListGroupImages[i].imageId) {
  3075. if let secureData = try FileEncryption.shared.readSecure(filename: valueListGroupImages[i].imageId) {
  3076. let image = UIImage(data: secureData)
  3077. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3078. if save {
  3079. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3080. }
  3081. }
  3082. }
  3083. }
  3084. }
  3085. catch {
  3086. }
  3087. DispatchQueue.main.async { [self] in
  3088. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3089. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3090. if row != nil && section != nil{
  3091. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3092. }
  3093. }
  3094. }
  3095. }
  3096. } else {
  3097. Download().startHTTP(forKey:dataMessages[index]["image_id"] as? String ?? "") { (name, progress) in
  3098. guard progress == 100 else {
  3099. return
  3100. }
  3101. do {
  3102. let secureName = try FileEncryption.shared.writeSecure(filename: self.dataMessages[index]["image_id"] as? String)?[0] as? String ?? ""
  3103. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3104. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3105. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3106. if let dirPath = paths.first {
  3107. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["image_id"] as? String ?? "")
  3108. if FileManager.default.fileExists(atPath: imageURL.path) {
  3109. let image = UIImage(contentsOfFile: imageURL.path)
  3110. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3111. if save {
  3112. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3113. }
  3114. }
  3115. else if FileEncryption.shared.isSecureExists(filename: secureName) {
  3116. if let secureData = try FileEncryption.shared.readSecure(filename: secureName) {
  3117. let image = UIImage(data: secureData)
  3118. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3119. if save {
  3120. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3121. }
  3122. }
  3123. }
  3124. }
  3125. } catch {
  3126. }
  3127. DispatchQueue.main.async { [self] in
  3128. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3129. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3130. if row != nil && section != nil{
  3131. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3132. }
  3133. }
  3134. }
  3135. }
  3136. } else if dataMessages[index]["video_id"] as? String != nil && !((dataMessages[index]["video_id"] as? String)!.isEmpty){
  3137. Download().startHTTP(forKey: dataMessages[index]["video_id"] as? String ?? "", isImage: false) { (name, progress) in
  3138. guard progress == 100 else {
  3139. return
  3140. }
  3141. do {
  3142. let secureName = try FileEncryption.shared.writeSecure(filename: self.dataMessages[index]["video_id"] as? String)?[0] as? String ?? ""
  3143. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3144. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3145. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3146. if let dirPath = paths.first {
  3147. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["video_id"] as? String ?? "")
  3148. if FileManager.default.fileExists(atPath: videoURL.path) {
  3149. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3150. if save {
  3151. PHPhotoLibrary.shared().performChanges({
  3152. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
  3153. }) { saved, error in
  3154. }
  3155. }
  3156. }
  3157. else if FileEncryption.shared.isSecureExists(filename: secureName) {
  3158. if let secureData = try FileEncryption.shared.readSecure(filename: secureName) {
  3159. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  3160. let tempPath = cachesDirectory.appendingPathComponent(name)
  3161. try secureData.write(to: tempPath)
  3162. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3163. if save {
  3164. PHPhotoLibrary.shared().performChanges({
  3165. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
  3166. }) { saved, error in
  3167. }
  3168. }
  3169. }
  3170. }
  3171. }
  3172. } catch {
  3173. }
  3174. DispatchQueue.main.async { [self] in
  3175. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3176. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3177. if row != nil && section != nil{
  3178. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3179. }
  3180. }
  3181. }
  3182. }
  3183. else if dataMessages[index]["file_id"] as? String != nil && !((dataMessages[index]["file_id"] as? String)!.isEmpty) {
  3184. Download().startHTTP(forKey: dataMessages[index]["file_id"] as? String ?? "", isImage: false) { (name, progress) in
  3185. guard progress == 100 else {
  3186. return
  3187. }
  3188. do {
  3189. try FileEncryption.shared.writeSecure(filename: name)
  3190. } catch {
  3191. }
  3192. DispatchQueue.main.async { [self] in
  3193. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3194. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3195. if row != nil && section != nil{
  3196. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3197. }
  3198. }
  3199. }
  3200. }
  3201. }
  3202. }
  3203. }
  3204. private func sendTyping(l_pin: String, isTyping: Bool = false) {
  3205. DispatchQueue.global().async {
  3206. let tmessage = CoreMessage_TMessageBank.getUpdateTypingStatus(p_opposite: l_pin, p_scope: "3", p_status: isTyping ? "3": "4")
  3207. _ = Nexilis.write(message: tmessage)
  3208. }
  3209. }
  3210. private func getCounter() {
  3211. Database().database?.inTransaction({ fmdb, rollback in
  3212. if let c = Database().getRecords(fmdb: fmdb, query: "SELECT counter FROM MESSAGE_SUMMARY where l_pin='\(dataPerson["f_pin"]!!)'"), c.next() {
  3213. counter = Int(c.int(forColumnIndex: 0))
  3214. c.close()
  3215. }
  3216. })
  3217. }
  3218. private func updateCounter(counter: Int) {
  3219. DispatchQueue.global().async {
  3220. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3221. do {
  3222. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", cvalues: [
  3223. "counter" : "\(counter)"
  3224. ], _where: "l_pin = '\(self.dataPerson["f_pin"]!!)'")
  3225. } catch {
  3226. rollback.pointee = true
  3227. print("Access database error: \(error.localizedDescription)")
  3228. }
  3229. })
  3230. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  3231. }
  3232. }
  3233. private func addButtonScrollToBottom() {
  3234. if tableChatView.alpha != 1 || isSearching {
  3235. return
  3236. }
  3237. self.view.addSubview(buttonScrollToBottom)
  3238. buttonScrollToBottom.translatesAutoresizingMaskIntoConstraints = false
  3239. NSLayoutConstraint.activate([
  3240. buttonScrollToBottom.bottomAnchor.constraint(equalTo: buttonSendChat.topAnchor, constant: -50),
  3241. buttonScrollToBottom.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  3242. buttonScrollToBottom.widthAnchor.constraint(equalToConstant: 60),
  3243. buttonScrollToBottom.heightAnchor.constraint(equalToConstant: 30.0)
  3244. ])
  3245. buttonScrollToBottom.backgroundColor = .greenColor
  3246. buttonScrollToBottom.setImage(UIImage(systemName: "chevron.down.circle"), for: .normal)
  3247. buttonScrollToBottom.imageView?.contentMode = .scaleAspectFit
  3248. buttonScrollToBottom.imageView?.tintColor = .white
  3249. buttonScrollToBottom.contentVerticalAlignment = .fill
  3250. buttonScrollToBottom.contentHorizontalAlignment = .fill
  3251. buttonScrollToBottom.imageEdgeInsets.top = 2.0
  3252. buttonScrollToBottom.imageEdgeInsets.bottom = 2.0
  3253. buttonScrollToBottom.layer.cornerRadius = 10.0
  3254. buttonScrollToBottom.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
  3255. buttonScrollToBottom.clipsToBounds = true
  3256. buttonScrollToBottom.addTarget(self, action: #selector(scrollTobottomAction), for: .touchUpInside)
  3257. }
  3258. private func addCounterAtButttonScrollToBottom() {
  3259. if tableChatView.alpha != 1 || isSearching {
  3260. return
  3261. }
  3262. self.view.addSubview(indicatorCounterBSTB)
  3263. indicatorCounterBSTB.translatesAutoresizingMaskIntoConstraints = false
  3264. indicatorCounterBSTB.backgroundColor = .systemRed
  3265. indicatorCounterBSTB.layer.cornerRadius = 7.5
  3266. indicatorCounterBSTB.clipsToBounds = true
  3267. indicatorCounterBSTB.layer.borderWidth = 0.5
  3268. indicatorCounterBSTB.layer.borderColor = UIColor.secondaryColor.cgColor
  3269. NSLayoutConstraint.activate([
  3270. indicatorCounterBSTB.bottomAnchor.constraint(equalTo: buttonScrollToBottom.topAnchor, constant: 5),
  3271. indicatorCounterBSTB.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -50),
  3272. indicatorCounterBSTB.widthAnchor.constraint(greaterThanOrEqualToConstant: 15),
  3273. indicatorCounterBSTB.heightAnchor.constraint(equalToConstant: 15)
  3274. ])
  3275. indicatorCounterBSTB.addSubview(labelCounter)
  3276. labelCounter.translatesAutoresizingMaskIntoConstraints = false
  3277. NSLayoutConstraint.activate([
  3278. labelCounter.leadingAnchor.constraint(equalTo: indicatorCounterBSTB.leadingAnchor, constant: 2),
  3279. labelCounter.trailingAnchor.constraint(equalTo: indicatorCounterBSTB.trailingAnchor, constant: -2),
  3280. labelCounter.centerXAnchor.constraint(equalTo: indicatorCounterBSTB.centerXAnchor),
  3281. ])
  3282. labelCounter.font = UIFont.systemFont(ofSize: 11)
  3283. labelCounter.text = "\(counter)"
  3284. labelCounter.textColor = .secondaryColor
  3285. labelCounter.textAlignment = .center
  3286. }
  3287. @objc func scrollTobottomAction() {
  3288. tableChatView.scrollToBottom()
  3289. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [self] in
  3290. if buttonScrollToBottom.isDescendant(of: self.view) {
  3291. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  3292. buttonScrollToBottom.removeFromSuperview()
  3293. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3294. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3295. indicatorCounterBSTB.removeFromSuperview()
  3296. }
  3297. }
  3298. }
  3299. }
  3300. private func checkNewMessage(tableView: UITableView) {
  3301. // let indexPathFirst = tableView.indexPathsForVisibleRows?.first
  3302. // if indexPathFirst != nil {
  3303. // let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPathFirst!.section] })
  3304. // if self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPathFirst!.row]["message_id"] as? String }) == 0 && !gettingDataMessage {
  3305. // gettingDataMessage = true
  3306. // addDataMessage()
  3307. // }
  3308. // }
  3309. currentIndexpath = tableView.indexPathsForVisibleRows?.last
  3310. let indexFirst = tableView.indexPathsForVisibleRows?.first
  3311. if indexFirst != nil {
  3312. let dataMessages = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  3313. if dataMessages.count == 0 || dataMessages.count - 1 < currentIndexpath!.row {
  3314. return
  3315. }
  3316. let contentHeight = tableView.contentSize.height
  3317. let scrollViewHeight = tableView.frame.height
  3318. let fullContentOffset = contentHeight - scrollViewHeight
  3319. let contentOffsetY = tableView.contentOffset.y
  3320. if ((currentIndexpath!.section == dataDates.count - 1 && indexFirst!.row != dataMessages.count - 1) || indexFirst!.section != dataDates.count - 1) && fullContentOffset - contentOffsetY > 100 {
  3321. if !buttonScrollToBottom.isDescendant(of: self.view) {
  3322. addButtonScrollToBottom()
  3323. addCounterAtButttonScrollToBottom()
  3324. }
  3325. } else if (indexFirst!.section == dataDates.count - 1 && indexFirst!.row == dataMessages.count - 1) || fullContentOffset - contentOffsetY < 50 {
  3326. if buttonScrollToBottom.isDescendant(of: self.view) {
  3327. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  3328. buttonScrollToBottom.removeFromSuperview()
  3329. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3330. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3331. indicatorCounterBSTB.removeFromSuperview()
  3332. }
  3333. }
  3334. }
  3335. let indexPathFirst = tableChatView.indexPathsForVisibleRows?.first
  3336. if indexPathFirst != nil && listViewOnSection.count != 0 && listViewOnSection.count - 1 >= indexPathFirst!.section {
  3337. let headerView = listViewOnSection[indexPathFirst!.section]
  3338. if headerView.isHidden {
  3339. headerView.isHidden = false
  3340. }
  3341. }
  3342. var listData = dataMessages[0...currentIndexpath!.row]
  3343. listData = listData.filter({$0["status"] as? String != "4" && $0["status"] as? String != "8"})
  3344. if listData.count != 0 && !isContactCenter {
  3345. let idMe = User.getMyPin() as String?
  3346. for i in 0...listData.count - 1 {
  3347. if listData[i]["f_pin"] as? String != idMe {
  3348. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: "3", message_id: listData[i]["message_id"] as? String ?? "")
  3349. }
  3350. }
  3351. }
  3352. }
  3353. if counter == 0 && indicatorCounterBSTB.isDescendant(of: self.view) {
  3354. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3355. indicatorCounterBSTB.removeFromSuperview()
  3356. } else if counter != 0 && currentIndexpath != nil {
  3357. let dataFilter = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  3358. if dataFilter.count == 0 {
  3359. return
  3360. }
  3361. let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == dataFilter[currentIndexpath!.row]["message_id"] as? String})
  3362. if idx == nil {
  3363. return
  3364. }
  3365. if (dataMessages.count - counter) <= idx! {
  3366. let countUpdate = idx! - (dataMessages.count - counter)
  3367. counter = counter - (countUpdate + 1)
  3368. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3369. labelCounter.text = "\(counter)"
  3370. }
  3371. updateCounter(counter: counter)
  3372. }
  3373. }
  3374. }
  3375. }
  3376. //EPV
  3377. extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewControllerDelegate {
  3378. public func didSelect(imagevideo: Any?) {
  3379. if (imagevideo != nil) {
  3380. let imageVideoData = imagevideo as! [UIImagePickerController.InfoKey: Any]
  3381. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3382. previewImageVC.imageVideoData = imageVideoData
  3383. if (textFieldSend.textColor != .lightGray) {
  3384. previewImageVC.currentTextTextField = textFieldSend.text
  3385. }
  3386. previewImageVC.modalPresentationStyle = .custom
  3387. previewImageVC.delegate = self
  3388. previewImageVC.isAck = self.isAck
  3389. previewImageVC.isConfidential = self.isConfidential
  3390. previewImageVC.isCC = self.isContactCenter
  3391. self.present(previewImageVC, animated: true, completion: nil)
  3392. }
  3393. }
  3394. public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  3395. if !isBlackCancelButton {
  3396. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  3397. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  3398. }
  3399. guard let result = results.first else { return }
  3400. if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
  3401. picker.dismiss(animated: true, completion: nil)
  3402. result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
  3403. if let error = error {
  3404. print("Error loading GIF: \(error.localizedDescription)")
  3405. } else if let data = data {
  3406. DispatchQueue.main.async {
  3407. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3408. if (self.textFieldSend.textColor != .lightGray) {
  3409. previewImageVC.currentTextTextField = self.textFieldSend.text
  3410. }
  3411. previewImageVC.fromCopy = true
  3412. previewImageVC.isGIF = true
  3413. previewImageVC.dataGIF = data
  3414. previewImageVC.modalPresentationStyle = .custom
  3415. previewImageVC.delegate = self
  3416. previewImageVC.isAck = self.isAck
  3417. previewImageVC.isConfidential = self.isConfidential
  3418. previewImageVC.isCC = self.isContactCenter
  3419. self.present(previewImageVC, animated: true, completion: nil)
  3420. }
  3421. }
  3422. }
  3423. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
  3424. picker.dismiss(animated: true, completion: nil)
  3425. result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
  3426. if let image = object as? UIImage {
  3427. DispatchQueue.main.async {
  3428. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3429. if (self.textFieldSend.textColor != .lightGray) {
  3430. previewImageVC.currentTextTextField = self.textFieldSend.text
  3431. }
  3432. previewImageVC.fromCopy = true
  3433. previewImageVC.image = image
  3434. previewImageVC.modalPresentationStyle = .custom
  3435. previewImageVC.delegate = self
  3436. previewImageVC.isAck = self.isAck
  3437. previewImageVC.isConfidential = self.isConfidential
  3438. previewImageVC.isCC = self.isContactCenter
  3439. self.present(previewImageVC, animated: true, completion: nil)
  3440. }
  3441. }
  3442. }
  3443. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
  3444. picker.dismiss(animated: true, completion: {
  3445. Nexilis.showLoader()
  3446. result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
  3447. if let tempURL = tempURL {
  3448. let fileManager = FileManager.default
  3449. let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
  3450. let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
  3451. do {
  3452. if fileManager.fileExists(atPath: destinationURL.path) {
  3453. try fileManager.removeItem(at: destinationURL)
  3454. }
  3455. try fileManager.copyItem(at: tempURL, to: destinationURL)
  3456. DispatchQueue.main.async {
  3457. Nexilis.hideLoader {
  3458. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3459. if (self.textFieldSend.textColor != .lightGray) {
  3460. previewImageVC.currentTextTextField = self.textFieldSend.text
  3461. }
  3462. previewImageVC.modalPresentationStyle = .custom
  3463. previewImageVC.urlVideoPhpPicker = destinationURL
  3464. previewImageVC.delegate = self
  3465. previewImageVC.isAck = self.isAck
  3466. previewImageVC.isConfidential = self.isConfidential
  3467. previewImageVC.isCC = self.isContactCenter
  3468. self.present(previewImageVC, animated: true, completion: nil)
  3469. }
  3470. }
  3471. } catch {
  3472. print("Error copying video file: \(error.localizedDescription)")
  3473. }
  3474. }
  3475. }
  3476. })
  3477. }
  3478. }
  3479. func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController) {
  3480. sendChat(message_text: message_text, attachment_flag: attachment_flag, image_id: image_id, video_id: video_id, thumb_id: thumb_id, viewController: viewController, gif_id : gif_id)
  3481. }
  3482. }
  3483. //EQL
  3484. extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPreviewControllerDataSource {
  3485. public func didSelectDocument(document: Any?) {
  3486. if (document != nil) {
  3487. self.previewItem = (document as! [URL])[0] as NSURL
  3488. let previewController = QLPreviewController()
  3489. let navController = CustomNavigationController(rootViewController: previewController)
  3490. navController.navigationBar.tintColor = .white
  3491. navController.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  3492. navController.navigationBar.isTranslucent = false
  3493. navController.navigationBar.overrideUserInterfaceStyle = .dark
  3494. navController.navigationBar.barStyle = .black
  3495. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  3496. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  3497. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  3498. navController.navigationBar.titleTextAttributes = textAttributes
  3499. let leftBarButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
  3500. let rightBarButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
  3501. // leftBarButton.tintColor = .white
  3502. // rightBarButton.tintColor = .white
  3503. leftBarButton.navigation = navController
  3504. rightBarButton.navigation = navController
  3505. // navController.navigationBar.barTintColor = .mainColor
  3506. navController.navigationBar.isTranslucent = false
  3507. previewController.navigationItem.leftBarButtonItem = leftBarButton
  3508. previewController.navigationItem.rightBarButtonItem = rightBarButton
  3509. previewController.dataSource = self
  3510. previewController.modalPresentationStyle = .pageSheet
  3511. self.present(navController, animated: true, completion: nil)
  3512. }
  3513. }
  3514. @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
  3515. sender.navigation.dismiss(animated: true, completion: nil)
  3516. }
  3517. @objc private func sendDocument(sender: navigationQLPreviewDocument) {
  3518. sender.navigation.dismiss(animated: true, completion: nil)
  3519. do {
  3520. let dataFile = try Data(contentsOf: self.previewItem! as URL)
  3521. let urlFile = self.previewItem?.absoluteString
  3522. var originaFileName = (urlFile! as NSString).lastPathComponent
  3523. originaFileName = NSString(string: originaFileName).removingPercentEncoding!
  3524. let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
  3525. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  3526. let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
  3527. if !FileManager.default.fileExists(atPath: fileURL.path) {
  3528. do {
  3529. try dataFile.write(to: fileURL)
  3530. //print("file saved")
  3531. } catch {
  3532. //print("error saving file:", error)
  3533. }
  3534. }
  3535. sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
  3536. } catch {
  3537. }
  3538. }
  3539. public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  3540. return self.previewItem != nil ? 1 : 0
  3541. }
  3542. public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  3543. return self.previewItem!
  3544. }
  3545. }
  3546. //ETV
  3547. extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
  3548. func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?) {
  3549. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3550. previewImageVC.image = image
  3551. previewImageVC.isGIF = image == nil
  3552. previewImageVC.fromCopy = true
  3553. previewImageVC.dataGIF = dataGIF
  3554. previewImageVC.currentTextTextField = textFieldSend.text
  3555. previewImageVC.modalPresentationStyle = .custom
  3556. previewImageVC.delegate = self
  3557. previewImageVC.isAck = self.isAck
  3558. previewImageVC.isConfidential = self.isConfidential
  3559. self.present(previewImageVC, animated: true, completion: nil)
  3560. }
  3561. public func textViewDidChangeSelection(_ textView: UITextView) {
  3562. var nowTextFieldSend = self.textFieldSend
  3563. if isEditingMessage {
  3564. nowTextFieldSend = editTextView
  3565. }
  3566. let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
  3567. let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
  3568. if doubleCurrentLine.isFinite {
  3569. let currentLine = Int(doubleCurrentLine)
  3570. UIView.animate(withDuration: 0.3) {
  3571. let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
  3572. if currentLine == 0 && numberOfLines == 1 {
  3573. if self.isEditingMessage {
  3574. self.constraintHeighteditTextView.constant = 40
  3575. } else {
  3576. self.heightTextFieldSend.constant = 40
  3577. }
  3578. } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
  3579. if self.isEditingMessage {
  3580. self.constraintHeighteditTextView.constant = 95.0
  3581. } else {
  3582. self.heightTextFieldSend.constant = 95.0
  3583. }
  3584. } else if currentLine < 4 && numberOfLines < 5 {
  3585. if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
  3586. if self.isEditingMessage {
  3587. self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
  3588. } else {
  3589. self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
  3590. }
  3591. }
  3592. }
  3593. }
  3594. }
  3595. if self.isEditingMessage && textView == editTextView {
  3596. if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  3597. buttonSendEdit.isEnabled = false
  3598. } else if !buttonSendEdit.isEnabled {
  3599. buttonSendEdit.isEnabled = true
  3600. }
  3601. }
  3602. //indention code:
  3603. let text = textView.text ?? ""
  3604. let cursorPositionIndent = textView.selectedRange.location
  3605. // Prevent moving cursor before the 2-space indent
  3606. let lines = text.components(separatedBy: "\n")
  3607. var adjustedCursorPosition = cursorPositionIndent
  3608. for line in lines {
  3609. if let range = text.range(of: line), NSRange(range, in: text).contains(cursorPositionIndent) {
  3610. if line.hasPrefix(" •") || line.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  3611. let startOfLine = text.distance(from: text.startIndex, to: range.lowerBound)
  3612. let minCursorPosition = startOfLine + 2 // Prevent cursor before indentation
  3613. if cursorPositionIndent < minCursorPosition {
  3614. adjustedCursorPosition = minCursorPosition
  3615. }
  3616. }
  3617. break
  3618. }
  3619. }
  3620. if adjustedCursorPosition != cursorPositionIndent {
  3621. textView.selectedRange = NSRange(location: adjustedCursorPosition, length: 0)
  3622. }
  3623. }
  3624. public func textViewDidChange(_ textView: UITextView) {
  3625. if textView.text.count == 0 {
  3626. isAlwaysHideLinkPreview = false
  3627. }
  3628. if allowTyping {
  3629. allowTyping = false
  3630. if isContactCenter && !fPinContacCenter.isEmpty {
  3631. sendTyping(l_pin: fPinContacCenter, isTyping: true)
  3632. } else {
  3633. sendTyping(l_pin: dataPerson["f_pin"]!!, isTyping: true)
  3634. }
  3635. DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
  3636. self.allowTyping = true
  3637. })
  3638. }
  3639. timerCheckLink?.invalidate()
  3640. timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
  3641. self.checkLink(fullText: textView.text)
  3642. })
  3643. //indention code:
  3644. let text = textView.text ?? ""
  3645. let cursorPosition = textView.selectedRange.location
  3646. // Handle Bullets (- [space] + letter → • )
  3647. let bulletPattern = #"(?<=\n|^)- (\S)"#
  3648. if let match = text.range(of: bulletPattern, options: .regularExpression) {
  3649. let matchedText = text[match]
  3650. if let spaceIndex = matchedText.firstIndex(of: " ") {
  3651. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  3652. let replacedText = text.replacingOccurrences(of: matchedText, with: " • \(firstLetter)", range: match)
  3653. let newCursorPosition = cursorPosition + 2 // Adjust cursor position
  3654. textView.text = replacedText
  3655. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  3656. return
  3657. }
  3658. }
  3659. // Handle Numbered Lists (e.g., "1. " [space] + letter → " 1.")
  3660. let numberPattern = #"(?<=\n|^)(\d+)\. (\S)"# // Matches "1. X"
  3661. if let match = text.range(of: numberPattern, options: .regularExpression) {
  3662. let matchedText = text[match]
  3663. if let spaceIndex = matchedText.firstIndex(of: " ") {
  3664. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  3665. let replacedText = text.replacingOccurrences(of: matchedText, with: " \(matchedText)", range: match)
  3666. let newCursorPosition = cursorPosition + 2 // Adjust cursor
  3667. textView.text = replacedText
  3668. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  3669. return
  3670. }
  3671. }
  3672. // Handle Undo: If user removes the first letter, revert back to original "- " or "1. "
  3673. let bulletUndoPattern = #"(^|\n) • $"# // Matches " • " when the letter is removed
  3674. if let match = text.range(of: bulletUndoPattern, options: .regularExpression) {
  3675. let replacedText = text.replacingOccurrences(of: " • ", with: "- ", range: match)
  3676. let newCursorPosition = cursorPosition - 2
  3677. textView.text = replacedText
  3678. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  3679. return
  3680. }
  3681. let numberUndoPattern = #"(^|\n) (\d+)\. $"# // Matches " 1. " when the letter is removed
  3682. if let match = text.range(of: numberUndoPattern, options: .regularExpression) {
  3683. let replacedText = text.replacingOccurrences(of: " ", with: "", range: match)
  3684. let newCursorPosition = cursorPosition - 2
  3685. textView.text = replacedText
  3686. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  3687. }
  3688. textView.preserveCursorPosition(withChanges: { _ in
  3689. textView.attributedText = textView.text.richText(isEditing: true)
  3690. return .preserveCursor
  3691. })
  3692. }
  3693. private func checkLink(fullText: String) {
  3694. if !isAlwaysHideLinkPreview {
  3695. var text = ""
  3696. let listTextSplitBreak = fullText.components(separatedBy: "\n")
  3697. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  3698. if indexFirstLinkSplitBreak != nil {
  3699. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  3700. let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
  3701. if indexFirstLinkSplitSpace != nil {
  3702. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  3703. }
  3704. }
  3705. if !text.isEmpty {
  3706. var stringURl = text
  3707. if stringURl.starts(with: "www.") {
  3708. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  3709. }
  3710. var dataURL = ""
  3711. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3712. do {
  3713. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
  3714. while cursor.next() {
  3715. if let data = cursor.string(forColumnIndex: 0) {
  3716. dataURL = data
  3717. }
  3718. }
  3719. cursor.close()
  3720. }
  3721. } catch {
  3722. rollback.pointee = true
  3723. print("Access database error: \(error.localizedDescription)")
  3724. }
  3725. })
  3726. if !dataURL.isEmpty {
  3727. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  3728. let title = data["title"] as? String ?? ""
  3729. let description = data["description"] as? String ?? ""
  3730. let imageUrl = data["imageUrl"] as? String
  3731. let link = data["link"] as? String ?? ""
  3732. if self.showingLink != text {
  3733. self.showingLink = text
  3734. self.deleteLinkPreview()
  3735. if !textFieldSend.text.isEmpty || textFieldSend.text.contains(text){
  3736. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  3737. }
  3738. }
  3739. }
  3740. } else {
  3741. let urlConfig = URLSessionConfiguration.default
  3742. let sessionDelegate = SelfSignedURLSessionDelegate()
  3743. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  3744. let slp = SwiftLinkPreview(session: session,
  3745. workQueue: SwiftLinkPreview.defaultWorkQueue,
  3746. responseQueue: DispatchQueue.main,
  3747. cache: DisabledCache.instance)
  3748. let preview = slp.preview(stringURl,
  3749. onSuccess: { result in
  3750. let title = result.title ?? "No Title"
  3751. let description = stringURl.contains("google.com") ? "" : result.description
  3752. let imageUrl = result.icon
  3753. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3754. do {
  3755. var dataJson: [String: Any] = [:]
  3756. dataJson["title"] = title
  3757. dataJson["description"] = description
  3758. dataJson["imageUrl"] = imageUrl
  3759. dataJson["link"] = text
  3760. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  3761. return
  3762. }
  3763. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  3764. "id" : "\(Date().currentTimeMillis().toHex())",
  3765. "link" : text,
  3766. "data_link" : json,
  3767. "retry": 0
  3768. ], replace: true)
  3769. } catch {
  3770. rollback.pointee = true
  3771. print("Access database error: \(error.localizedDescription)")
  3772. }
  3773. })
  3774. if self.showingLink != text {
  3775. self.showingLink = text
  3776. self.deleteLinkPreview()
  3777. if !self.textFieldSend.text.isEmpty || self.textFieldSend.text.contains(text){
  3778. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  3779. }
  3780. }
  3781. },
  3782. onError: { error in
  3783. self.deleteLinkPreview()
  3784. })
  3785. }
  3786. } else {
  3787. deleteLinkPreview()
  3788. }
  3789. }
  3790. }
  3791. private func buildPreviewLink(imageUrl: String?, title: String, description: String?, stringURl: String) {
  3792. if !self.viewTextfield.subviews.contains(self.containerLink){
  3793. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  3794. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
  3795. }, completion: nil)
  3796. }
  3797. self.viewTextfield.addSubview(self.containerLink)
  3798. self.containerLink.translatesAutoresizingMaskIntoConstraints = false
  3799. self.containerLink.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  3800. self.containerLink.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor).isActive = true
  3801. self.containerLink.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  3802. self.containerLink.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  3803. self.containerLink.backgroundColor = .secondaryColor
  3804. if self.reffId != nil {
  3805. self.bottomAnchorPreviewReply.isActive = false
  3806. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  3807. self.bottomAnchorPreviewReply.isActive = true
  3808. }
  3809. let imagePreview = UIImageView()
  3810. if imageUrl != nil {
  3811. self.containerLink.addSubview(imagePreview)
  3812. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  3813. imagePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor).isActive = true
  3814. imagePreview.bottomAnchor.constraint(equalTo: self.containerLink.bottomAnchor).isActive = true
  3815. imagePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor).isActive = true
  3816. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  3817. imagePreview.loadImageAsync(with: imageUrl)
  3818. imagePreview.contentMode = .scaleAspectFit
  3819. }
  3820. let titlePreview = UILabel()
  3821. self.containerLink.addSubview(titlePreview)
  3822. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  3823. if imageUrl != nil {
  3824. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  3825. } else {
  3826. titlePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  3827. }
  3828. titlePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor, constant: 25.0).isActive = true
  3829. titlePreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  3830. titlePreview.text = title
  3831. titlePreview.font = UIFont.systemFont(ofSize: 14.0 + offset(), weight: .bold)
  3832. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  3833. let descPreview = UILabel()
  3834. self.containerLink.addSubview(descPreview)
  3835. descPreview.translatesAutoresizingMaskIntoConstraints = false
  3836. if imageUrl != nil {
  3837. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  3838. } else {
  3839. descPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  3840. }
  3841. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  3842. descPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  3843. descPreview.text = description
  3844. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  3845. descPreview.textColor = .gray
  3846. descPreview.numberOfLines = 1
  3847. let linkPreview = UILabel()
  3848. self.containerLink.addSubview(linkPreview)
  3849. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  3850. if imageUrl != nil {
  3851. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  3852. } else {
  3853. linkPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  3854. }
  3855. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  3856. linkPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  3857. linkPreview.text = stringURl
  3858. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  3859. linkPreview.textColor = .gray
  3860. linkPreview.numberOfLines = 1
  3861. let cancelPreview = UIButton(type: .custom)
  3862. self.containerLink.addSubview(cancelPreview)
  3863. cancelPreview.translatesAutoresizingMaskIntoConstraints = false
  3864. cancelPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -10).isActive = true
  3865. cancelPreview.centerYAnchor.constraint(equalTo: self.containerLink.centerYAnchor).isActive = true
  3866. cancelPreview.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  3867. cancelPreview.addTarget(nil, action: #selector(self.removeLinkPreviewUntilEmptyTextView), for: .touchUpInside)
  3868. cancelPreview.backgroundColor = .clear
  3869. cancelPreview.tintColor = .mainColor
  3870. }
  3871. public func textViewDidBeginEditing(_ textView: UITextView) {
  3872. if textView.textColor == UIColor.lightGray {
  3873. textView.text = nil
  3874. textView.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  3875. }
  3876. }
  3877. public func textViewDidEndEditing(_ textView: UITextView) {
  3878. if textView.text.isEmpty && textView != editTextView {
  3879. textView.text = "Send message".localized()
  3880. textView.textColor = UIColor.lightGray
  3881. }
  3882. }
  3883. public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  3884. let nsText = textView.text as NSString? ?? ""
  3885. let newText = nsText.replacingCharacters(in: range, with: text)
  3886. var lines = textView.text.components(separatedBy: "\n")
  3887. let affectedLineIndex = textView.text[..<textView.text.index(textView.text.startIndex, offsetBy: range.location)].components(separatedBy: "\n").count - 1
  3888. guard affectedLineIndex >= 0, affectedLineIndex < lines.count else { return true }
  3889. let affectedLine = lines[affectedLineIndex]
  3890. // Prevent deleting two-space indentation before bullet/number
  3891. if affectedLine.hasPrefix(" •") || affectedLine.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  3892. let startIndex = textView.text.distance(from: textView.text.startIndex, to: textView.text.range(of: affectedLine)?.lowerBound ?? textView.text.startIndex)
  3893. if range.location == startIndex || range.location == startIndex + 1 {
  3894. return false
  3895. }
  3896. }
  3897. // Auto-indent new lines based on previous line
  3898. if text == "\n" {
  3899. let previousLine = lines[affectedLineIndex]
  3900. if previousLine.hasPrefix(" •") {
  3901. let newBullet = "\n • "
  3902. textView.text = nsText.replacingCharacters(in: range, with: newBullet)
  3903. textView.selectedRange = NSRange(location: range.location + newBullet.count, length: 0)
  3904. return false
  3905. }
  3906. if let match = previousLine.range(of: #"^\s{2}(\d+)\."#, options: .regularExpression),
  3907. let numberMatch = previousLine[match].components(separatedBy: ".").first,
  3908. let number = Int(numberMatch.trimmingCharacters(in: .whitespaces)) {
  3909. let newNumber = "\n \(number + 1). "
  3910. textView.text = nsText.replacingCharacters(in: range, with: newNumber)
  3911. textView.selectedRange = NSRange(location: range.location + newNumber.count, length: 0)
  3912. return false
  3913. }
  3914. }
  3915. // **Handle Backspace on Empty Bullet (Convert " • " → "- ")**
  3916. if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
  3917. lines[affectedLineIndex] = "- " // Replace " • " with "- "
  3918. textView.text = lines.joined(separator: "\n")
  3919. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  3920. return false
  3921. }
  3922. if text.isEmpty, let numberMatch = affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) {
  3923. lines[affectedLineIndex] = "\(affectedLine.trimmingCharacters(in: .whitespaces))" // Remove indent
  3924. textView.text = lines.joined(separator: "\n")
  3925. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  3926. return false
  3927. }
  3928. if (self.textFieldSend.text.count == 0) {
  3929. return text != "\n"
  3930. }
  3931. return true
  3932. }
  3933. func isGIFData(_ data: Data) -> Bool {
  3934. let gifSignature: [UInt8] = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]
  3935. let rawData = [UInt8](data.prefix(6))
  3936. return rawData == gifSignature
  3937. }
  3938. public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  3939. switch interaction {
  3940. case .invokeDefaultAction:
  3941. let gesture = ObjectGesture()
  3942. gesture.message_id = URL.absoluteString
  3943. tapMessageText(gesture)
  3944. return false
  3945. case .presentActions:
  3946. UIPasteboard.general.string = URL.absoluteString
  3947. self.view.makeToast("Link Copied".localized(), duration: 3)
  3948. return false
  3949. case .preview:
  3950. return true
  3951. @unknown default:
  3952. return true
  3953. }
  3954. }
  3955. }
  3956. //EUC
  3957. extension EditorPersonal: UIContextMenuInteractionDelegate {
  3958. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
  3959. if showMenuContext {
  3960. showMenuContext = false
  3961. interaction.view!.removeInteraction(interaction)
  3962. }
  3963. }
  3964. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
  3965. if textFieldSend.isFirstResponder {
  3966. textFieldSend.resignFirstResponder()
  3967. }
  3968. let indexPath = self.tableChatView.indexPathForRow(at: interaction.view!.convert(location, to: self.tableChatView))
  3969. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  3970. var star: UIAction
  3971. if (dataMessages[indexPath!.row]["is_stared"] as? String ?? "" == "0") {
  3972. star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star"), handler: {(_) in
  3973. if self.removed {
  3974. return
  3975. }
  3976. DispatchQueue.global().async {
  3977. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3978. do {
  3979. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3980. "is_stared" : 1
  3981. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  3982. } catch {
  3983. rollback.pointee = true
  3984. print("Access database error: \(error.localizedDescription)")
  3985. }
  3986. })
  3987. }
  3988. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  3989. if idx != nil{
  3990. self.dataMessages[idx!]["is_stared"] = "1"
  3991. }
  3992. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  3993. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  3994. })
  3995. } else {
  3996. star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash"), handler: {(_) in
  3997. if self.removed {
  3998. return
  3999. }
  4000. DispatchQueue.global().async {
  4001. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4002. do {
  4003. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4004. "is_stared" : 0
  4005. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  4006. } catch {
  4007. rollback.pointee = true
  4008. print("Access database error: \(error.localizedDescription)")
  4009. }
  4010. })
  4011. }
  4012. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4013. if idx != nil{
  4014. self.dataMessages[idx!]["is_stared"] = "0"
  4015. }
  4016. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4017. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  4018. })
  4019. }
  4020. let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left"), handler: {(_) in
  4021. if self.removed {
  4022. return
  4023. }
  4024. if self.isSearching {
  4025. self.cancelAction()
  4026. }
  4027. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  4028. self.handleReply(indexPath: indexPath!)
  4029. })
  4030. })
  4031. let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right"), handler: {(_) in
  4032. if self.removed {
  4033. return
  4034. }
  4035. if self.isSearching {
  4036. self.cancelAction()
  4037. }
  4038. if self.reffId != nil {
  4039. self.deleteReplyView()
  4040. }
  4041. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  4042. self.forwardSession = true
  4043. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  4044. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  4045. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  4046. self.navigationItem.rightBarButtonItems = nil
  4047. }
  4048. self.navigationItem.rightBarButtonItem = cancelButton
  4049. if self.isContactCenter || self.fromNotification {
  4050. self.navigationItem.leftBarButtonItem = nil
  4051. }
  4052. self.changeAppBar()
  4053. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4054. if idx != nil{
  4055. self.dataMessages[idx!]["isSelected"] = true
  4056. }
  4057. self.addMultipleSelectSession()
  4058. self.tableChatView.reloadData()
  4059. }
  4060. })
  4061. let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc"), handler: {(_) in
  4062. if self.removed {
  4063. return
  4064. }
  4065. if self.isSearching {
  4066. self.cancelAction()
  4067. }
  4068. if self.reffId != nil {
  4069. self.deleteReplyView()
  4070. }
  4071. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  4072. self.copySession = true
  4073. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  4074. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  4075. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  4076. self.navigationItem.rightBarButtonItems = nil
  4077. }
  4078. self.navigationItem.rightBarButtonItem = cancelButton
  4079. if self.isContactCenter || self.fromNotification {
  4080. self.navigationItem.leftBarButtonItem = nil
  4081. }
  4082. self.changeAppBar()
  4083. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4084. if idx != nil{
  4085. self.dataMessages[idx!]["isSelected"] = true
  4086. }
  4087. self.addMultipleSelectSession()
  4088. self.tableChatView.reloadData()
  4089. }
  4090. })
  4091. let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil.tip.crop.circle"), handler: {(_) in
  4092. self.isEditingMessage = true
  4093. self.showEditMessageView(at: indexPath!)
  4094. })
  4095. let translate = UIAction(title: "Translate".localized(), image: UIImage(systemName: "t.bubble"), handler: {(_) in
  4096. self.view.makeToast("Translating...".localized(), duration: 3)
  4097. var translation: String = "English"
  4098. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  4099. if lang == "id" {
  4100. translation = "Indonesia"
  4101. }
  4102. let payload: [String : Any] = [
  4103. "role": "user",
  4104. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  4105. ]
  4106. let parameter: [String : Any] = [
  4107. "use_video": "0",
  4108. "translate": translation,
  4109. "payload": [payload]
  4110. ]
  4111. DispatchQueue.global().async {
  4112. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  4113. let response = response as? HTTPURLResponse
  4114. if response?.statusCode != 200 || error != nil {
  4115. DispatchQueue.main.async {
  4116. self.view.makeToast("There is an error occurred while translating your message. Please try again or check your network connection.".localized(), duration: 3)
  4117. }
  4118. return
  4119. }
  4120. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  4121. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
  4122. let dataContent = json["content"]!
  4123. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4124. if idx != nil{
  4125. self.dataMessages[idx!][TypeDataMessage.message_text] = (dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "") + "\n\n" + "$\(dataContent)$"
  4126. }
  4127. DispatchQueue.main.async{
  4128. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4129. }
  4130. }
  4131. }
  4132. })
  4133. }
  4134. })
  4135. let gcs = UIAction(title: "Get Chat Suggestion".localized(), image: UIImage(systemName: "exclamationmark.bubble"), handler: {(_) in
  4136. self.view.makeToast("Getting chat suggestion...".localized(), duration: 3)
  4137. let payload: [String : Any] = [
  4138. "role": "user",
  4139. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  4140. ]
  4141. let parameter: [String : Any] = [
  4142. "use_video": "0",
  4143. "suggest": "1",
  4144. "payload": [payload]
  4145. ]
  4146. DispatchQueue.global().async {
  4147. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  4148. let response = response as? HTTPURLResponse
  4149. if response?.statusCode != 200 || error != nil {
  4150. DispatchQueue.main.async {
  4151. self.view.makeToast("There is an error occurred while getting chat suggestion for you. Please try again or check your network connection.".localized(), duration: 3)
  4152. }
  4153. return
  4154. }
  4155. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  4156. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
  4157. if let dataMessage = json["message"] as? [[String: Any]] {
  4158. if let dataContent = dataMessage[0]["content"] as? String {
  4159. DispatchQueue.main.async{
  4160. self.textFieldSend.text = dataContent
  4161. self.textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  4162. }
  4163. }
  4164. }
  4165. }
  4166. }
  4167. })
  4168. }
  4169. })
  4170. let more = UIMenu(title: "More...".localized(), children: [translate, gcs])
  4171. let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle"), handler: {(_) in
  4172. if self.removed {
  4173. return
  4174. }
  4175. let messageInfoVC = MessageInfo()
  4176. messageInfoVC.data = dataMessages[indexPath!.row]
  4177. messageInfoVC.dataPerson = self.dataPerson
  4178. self.navigationController?.pushViewController(messageInfoVC, animated: true)
  4179. })
  4180. let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash"), attributes: .destructive, handler: {(_) in
  4181. if self.removed {
  4182. return
  4183. }
  4184. if self.isSearching {
  4185. self.cancelAction()
  4186. }
  4187. if self.reffId != nil {
  4188. self.deleteReplyView()
  4189. }
  4190. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  4191. self.deleteSession = true
  4192. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  4193. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  4194. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  4195. self.navigationItem.rightBarButtonItems = nil
  4196. }
  4197. self.navigationItem.rightBarButtonItem = cancelButton
  4198. if self.isContactCenter || self.fromNotification {
  4199. self.navigationItem.leftBarButtonItem = nil
  4200. }
  4201. self.changeAppBar()
  4202. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4203. if idx != nil{
  4204. self.dataMessages[idx!]["isSelected"] = true
  4205. }
  4206. self.addMultipleSelectSession()
  4207. self.tableChatView.reloadData()
  4208. }
  4209. })
  4210. let resend = UIAction(title: "Resend".localized(), image: UIImage(systemName: "arrow.clockwise"), handler: {(_) in
  4211. let messageId = dataMessages[indexPath!.row][TypeDataMessage.message_id] as? String ?? ""
  4212. let status = dataMessages[indexPath!.row][TypeDataMessage.status] as? String ?? ""
  4213. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  4214. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  4215. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  4216. self.groupImages[idxMessageIdParent].value[idxInImages].status = "1"
  4217. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage[TypeDataMessage.status] = "1"
  4218. }
  4219. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  4220. }
  4221. if (idx != nil) {
  4222. do {
  4223. self.dataMessages[idx!][TypeDataMessage.status] = "1"
  4224. self.dataMessages[idx!][TypeDataMessage.progress] = 0.0
  4225. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  4226. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataMessages[idx!]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String })
  4227. if row != nil && section != nil {
  4228. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  4229. }
  4230. } catch {
  4231. }
  4232. }
  4233. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4234. do {
  4235. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4236. "status" : "1"
  4237. ], _where: "message_id = '\(messageId)'")
  4238. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
  4239. "status" : "1"
  4240. ], _where: "message_id = '\(messageId)'")
  4241. } catch {
  4242. rollback.pointee = true
  4243. print("Access database error: \(error.localizedDescription)")
  4244. }
  4245. })
  4246. let message = CoreMessage_TMessageBank.sendMessage(message_id: messageId,
  4247. l_pin: dataMessages[indexPath!.row][TypeDataMessage.l_pin] as? String ?? "",
  4248. message_scope_id: dataMessages[indexPath!.row][TypeDataMessage.message_scope_id] as? String ?? "",
  4249. status: "1",
  4250. message_text: dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "",
  4251. credential: dataMessages[indexPath!.row][TypeDataMessage.credential] as? String ?? "",
  4252. attachment_flag: dataMessages[indexPath!.row][TypeDataMessage.attachment_flag] as? String ?? "",
  4253. ex_blog_id: dataMessages[indexPath!.row][TypeDataMessage.blog_id] as? String ?? "",
  4254. message_large_text: "",
  4255. ex_format: "",
  4256. image_id: dataMessages[indexPath!.row][TypeDataMessage.image_id] as? String ?? "",
  4257. audio_id: dataMessages[indexPath!.row][TypeDataMessage.audio_id] as? String ?? "",
  4258. video_id: dataMessages[indexPath!.row][TypeDataMessage.video_id] as? String ?? "",
  4259. file_id: dataMessages[indexPath!.row][TypeDataMessage.file_id] as? String ?? "",
  4260. thumb_id: dataMessages[indexPath!.row][TypeDataMessage.thumb_id] as? String ?? "",
  4261. reff_id: dataMessages[indexPath!.row][TypeDataMessage.reff_id] as? String ?? "",
  4262. read_receipts: dataMessages[indexPath!.row][TypeDataMessage.read_receipts] as? String ?? "",
  4263. chat_id: dataMessages[indexPath!.row][TypeDataMessage.chat_id] as? String ?? "",
  4264. is_call_center: dataMessages[indexPath!.row][TypeDataMessage.is_call_center] as? String ?? "",
  4265. call_center_id: dataMessages[indexPath!.row][TypeDataMessage.call_center_id] as? String ?? "",
  4266. opposite_pin: dataMessages[indexPath!.row][TypeDataMessage.opposite_pin] as? String ?? "")
  4267. Nexilis.addQueueMessage(message: message)
  4268. })
  4269. var children: [UIMenuElement] = [star, reply, forward, copy, delete]
  4270. var isMore = false
  4271. // let copyOption = self.copyOption(indexPath: indexPath!)
  4272. let idMe = User.getMyPin() as String?
  4273. if dataMessages[indexPath!.row]["status"] as? String ?? "" == "0" {
  4274. children = [resend, delete]
  4275. } else if isContactCenter {
  4276. if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  4277. children = [reply]
  4278. }
  4279. else {
  4280. children = [reply, copy]
  4281. }
  4282. } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as? String ?? "" == "1") || dataMessages[indexPath!.row]["message_scope_id"] as? String ?? "" == "18" || dataPerson["f_pin"] == "-999" || dataMessages[indexPath!.row]["credential"] as? String ?? "" == "1" {
  4283. children = [delete]
  4284. } else if (groupImages[dataMessages[indexPath!.row]["message_id"] as? String ?? ""] != nil) {
  4285. forward.title = "Forward All".localized()
  4286. delete.title = "Delete All".localized()
  4287. children = [forward, delete]
  4288. } else if blocking == "1" || blocking == "-1" {
  4289. children = [star, forward, copy ,delete]
  4290. if !(dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty {
  4291. children = [star, forward ,delete]
  4292. }
  4293. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4294. children.insert(info, at: children.count - 1)
  4295. }
  4296. }
  4297. else if !(dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["file_id"] as? String ?? "").isEmpty {
  4298. children = [star, reply, forward ,delete]
  4299. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4300. children.insert(info, at: children.count - 1)
  4301. }
  4302. } else if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  4303. children = [reply, delete]
  4304. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4305. children.insert(info, at: children.count - 1)
  4306. }
  4307. } else {
  4308. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4309. children.insert(info, at: children.count - 1)
  4310. }
  4311. if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty {
  4312. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe && ((dataMessages[indexPath!.row][TypeDataMessage.is_forwarded] as? Int) ?? 0) == 0 {
  4313. let date = Date(milliseconds: Int64(dataMessages[indexPath!.row][TypeDataMessage.server_date] as? String ?? "") ?? 0)
  4314. let pastDate = date.addingTimeInterval(-10 * 60)
  4315. let differenceInSeconds = date.timeIntervalSince(pastDate)
  4316. if abs(differenceInSeconds) <= 15 * 60 {
  4317. children.insert(edit, at: children.count - 1)
  4318. }
  4319. }
  4320. isMore = true
  4321. }
  4322. }
  4323. let mainMenu = UIMenu(title: "", options: [.displayInline],
  4324. children: children)
  4325. var menuForShow = UIMenu(title: "", children: [mainMenu])
  4326. if isMore {
  4327. menuForShow = UIMenu(title: "", children: [mainMenu, more])
  4328. }
  4329. return UIContextMenuConfiguration(identifier: nil,
  4330. previewProvider: nil) { _ in
  4331. return menuForShow
  4332. }
  4333. }
  4334. func showEditMessageView(at indexPath: IndexPath) {
  4335. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  4336. let oldText = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  4337. editVC = UIViewController()
  4338. if let view = editVC.view {
  4339. view.backgroundColor = .clear
  4340. let blurView = UIView()
  4341. let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
  4342. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  4343. blurEffectView.frame = blurView.bounds
  4344. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  4345. blurView.addSubview(blurEffectView)
  4346. blurView.sendSubviewToBack(blurEffectView)
  4347. view.addSubview(blurView)
  4348. blurView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  4349. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissEditVC))
  4350. tapGesture.cancelsTouchesInView = false
  4351. view.addGestureRecognizer(tapGesture)
  4352. editTextView = CustomTextView()
  4353. editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
  4354. editTextView.layer.borderWidth = 1.0
  4355. editTextView.textColor = UIColor.black
  4356. editTextView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  4357. editTextView.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  4358. editTextView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  4359. editTextView.font = UIFont.systemFont(ofSize: 12 + offset())
  4360. editTextView.delegate = self
  4361. editTextView.allowsEditingTextAttributes = true
  4362. editTextView.backgroundColor = .clear
  4363. view.addSubview(editTextView)
  4364. editTextView.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 15, paddingRight: 15)
  4365. constraintBottomeditTextView = editTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  4366. constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
  4367. constraintBottomeditTextView.isActive = true
  4368. constraintHeighteditTextView.isActive = true
  4369. editTextView.text = oldText
  4370. editTextView.becomeFirstResponder()
  4371. buttonSendEdit.setImage(resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.blackDarkMode) : UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal)
  4372. buttonSendEdit.circle()
  4373. buttonSendEdit.isEnabled = true
  4374. buttonSendEdit.actionHandle(controlEvents: .touchUpInside,
  4375. ForAction:{() -> Void in
  4376. let newText = self.editTextView.text ?? ""
  4377. if !newText.isEmpty && newText.trimmingCharacters(in: .whitespacesAndNewlines) != oldText {
  4378. let lastEdited = Int64(Date().currentTimeMillis())
  4379. let message = CoreMessage_TMessageBank.editMessage(message_id: dataMessages[indexPath.row][TypeDataMessage.message_id] as? String ?? "", l_pin: dataMessages[indexPath.row][TypeDataMessage.l_pin] as? String ?? "", message_scope_id: dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String ?? "", status: "1", message_text: newText, credential: dataMessages[indexPath.row][TypeDataMessage.credential] as? String ?? "", attachment_flag: dataMessages[indexPath.row][TypeDataMessage.attachment_flag] as? String ?? "", ex_blog_id: dataMessages[indexPath.row][TypeDataMessage.blog_id] as? String ?? "", message_large_text: "", ex_format: "", image_id: dataMessages[indexPath.row][TypeDataMessage.image_id] as? String ?? "", audio_id: dataMessages[indexPath.row][TypeDataMessage.audio_id] as? String ?? "", video_id: dataMessages[indexPath.row][TypeDataMessage.video_id] as? String ?? "", file_id: dataMessages[indexPath.row][TypeDataMessage.file_id] as? String ?? "", thumb_id: dataMessages[indexPath.row][TypeDataMessage.thumb_id] as? String ?? "", reff_id: dataMessages[indexPath.row][TypeDataMessage.reff_id] as? String ?? "", read_receipts: dataMessages[indexPath.row][TypeDataMessage.read_receipts] as? String ?? "", chat_id: dataMessages[indexPath.row][TypeDataMessage.chat_id] as? String ?? "", is_call_center: dataMessages[indexPath.row][TypeDataMessage.is_call_center] as? String ?? "", call_center_id: dataMessages[indexPath.row][TypeDataMessage.call_center_id] as? String ?? "", opposite_pin: dataMessages[indexPath.row][TypeDataMessage.opposite_pin] as? String ?? "", server_date: dataMessages[indexPath.row][TypeDataMessage.server_date] as? String ?? "", local_time_stamp: dataMessages[indexPath.row][TypeDataMessage.server_date] as? String ?? "", last_edit: lastEdited)
  4380. Nexilis.addQueueMessage(message: message, isEditMessage: true)
  4381. DispatchQueue.global().async {
  4382. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4383. do {
  4384. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4385. "message_text" : newText,
  4386. "last_edited" : lastEdited
  4387. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  4388. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4389. } catch {
  4390. rollback.pointee = true
  4391. print("Access database error: \(error.localizedDescription)")
  4392. }
  4393. })
  4394. }
  4395. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
  4396. if idx != nil{
  4397. self.dataMessages[idx!][TypeDataMessage.message_text] = newText
  4398. self.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
  4399. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  4400. }
  4401. }
  4402. })
  4403. buttonSendEdit.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  4404. view.addSubview(buttonSendEdit)
  4405. buttonSendEdit.anchor(right: view.rightAnchor, paddingRight: 15, width: 40, height: 40)
  4406. constraintBottomSendEditTV = buttonSendEdit.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  4407. constraintBottomSendEditTV.isActive = true
  4408. let viewMessage = UIView()
  4409. view.addSubview(viewMessage)
  4410. viewMessage.translatesAutoresizingMaskIntoConstraints = false
  4411. if (dataMessages[indexPath.row][TypeDataMessage.f_pin] as? String == User.getMyPin()) {
  4412. viewMessage.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 60).isActive = true
  4413. viewMessage.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15).isActive = true
  4414. viewMessage.backgroundColor = .blueBubbleColor
  4415. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  4416. } else {
  4417. viewMessage.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: 60).isActive = true
  4418. viewMessage.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
  4419. viewMessage.backgroundColor = .whiteBubbleColor
  4420. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  4421. }
  4422. viewMessage.bottomAnchor.constraint(equalTo: editTextView.topAnchor, constant: -15).isActive = true
  4423. viewMessage.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
  4424. viewMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  4425. viewMessage.layer.cornerRadius = 10.0
  4426. viewMessage.clipsToBounds = true
  4427. let messageText = UILabel()
  4428. messageText.numberOfLines = 0
  4429. messageText.lineBreakMode = .byWordWrapping
  4430. viewMessage.addSubview(messageText)
  4431. messageText.translatesAutoresizingMaskIntoConstraints = false
  4432. messageText.topAnchor.constraint(equalTo: viewMessage.topAnchor, constant: 15).isActive = true
  4433. messageText.leadingAnchor.constraint(equalTo: viewMessage.leadingAnchor, constant: 15).isActive = true
  4434. messageText.bottomAnchor.constraint(equalTo: viewMessage.bottomAnchor, constant: -15).isActive = true
  4435. messageText.trailingAnchor.constraint(equalTo: viewMessage.trailingAnchor, constant: -15).isActive = true
  4436. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  4437. messageText.font = .systemFont(ofSize: 12 + offset())
  4438. messageText.text = oldText
  4439. }
  4440. editVC.modalTransitionStyle = .crossDissolve
  4441. editVC.modalPresentationStyle = .overFullScreen
  4442. self.present(editVC, animated: true, completion: {
  4443. self.constraintHeighteditTextView.constant = self.editTextView.contentSize.height
  4444. if self.constraintHeighteditTextView.constant > 95 {
  4445. self.constraintHeighteditTextView.constant = 95.0
  4446. }
  4447. })
  4448. }
  4449. @objc func dismissEditVC() {
  4450. self.isEditingMessage = false
  4451. editVC.dismiss(animated: true)
  4452. }
  4453. @objc func cancelAction() {
  4454. DispatchQueue.main.async {
  4455. if self.copySession {
  4456. self.copySession = false
  4457. } else if self.forwardSession {
  4458. self.forwardSession = false
  4459. } else if self.deleteSession {
  4460. self.deleteSession = false
  4461. } else if self.isSearching {
  4462. self.countMatchesSearch = 0
  4463. self.isSearching = false
  4464. }
  4465. if self.viewTextfield.isHidden {
  4466. self.viewTextfield.isHidden = false
  4467. }
  4468. if self.viewAttachment.isHidden {
  4469. self.viewAttachment.isHidden = false
  4470. }
  4471. if self.containerAction.isHidden {
  4472. self.containerAction.isHidden = false
  4473. }
  4474. if self.viewButton.isHidden {
  4475. self.viewButton.isHidden = false
  4476. }
  4477. if self.constraintBottomTableViewWithTextfield.constant == -60.0 {
  4478. self.constraintBottomTableViewWithTextfield.constant = self.constraintBottomTableViewWithTextfield.constant + 70
  4479. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  4480. if (self.currentIndexpath != nil) {
  4481. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: true)
  4482. } else {
  4483. self.tableChatView.scrollToBottom()
  4484. }
  4485. })
  4486. }
  4487. let data = self.dataMessages.filter({ $0["isSelected"] as? Bool == true })
  4488. for i in 0..<data.count {
  4489. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data[i]["message_id"] as? String})
  4490. if idx != nil{
  4491. self.dataMessages[idx!]["isSelected"] = false
  4492. }
  4493. }
  4494. self.tableChatView.reloadData()
  4495. self.setRightButtonItem()
  4496. self.changeAppBar()
  4497. if self.isContactCenter || self.fromNotification {
  4498. let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(self.didTapExit))
  4499. self.navigationItem.leftBarButtonItem = backButton
  4500. }
  4501. self.containerMultpileSelectSession.removeFromSuperview()
  4502. self.checkNewMessage(tableView: self.tableChatView)
  4503. }
  4504. }
  4505. private func addMultipleSelectSession() {
  4506. viewTextfield.isHidden = true
  4507. viewAttachment.isHidden = true
  4508. containerAction.isHidden = true
  4509. viewButton.isHidden = true
  4510. constraintBottomTableViewWithTextfield.constant = constraintBottomTableViewWithTextfield.constant - 70
  4511. view.addSubview(containerMultpileSelectSession)
  4512. containerMultpileSelectSession.translatesAutoresizingMaskIntoConstraints = false
  4513. constraintBottomContainerMultpileSelectSession = containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
  4514. NSLayoutConstraint.activate([
  4515. containerMultpileSelectSession.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  4516. containerMultpileSelectSession.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  4517. constraintBottomContainerMultpileSelectSession,
  4518. containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 50)
  4519. ])
  4520. containerMultpileSelectSession.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  4521. addSubviewMultipleSession()
  4522. }
  4523. private func addSubviewMultipleSession() {
  4524. let container = UIView()
  4525. containerMultpileSelectSession.addSubview(container)
  4526. container.translatesAutoresizingMaskIntoConstraints = false
  4527. NSLayoutConstraint.activate([
  4528. container.leadingAnchor.constraint(equalTo: containerMultpileSelectSession.leadingAnchor),
  4529. container.trailingAnchor.constraint(equalTo:containerMultpileSelectSession.trailingAnchor),
  4530. container.bottomAnchor.constraint(equalTo: containerMultpileSelectSession.bottomAnchor),
  4531. container.heightAnchor.constraint(equalToConstant: 50)
  4532. ])
  4533. container.layer.shadowOpacity = 0.7
  4534. container.layer.shadowOffset = CGSize(width: 3, height: 3)
  4535. container.layer.shadowRadius = 3.0
  4536. container.layer.shadowColor = UIColor.black.cgColor
  4537. container.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  4538. if !isSearching {
  4539. let title = UILabel()
  4540. container.addSubview(title)
  4541. title.translatesAutoresizingMaskIntoConstraints = false
  4542. NSLayoutConstraint.activate([
  4543. title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  4544. title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4545. ])
  4546. let countSelected = dataMessages.filter({ $0["isSelected"] as? Bool == true }).count
  4547. title.text = "\(countSelected) " + "Selected".localized()
  4548. title.textColor = .mainColor
  4549. title.font = UIFont.systemFont(ofSize: 15.0).bold
  4550. let button = UIImageView()
  4551. container.addSubview(button)
  4552. button.translatesAutoresizingMaskIntoConstraints = false
  4553. NSLayoutConstraint.activate([
  4554. button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
  4555. button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4556. button.widthAnchor.constraint(equalToConstant: 30),
  4557. button.heightAnchor.constraint(equalToConstant: 30),
  4558. ])
  4559. if copySession {
  4560. button.image = UIImage(systemName: "doc.on.doc")
  4561. if countSelected == 0 {
  4562. button.tintColor = .gray
  4563. } else {
  4564. button.tintColor = .mainColor
  4565. }
  4566. } else if forwardSession {
  4567. button.image = UIImage(systemName: "arrowshape.turn.up.right")
  4568. if countSelected == 0 {
  4569. button.tintColor = .gray
  4570. } else {
  4571. button.tintColor = .mainColor
  4572. }
  4573. } else if deleteSession {
  4574. button.image = UIImage(systemName: "trash")
  4575. if countSelected == 0 {
  4576. button.tintColor = .gray
  4577. } else {
  4578. button.tintColor = .red
  4579. }
  4580. }
  4581. let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
  4582. button.isUserInteractionEnabled = true
  4583. button.addGestureRecognizer(buttonGesture)
  4584. let selectedMessage = dataMessages.filter({ $0["isSelected"] as? Bool == true })
  4585. if selectedMessage.count > 0 {
  4586. for i in 0..<selectedMessage.count {
  4587. if let isGroupingImages = groupImages[selectedMessage[i]["message_id"] as? String ?? ""] {
  4588. title.text = "\(countSelected + (isGroupingImages.count - 1)) " + "Selected".localized()
  4589. }
  4590. }
  4591. }
  4592. } else {
  4593. buttonUp = UIButton()
  4594. container.addSubview(buttonUp)
  4595. buttonUp.translatesAutoresizingMaskIntoConstraints = false
  4596. NSLayoutConstraint.activate([
  4597. buttonUp.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10),
  4598. buttonUp.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4599. buttonUp.widthAnchor.constraint(equalToConstant: 30),
  4600. buttonUp.heightAnchor.constraint(equalToConstant: 30),
  4601. ])
  4602. buttonUp.addTarget(self, action: #selector(upSearchText), for: .touchUpInside)
  4603. buttonDown = UIButton()
  4604. container.addSubview(buttonDown)
  4605. buttonDown.translatesAutoresizingMaskIntoConstraints = false
  4606. NSLayoutConstraint.activate([
  4607. buttonDown.leadingAnchor.constraint(equalTo: buttonUp.trailingAnchor, constant: 15),
  4608. buttonDown.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4609. buttonDown.widthAnchor.constraint(equalToConstant: 30),
  4610. buttonDown.heightAnchor.constraint(equalToConstant: 30),
  4611. ])
  4612. buttonDown.addTarget(self, action: #selector(downSearchText), for: .touchUpInside)
  4613. buttonUp.setImage(UIImage(systemName: "chevron.up"), for: .normal)
  4614. buttonUp.tintColor = .gray
  4615. buttonDown.setImage(UIImage(systemName: "chevron.down"), for: .normal)
  4616. buttonDown.tintColor = .gray
  4617. titleSearchMatches = UILabel()
  4618. container.addSubview(titleSearchMatches)
  4619. titleSearchMatches.translatesAutoresizingMaskIntoConstraints = false
  4620. NSLayoutConstraint.activate([
  4621. titleSearchMatches.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  4622. titleSearchMatches.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4623. ])
  4624. titleSearchMatches.textColor = .mainColor
  4625. titleSearchMatches.font = UIFont.systemFont(ofSize: 15.0).bold
  4626. titleSearchMatches.isHidden = true
  4627. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  4628. self.searchBar.becomeFirstResponder()
  4629. })
  4630. }
  4631. }
  4632. @objc func upSearchText() {
  4633. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch + 1)
  4634. }
  4635. @objc func downSearchText() {
  4636. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch - 1)
  4637. }
  4638. @objc func sessionAction() {
  4639. if copySession {
  4640. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as? Bool == true })
  4641. let countSelected = dataMessages.count
  4642. if countSelected == 0 {
  4643. return
  4644. }
  4645. var text = ""
  4646. for i in 0..<countSelected {
  4647. let stringDate = (dataMessages[i]["server_date"] as? String ?? "")
  4648. let date = Date(milliseconds: Int64(stringDate)!)
  4649. let formatterDate = DateFormatter()
  4650. let formatterTime = DateFormatter()
  4651. formatterDate.dateFormat = "dd/MM/yy"
  4652. formatterDate.locale = NSLocale(localeIdentifier: "id") as Locale?
  4653. formatterTime.dateFormat = "HH:mm"
  4654. formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
  4655. let dataProfile = getDataProfile(message_id: dataMessages[i]["message_id"] as? String ?? "")
  4656. let textCopied = (dataMessages[i]["message_text"] as? String ?? "").richText(isEditing: true)
  4657. if text.isEmpty {
  4658. text = "*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  4659. } else {
  4660. text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  4661. }
  4662. }
  4663. text = text + "\n\n\nchat " + "Powered by Nexilis".localized()
  4664. DispatchQueue.main.async {
  4665. UIPasteboard.general.string = text
  4666. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  4667. }
  4668. cancelAction()
  4669. } else if forwardSession {
  4670. var dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  4671. let countSelected = dataMessages.count
  4672. if countSelected == 0 {
  4673. return
  4674. }
  4675. for i in 0..<countSelected {
  4676. if groupImages[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  4677. var tempData = dataMessages
  4678. tempData.remove(at: 0)
  4679. let dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  4680. tempData.insert(contentsOf: dataMessageInGrouping, at: i)
  4681. dataMessages = tempData
  4682. }
  4683. }
  4684. contactChatNav.modalPresentationStyle = .custom
  4685. contactChatNav.navigationBar.tintColor = .white
  4686. contactChatNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  4687. contactChatNav.navigationBar.isTranslucent = false
  4688. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  4689. contactChatNav.navigationBar.titleTextAttributes = textAttributes
  4690. contactChatNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  4691. if let controller = contactChatNav.viewControllers.first as? ContactChatViewController {
  4692. controller.isChooser = { [weak self] scope, pin in
  4693. if scope == "3" {
  4694. let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
  4695. editorPersonalVC.unique_l_pin = pin
  4696. editorPersonalVC.dataMessageForward = dataMessages
  4697. self?.navigationController?.replaceAllViewController(with: editorPersonalVC, animated: true)
  4698. } else {
  4699. let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
  4700. editorGroupVC.unique_l_pin = pin
  4701. editorGroupVC.dataMessageForward = dataMessages
  4702. self?.navigationController?.replaceAllViewController(with: editorGroupVC, animated: true)
  4703. }
  4704. }
  4705. }
  4706. self.present(contactChatNav, animated: true, completion: nil)
  4707. } else if deleteSession {
  4708. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  4709. var countSelected = dataMessages.count
  4710. if countSelected == 0 {
  4711. return
  4712. }
  4713. for i in 0..<countSelected {
  4714. if let isGroupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  4715. countSelected += (isGroupingImages.count - 1)
  4716. }
  4717. }
  4718. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  4719. if let action = self.actionDelete(for: "me", title: "Delete".localized() + " \(countSelected) " + "For Me".localized(), dataMessages: dataMessages) {
  4720. alertController.addAction(action)
  4721. }
  4722. let idMe = User.getMyPin() as String?
  4723. let dataFilterFpin = dataMessages.filter({ $0["l_pin"] as? String == idMe})
  4724. let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1" || $0["lock"] as? String == "2" })
  4725. // let statusDataRead = dataMessages.filter({ Int($0["status"] as? String ?? "")! >= 4})
  4726. let statusFailed = dataMessages.filter({ Int($0["status"] as? String ?? "")! == 0})
  4727. if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusFailed.count == 0 {
  4728. if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
  4729. alertController.addAction(action)
  4730. }
  4731. }
  4732. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  4733. self.present(alertController, animated: true)
  4734. }
  4735. }
  4736. private func getDataProfile(message_id: String) -> [String: String]{
  4737. var data: [String: String] = [:]
  4738. Database().database?.inTransaction({ fmdb, rollback in
  4739. if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
  4740. data["name"] = c.string(forColumnIndex: 0)!
  4741. c.close()
  4742. } else {
  4743. data["name"] = "Unknown".localized()
  4744. data["image_id"] = ""
  4745. }
  4746. })
  4747. return data
  4748. }
  4749. private func deleteMessage(l_pin: String, message_id: String, scope: String, type: String, chat: String) {
  4750. let tmessage = CoreMessage_TMessageBank.deleteMessage(l_pin: l_pin, messageId: message_id, scope: scope, type: type, chat: chat)
  4751. Nexilis.deleteQueueMessage(message: tmessage)
  4752. }
  4753. private func queryMessageReply(message_id: String) -> [String: Any?] {
  4754. var dataQuery: [String: Any] = [:]
  4755. Database().database?.inTransaction({ fmdb, rollback in
  4756. if let c = Database().getRecords(fmdb: fmdb, query: "SELECT message_id, f_pin, message_text, attachment_flag, thumb_id, image_id, video_id, file_id FROM MESSAGE where message_id='\(message_id)'"), c.next() {
  4757. dataQuery["message_id"] = c.string(forColumnIndex: 0) ?? ""
  4758. dataQuery["f_pin"] = c.string(forColumnIndex: 1) ?? ""
  4759. dataQuery["message_text"] = c.string(forColumnIndex: 2) ?? ""
  4760. dataQuery["attachment_flag"] = c.string(forColumnIndex: 3) ?? ""
  4761. dataQuery["thumb_id"] = c.string(forColumnIndex: 4) ?? ""
  4762. dataQuery["image_id"] = c.string(forColumnIndex: 5) ?? ""
  4763. dataQuery["video_id"] = c.string(forColumnIndex: 6) ?? ""
  4764. dataQuery["file_id"] = c.string(forColumnIndex: 7) ?? ""
  4765. c.close()
  4766. }
  4767. })
  4768. return dataQuery
  4769. }
  4770. @objc func segmentedControlValueChanged(_ sender: segmentedControllerObject) {
  4771. switch sender.selectedSegmentIndex {
  4772. case 0:
  4773. sender.navigation.viewControllers[0].children[1].view.isHidden = true
  4774. break;
  4775. case 1:
  4776. sender.navigation.viewControllers[0].children[1].view.isHidden = false
  4777. break;
  4778. default:
  4779. break;
  4780. }
  4781. }
  4782. private func copyOption(indexPath: IndexPath) -> UIMenu {
  4783. var ratingButtonTitles = ["Text".localized(), "Image".localized()]
  4784. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  4785. ratingButtonTitles = ["Image".localized()]
  4786. }
  4787. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  4788. let copyActions = ratingButtonTitles
  4789. .enumerated()
  4790. .map { index, title in
  4791. return UIAction(
  4792. title: title,
  4793. identifier: nil,
  4794. handler: {(_) in
  4795. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  4796. DispatchQueue.main.async {
  4797. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4798. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4799. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4800. if let dirPath = paths.first {
  4801. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  4802. if FileManager.default.fileExists(atPath: imageURL.path) {
  4803. let image = UIImage(contentsOfFile: imageURL.path)
  4804. UIPasteboard.general.image = image
  4805. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4806. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  4807. do {
  4808. if let imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  4809. let image = UIImage(data: imageData)
  4810. UIPasteboard.general.image = image
  4811. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4812. }
  4813. } catch {
  4814. }
  4815. }
  4816. }
  4817. }
  4818. return
  4819. }
  4820. if (index == 0) {
  4821. DispatchQueue.main.async {
  4822. UIPasteboard.general.string = dataMessages[indexPath.row]["message_text"] as? String
  4823. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  4824. }
  4825. } else {
  4826. DispatchQueue.main.async {
  4827. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4828. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4829. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4830. if let dirPath = paths.first {
  4831. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  4832. if FileManager.default.fileExists(atPath: imageURL.path) {
  4833. let image = UIImage(contentsOfFile: imageURL.path)
  4834. UIPasteboard.general.image = image
  4835. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4836. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  4837. do {
  4838. if let imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  4839. let image = UIImage(data: imageData)
  4840. UIPasteboard.general.image = image
  4841. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4842. }
  4843. } catch {
  4844. }
  4845. }
  4846. }
  4847. }
  4848. }
  4849. self.dismissKeyboard()
  4850. })
  4851. }
  4852. return UIMenu(
  4853. title: "Copy".localized(),
  4854. image: UIImage(systemName: "doc.on.doc.fill"),
  4855. children: copyActions)
  4856. }
  4857. private func actionDelete(for type: String, title: String, dataMessages: [[String: Any?]]) -> UIAlertAction? {
  4858. return UIAlertAction(title: title, style: .destructive) { [unowned self] _ in
  4859. for i in 0..<dataMessages.count {
  4860. if (type == "me") {
  4861. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  4862. for i in 0..<groupingImages.count {
  4863. self.deleteMessage(l_pin: groupingImages[i].lPin, message_id: groupingImages[i].messageId, scope: "3", type: "1", chat: "")
  4864. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId })
  4865. if idx != nil {
  4866. self.dataMessages.remove(at: idx!)
  4867. if (idx == self.dataMessages.count - 1) {
  4868. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4869. }
  4870. for i in 0..<dataDates.count {
  4871. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  4872. dataDates.remove(at: i)
  4873. }
  4874. }
  4875. }
  4876. }
  4877. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  4878. } else {
  4879. self.deleteMessage(l_pin: dataMessages[i]["l_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: "3", type: "1", chat: "")
  4880. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  4881. if idx != nil {
  4882. self.dataMessages.remove(at: idx!)
  4883. if (idx == self.dataMessages.count - 1) {
  4884. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4885. }
  4886. for i in 0..<dataDates.count {
  4887. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  4888. dataDates.remove(at: i)
  4889. }
  4890. }
  4891. }
  4892. }
  4893. } else {
  4894. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  4895. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  4896. imageView.tintColor = .white
  4897. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  4898. banner.show()
  4899. } else {
  4900. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  4901. for i in 0..<groupingImages.count {
  4902. self.deleteMessage(l_pin: groupingImages[i].lPin, message_id: groupingImages[i].messageId, scope: "3", type: "2", chat: "")
  4903. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId})
  4904. if idx != nil {
  4905. self.dataMessages[idx!]["lock"] = "1"
  4906. self.dataMessages[idx!]["attachment_flag"] = "0"
  4907. self.dataMessages[idx!]["reff_id"] = ""
  4908. }
  4909. }
  4910. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[0].messageId}) {
  4911. var dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  4912. dataMessageInGrouping.remove(at: 0)
  4913. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx+1)
  4914. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  4915. }
  4916. } else {
  4917. self.deleteMessage(l_pin: dataMessages[i]["l_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: "3", type: "2", chat: "")
  4918. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  4919. if idx != nil {
  4920. self.dataMessages[idx!]["lock"] = "1"
  4921. self.dataMessages[idx!]["attachment_flag"] = "0"
  4922. self.dataMessages[idx!]["reff_id"] = ""
  4923. }
  4924. }
  4925. }
  4926. }
  4927. if self.listTimerCredential[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  4928. self.listTimerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  4929. self.timerCredential[dataMessages[i]["message_id"] as? String ?? ""]?.invalidate()
  4930. self.timerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  4931. }
  4932. }
  4933. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4934. cancelAction()
  4935. }
  4936. }
  4937. private func updateProfile() {
  4938. let idMe = User.getMyPin() as String?
  4939. DispatchQueue.global().async {
  4940. let message = CoreMessage_TMessageBank.getBatchBuddiesInfos(p_f_pin: idMe!, last_update: 0)
  4941. let _ = Nexilis.write(message: message)
  4942. }
  4943. }
  4944. private func generateQRCode(from string: String) -> UIImage? {
  4945. let data = string.data(using: String.Encoding.ascii)
  4946. if let filter = CIFilter(name: "CIQRCodeGenerator") {
  4947. filter.setValue(data, forKey: "inputMessage")
  4948. let transform = CGAffineTransform(scaleX: 3, y: 3)
  4949. if let output = filter.outputImage?.transformed(by: transform) {
  4950. return UIImage(ciImage: output)
  4951. }
  4952. }
  4953. return nil
  4954. }
  4955. @objc func deleteReplyView() {
  4956. if self.containerPreviewReply.isDescendant(of: self.viewTextfield) {
  4957. self.containerPreviewReply.subviews.forEach { $0.removeFromSuperview() }
  4958. self.containerPreviewReply.removeConstraints(self.containerPreviewReply.constraints)
  4959. self.containerPreviewReply.removeFromSuperview()
  4960. self.reffId = nil
  4961. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  4962. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50
  4963. }, completion: nil)
  4964. }
  4965. }
  4966. @objc func removeLinkPreviewUntilEmptyTextView() {
  4967. isAlwaysHideLinkPreview = true
  4968. deleteLinkPreview()
  4969. }
  4970. @objc func deleteLinkPreview() {
  4971. if self.containerLink.isDescendant(of: self.viewTextfield) {
  4972. self.containerLink.subviews.forEach { $0.removeFromSuperview() }
  4973. self.containerLink.removeConstraints(self.containerLink.constraints)
  4974. self.containerLink.removeFromSuperview()
  4975. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  4976. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
  4977. }, completion: nil)
  4978. self.showingLink = ""
  4979. }
  4980. if self.reffId != nil {
  4981. self.bottomAnchorPreviewReply.isActive = false
  4982. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  4983. self.bottomAnchorPreviewReply.isActive = true
  4984. }
  4985. }
  4986. }
  4987. //ECL
  4988. extension EditorPersonal: UICollectionViewDelegate, UICollectionViewDataSource {
  4989. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  4990. return 76
  4991. }
  4992. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  4993. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellSticker", for: indexPath)
  4994. if (cell.contentView.subviews.count > 0) {
  4995. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  4996. }
  4997. let imageSticker = UIImageView()
  4998. cell.contentView.addSubview(imageSticker)
  4999. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  5000. NSLayoutConstraint.activate([
  5001. imageSticker.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
  5002. imageSticker.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
  5003. imageSticker.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor),
  5004. imageSticker.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor)
  5005. ])
  5006. var imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5007. if imageStickerBundle == nil {
  5008. imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  5009. }
  5010. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  5011. return cell
  5012. }
  5013. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  5014. sendChat(message_text: "sticker/\(stickers[indexPath.row])", attachment_flag: "11", viewController: self)
  5015. constraintBottomAttachment.constant = 0.0
  5016. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  5017. self.viewSticker.removeFromSuperview()
  5018. }
  5019. }
  5020. //ETB
  5021. extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPlayerDelegate {
  5022. // public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  5023. // checkNewMessage(tableView: tableView)
  5024. // }
  5025. public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  5026. if tableChatView.alpha != 1 {
  5027. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  5028. UIView.animate(withDuration: 0.5, animations: {
  5029. self.tableChatView.alpha = 1.0
  5030. })
  5031. })
  5032. }
  5033. }
  5034. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  5035. lastY = scrollView.contentOffset.y
  5036. DispatchQueue.main.async { [self] in
  5037. if tableChatView.alpha != 1 {
  5038. return
  5039. }
  5040. checkNewMessage(tableView: self.tableChatView)
  5041. }
  5042. }
  5043. public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  5044. if isContactCenter && indexPath.row == 0 && isRequestContactCenter {
  5045. return
  5046. }
  5047. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section] })
  5048. if copySession || forwardSession || deleteSession {
  5049. if (dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" != "0" || dataMessages[indexPath.row]["lock"] as? String == "1") && !forwardSession && !deleteSession {
  5050. return
  5051. }
  5052. if !(dataMessages[indexPath.row]["image_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["video_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["file_id"] as? String ?? "").isEmpty {
  5053. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  5054. if file.isEmpty {
  5055. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  5056. if file.isEmpty {
  5057. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  5058. }
  5059. }
  5060. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5061. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5062. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5063. if let dirPath = paths.first {
  5064. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  5065. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  5066. return
  5067. }
  5068. }
  5069. }
  5070. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String})
  5071. if idx != nil {
  5072. self.dataMessages[idx!]["isSelected"] = !(self.dataMessages[idx!]["isSelected"] as! Bool)
  5073. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  5074. }
  5075. containerMultpileSelectSession.subviews.forEach({ $0.removeFromSuperview() })
  5076. addSubviewMultipleSession()
  5077. return
  5078. }
  5079. let message = dataMessages[indexPath.row]
  5080. if let attachmentFlag = message["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  5081. if attachmentFlag == "27" || attachmentFlag == "26" {
  5082. let streamingController = (attachmentFlag == "27") ? QmeraCreateStreamingViewController() : CreateSeminarViewController()
  5083. if let messageText = message["message_text"],
  5084. let messageText = messageText as? String,
  5085. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5086. if json["blog"] == nil {
  5087. json["blog"] = message["blog_id"] ?? nil
  5088. }
  5089. switch(attachmentFlag){
  5090. case "27":
  5091. (streamingController as! QmeraCreateStreamingViewController).data = json
  5092. default:
  5093. (streamingController as! CreateSeminarViewController).data = json
  5094. }
  5095. if json["by"] as? String != User.getMyPin() as String? {
  5096. switch(attachmentFlag){
  5097. case "27":
  5098. (streamingController as! QmeraCreateStreamingViewController).isJoin = true
  5099. default:
  5100. (streamingController as! CreateSeminarViewController).isJoin = true
  5101. }
  5102. }
  5103. }
  5104. let streamingNav = CustomNavigationController(rootViewController: streamingController)
  5105. streamingNav.modalPresentationStyle = .custom
  5106. streamingNav.navigationBar.tintColor = .white
  5107. streamingNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5108. streamingNav.navigationBar.isTranslucent = false
  5109. streamingNav.navigationBar.overrideUserInterfaceStyle = .dark
  5110. streamingNav.navigationBar.barStyle = .black
  5111. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  5112. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  5113. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  5114. streamingNav.navigationBar.titleTextAttributes = textAttributes
  5115. streamingNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5116. streamingNav.navigationBar.isTranslucent = false
  5117. navigationController?.present(streamingNav, animated: true, completion: nil)
  5118. } else if attachmentFlag == "25" {
  5119. let conferenceController = CreateSeminarViewController()
  5120. if let messageText = message["message_text"],
  5121. let messageText = messageText as? String,
  5122. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5123. if json["blog"] == nil {
  5124. json["blog"] = message["blog"] ?? nil
  5125. }
  5126. json["participant"] = message["members"]
  5127. let start = json["time"] as? Int64 ?? 0
  5128. json["start"] = String(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))
  5129. conferenceController.data = json
  5130. conferenceController.isJoin = true
  5131. }
  5132. let conferenceNav = CustomNavigationController(rootViewController: conferenceController)
  5133. conferenceNav.modalPresentationStyle = .custom
  5134. conferenceNav.navigationBar.tintColor = .white
  5135. conferenceNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5136. conferenceNav.navigationBar.isTranslucent = false
  5137. conferenceNav.navigationBar.overrideUserInterfaceStyle = .dark
  5138. conferenceNav.navigationBar.barStyle = .black
  5139. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  5140. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  5141. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  5142. conferenceNav.navigationBar.titleTextAttributes = textAttributes
  5143. conferenceNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5144. conferenceNav.navigationBar.isTranslucent = false
  5145. navigationController?.present(conferenceNav, animated: true, completion: nil)
  5146. } else if message["message_scope_id"] as? String == "18" {
  5147. let formView = FormEditor()
  5148. let messageText = message["message_text"] as? String ?? ""
  5149. formView.jsonData = messageText
  5150. formView.dataMessage = message
  5151. formView.dataPerson = self.dataPerson
  5152. formView.modalPresentationStyle = .custom
  5153. formView.modalTransitionStyle = .crossDissolve
  5154. formView.view.backgroundColor = .black.withAlphaComponent(0.2)
  5155. self.present(formView, animated: true, completion: nil)
  5156. }
  5157. }
  5158. }
  5159. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  5160. let containerView = UIView()
  5161. containerView.backgroundColor = .clear
  5162. let dateView = UIView()
  5163. containerView.addSubview(dateView)
  5164. dateView.translatesAutoresizingMaskIntoConstraints = false
  5165. var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
  5166. topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20.0)
  5167. NSLayoutConstraint.activate([
  5168. topAnchor,
  5169. dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20.0),
  5170. dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
  5171. dateView.heightAnchor.constraint(equalToConstant: 30),
  5172. dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  5173. ])
  5174. dateView.backgroundColor = .orangeColor
  5175. dateView.layer.cornerRadius = 15.0
  5176. dateView.clipsToBounds = true
  5177. let labelDate = UILabel()
  5178. dateView.addSubview(labelDate)
  5179. labelDate.translatesAutoresizingMaskIntoConstraints = false
  5180. NSLayoutConstraint.activate([
  5181. labelDate.centerYAnchor.constraint(equalTo: dateView.centerYAnchor),
  5182. labelDate.centerXAnchor.constraint(equalTo: dateView.centerXAnchor),
  5183. labelDate.leadingAnchor.constraint(equalTo: dateView.leadingAnchor, constant: 10),
  5184. labelDate.trailingAnchor.constraint(equalTo: dateView.trailingAnchor, constant: -10),
  5185. ])
  5186. labelDate.textAlignment = .center
  5187. labelDate.textColor = .secondaryColor
  5188. labelDate.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  5189. labelDate.text = dataDates[section]
  5190. if listViewOnSection.count == 0 || listViewOnSection.count - 1 < section {
  5191. listViewOnSection.append(containerView)
  5192. } else {
  5193. listViewOnSection.remove(at: section)
  5194. listViewOnSection.insert(containerView, at: section)
  5195. }
  5196. return containerView
  5197. }
  5198. public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  5199. return 80
  5200. }
  5201. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  5202. let idMe = User.getMyPin() as String?
  5203. let dataMessages = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  5204. let profileMessage = UIImageView()
  5205. let cell = tableView.dequeueReusableCell(withIdentifier: "cellEditorPersonal", for: indexPath as IndexPath)
  5206. cell.contentView.subviews.forEach({ $0.removeConstraints($0.constraints) })
  5207. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  5208. if isContactCenter && isRequestContactCenter && dataMessages[indexPath.row]["category_cc"] != nil {
  5209. cell.backgroundColor = .clear
  5210. cell.selectionStyle = .none
  5211. if dataMessages[indexPath.row]["category_cc"] is [CategoryCC] {
  5212. let category_cc = dataMessages[indexPath.row]["category_cc"] as! [CategoryCC]
  5213. profileMessage.frame.size = CGSize(width: 35, height: 35)
  5214. cell.contentView.addSubview(profileMessage)
  5215. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  5216. profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5217. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5218. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  5219. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  5220. profileMessage.circle()
  5221. profileMessage.clipsToBounds = true
  5222. profileMessage.backgroundColor = .lightGray
  5223. profileMessage.image = UIImage(systemName: "person")
  5224. profileMessage.tintColor = .white
  5225. profileMessage.contentMode = .scaleAspectFit
  5226. getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  5227. profileMessage.image = image
  5228. }
  5229. profileMessage.contentMode = .scaleAspectFill
  5230. let containerMessage = UIView()
  5231. cell.contentView.addSubview(containerMessage)
  5232. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  5233. containerMessage.topAnchor.constraint(equalTo: profileMessage.bottomAnchor).isActive = true
  5234. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 5).isActive = true
  5235. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
  5236. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5237. // containerMessage.backgroundColor = .grayColor
  5238. // containerMessage.layer.cornerRadius = 10.0
  5239. // containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  5240. // containerMessage.clipsToBounds = true
  5241. // let timeMessage = UILabel()
  5242. // cell.contentView.addSubview(timeMessage)
  5243. // timeMessage.translatesAutoresizingMaskIntoConstraints = false
  5244. // timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  5245. let messageText = UILabel()
  5246. containerMessage.addSubview(messageText)
  5247. messageText.translatesAutoresizingMaskIntoConstraints = false
  5248. messageText.numberOfLines = 0
  5249. messageText.lineBreakMode = .byWordWrapping
  5250. containerMessage.addSubview(messageText)
  5251. messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 5).isActive = true
  5252. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5253. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5254. if category_cc[0].id.contains("level0_") || dataMessages[indexPath.row]["attachment_flag"] != nil && dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "503" {
  5255. messageText.text = "Welcome to".localized() + " " + dataPerson["name"]!! + " " + "Contact Center".localized()
  5256. + "\n" + "Please choose your desired communication method...".localized()
  5257. } else if category_cc[0].id.contains("level1_") {
  5258. messageText.text = "Please select your Consultation Topic:".localized()
  5259. } else if !category_cc[0].id.contains("level1_") && dataMessages[indexPath.row]["attachment_flag"] == nil {
  5260. messageText.text = "Please select the type of topic that you chosen".localized()
  5261. } else if dataMessages[indexPath.row]["attachment_flag"] != nil && dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "502" {
  5262. messageText.text = "Please select the information option:".localized()
  5263. } else {
  5264. messageText.text = "Sorry, currently all our representatives are busy helping other customers. Do you want us to get back to you as soon as one of them is available?".localized()
  5265. }
  5266. messageText.font = UIFont.systemFont(ofSize: 14 + offset(), weight: .medium)
  5267. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5268. // let date = Date()
  5269. // let formatter = DateFormatter()
  5270. // formatter.dateFormat = "HH:mm"
  5271. // formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  5272. // timeMessage.text = formatter.string(from: date as Date)
  5273. // timeMessage.font = UIFont.systemFont(ofSize: 10, weight: .medium)
  5274. // timeMessage.textColor = .lightGray
  5275. let containerButton = UIView()
  5276. cell.contentView.addSubview(containerButton)
  5277. containerButton.translatesAutoresizingMaskIntoConstraints = false
  5278. containerButton.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  5279. containerButton.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  5280. containerButton.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5281. containerButton.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width * 0.9).isActive = true
  5282. containerButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 55).isActive = true
  5283. containerButton.backgroundColor = .clear
  5284. // timeMessage.bottomAnchor.constraint(equalTo:containerButton.topAnchor, constant: -5).isActive = true
  5285. for i in 0..<category_cc.count {
  5286. let buttonChat = UIButton(type: .custom)
  5287. containerButton.addSubview(buttonChat)
  5288. buttonChat.translatesAutoresizingMaskIntoConstraints = false
  5289. buttonChat.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width * 0.9 / 2 - 5).isActive = true
  5290. buttonChat.heightAnchor.constraint(greaterThanOrEqualToConstant: 55).isActive = true
  5291. if i % 2 == 0 {
  5292. if i / 2 + 1 == 1 {
  5293. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: 5).isActive = true
  5294. } else {
  5295. var constantTop = (i / 2 - 1) * 50
  5296. if constantTop == 0 {
  5297. constantTop = 55
  5298. } else {
  5299. constantTop = constantTop + 55
  5300. }
  5301. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: CGFloat(constantTop)).isActive = true
  5302. }
  5303. if i == category_cc.count - 1 {
  5304. buttonChat.bottomAnchor.constraint(equalTo: containerButton.bottomAnchor, constant: -5).isActive = true
  5305. }
  5306. buttonChat.leadingAnchor.constraint(equalTo: containerButton.leadingAnchor, constant: 5).isActive = true
  5307. } else {
  5308. let newi = i - 1
  5309. if newi / 2 + 1 == 1 {
  5310. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: 5).isActive = true
  5311. } else {
  5312. var constantTop = (newi / 2 - 1) * 50
  5313. if constantTop == 0 {
  5314. constantTop = 55
  5315. } else {
  5316. constantTop = constantTop + 55
  5317. }
  5318. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: CGFloat(constantTop)).isActive = true
  5319. }
  5320. if i == category_cc.count - 1 {
  5321. buttonChat.bottomAnchor.constraint(equalTo: containerButton.bottomAnchor, constant: -5).isActive = true
  5322. }
  5323. buttonChat.trailingAnchor.constraint(equalTo: containerButton.trailingAnchor, constant: -5).isActive = true
  5324. }
  5325. if category_cc[i].isActive {
  5326. buttonChat.backgroundColor = .orangeBNI
  5327. }
  5328. var nameImage = "pb_cc_bg_messaging"
  5329. if i == 1 {
  5330. nameImage = "pb_cc_bg_sms"
  5331. } else if i == 2 {
  5332. nameImage = "pb_cc_bg_voip"
  5333. } else if i == 3 {
  5334. nameImage = "pb_cc_bg_email"
  5335. } else if i == 4 {
  5336. nameImage = "pb_cc_bg_videocall"
  5337. } else if i == 5 {
  5338. nameImage = "pb_cc_bg_gsmcall"
  5339. } else if i == 6 {
  5340. nameImage = "pb_cc_bg_gptchatbot"
  5341. } else if i == 7 {
  5342. nameImage = "pb_cc_bg_whatsapp"
  5343. }
  5344. buttonChat.setImage(resizeImage(image: UIImage(named: nameImage, in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: self.view!.frame.size.width * 0.9 / 2 - 5, height: 55)), for: .normal)
  5345. // buttonChat.setTitle(category_cc[i].service_name.localized(), for: .normal)
  5346. // buttonChat.setTitleColor(.black, for: .normal)
  5347. // buttonChat.setImage(resizeImage(image: UIImage(named: "pb_gpt_bot", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)), for: .normal)
  5348. // buttonChat.contentHorizontalAlignment = .left
  5349. // buttonChat.imageEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) // Adjust left inset for the image
  5350. // buttonChat.titleEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) // Adjust left inset for the title
  5351. // buttonChat.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
  5352. // buttonChat.titleLabel?.numberOfLines = 0
  5353. // buttonChat.layer.borderWidth = 2
  5354. // buttonChat.layer.borderColor = UIColor.white.cgColor
  5355. // buttonChat.backgroundColor = .grayColor
  5356. // buttonChat.layer.cornerRadius = 8.0
  5357. // buttonChat.clipsToBounds = true
  5358. buttonChat.restorationIdentifier = "\(category_cc[i].id),\(category_cc[i].service_id)"
  5359. if dataMessages[indexPath.row]["attachment_flag"] != nil {
  5360. buttonChat.tag = Int(dataMessages[indexPath.row]["attachment_flag"] as? String ?? "")!
  5361. }
  5362. buttonChat.addTarget(self, action: #selector(ccAction(sender:)), for: .touchUpInside)
  5363. }
  5364. } else {
  5365. let messageWait = UILabel()
  5366. cell.contentView.addSubview(messageWait)
  5367. messageWait.translatesAutoresizingMaskIntoConstraints = false
  5368. messageWait.topAnchor.constraint(equalTo: cell.contentView.topAnchor).isActive = true
  5369. messageWait.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true
  5370. messageWait.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor, constant: 10).isActive = true
  5371. messageWait.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor, constant: -10).isActive = true
  5372. messageWait.text = dataMessages[indexPath.row]["category_cc"] as? String ?? dataMessages[indexPath.row]["message_text"] as? String ?? ""
  5373. messageWait.numberOfLines = 0
  5374. messageWait.font = UIFont.systemFont(ofSize: 12 + offset())
  5375. messageWait.textColor = .gray
  5376. messageWait.textAlignment = .center
  5377. }
  5378. return cell
  5379. }
  5380. let messageIdChat = (dataMessages[indexPath.row]["message_id"] as? String) ?? ""
  5381. let thumbChat = (dataMessages[indexPath.row]["thumb_id"] as? String) ?? ""
  5382. let imageChat = (dataMessages[indexPath.row]["image_id"] as? String) ?? ""
  5383. let videoChat = (dataMessages[indexPath.row]["video_id"] as? String) ?? ""
  5384. let fileChat = (dataMessages[indexPath.row]["file_id"] as? String) ?? ""
  5385. let reffChat = (dataMessages[indexPath.row]["reff_id"] as? String) ?? ""
  5386. let audioChat = (dataMessages[indexPath.row]["audio_id"] as? String) ?? ""
  5387. let gifChat = (dataMessages[indexPath.row]["gif_id"] as? String) ?? ""
  5388. let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as? String ?? "")]
  5389. cell.backgroundColor = .clear
  5390. cell.selectionStyle = .none
  5391. let nameSender = UILabel()
  5392. if isContactCenter {
  5393. profileMessage.frame.size = CGSize(width: 35, height: 35)
  5394. cell.contentView.addSubview(profileMessage)
  5395. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  5396. profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5397. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5398. profileMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
  5399. } else {
  5400. if copySession || forwardSession || deleteSession {
  5401. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
  5402. } else {
  5403. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5404. }
  5405. }
  5406. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  5407. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  5408. profileMessage.circle()
  5409. profileMessage.clipsToBounds = true
  5410. profileMessage.backgroundColor = .lightGray
  5411. profileMessage.image = UIImage(systemName: "person")
  5412. profileMessage.tintColor = .white
  5413. profileMessage.contentMode = .scaleAspectFit
  5414. let user = User.getData(pin: dataMessages[indexPath.row]["f_pin"] as? String)
  5415. getImage(name: user?.thumb ?? "", placeholderImage: UIImage(systemName: "person.circle.fill")!, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in
  5416. profileMessage.image = image
  5417. }
  5418. profileMessage.contentMode = .scaleAspectFill
  5419. cell.contentView.addSubview(nameSender)
  5420. nameSender.translatesAutoresizingMaskIntoConstraints = false
  5421. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  5422. nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
  5423. } else {
  5424. nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5425. }
  5426. nameSender.font = UIFont.systemFont(ofSize: 12 + offset(), weight: UIFont.Weight(800))
  5427. nameSender.text = user?.fullName ?? ""
  5428. nameSender.textAlignment = .right
  5429. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5430. nameSender.trailingAnchor.constraint(equalTo:profileMessage.leadingAnchor, constant: -5).isActive = true
  5431. nameSender.textColor = .systemBlue
  5432. } else {
  5433. nameSender.leadingAnchor.constraint(equalTo:profileMessage.trailingAnchor, constant: 5).isActive = true
  5434. nameSender.textColor = .orangeColor
  5435. }
  5436. }
  5437. let containerMessage = UIView()
  5438. cell.contentView.addSubview(containerMessage)
  5439. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  5440. let timeMessage = UILabel()
  5441. cell.contentView.addSubview(timeMessage)
  5442. timeMessage.translatesAutoresizingMaskIntoConstraints = false
  5443. if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5444. timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40 - 20).isActive = true
  5445. } else {
  5446. timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5 - 20).isActive = true
  5447. }
  5448. let statusMessage = UIImageView()
  5449. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "0" && dataMessages[indexPath.row]["lock"] as? String != "1") || forwardSession || deleteSession {
  5450. var showSelectedImage = true
  5451. if (!imageChat.isEmpty || !videoChat.isEmpty || !fileChat.isEmpty) && forwardSession {
  5452. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  5453. if file.isEmpty {
  5454. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  5455. if file.isEmpty {
  5456. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  5457. }
  5458. }
  5459. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5460. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5461. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5462. if let dirPath = paths.first {
  5463. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  5464. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  5465. showSelectedImage = false
  5466. }
  5467. }
  5468. }
  5469. if showSelectedImage {
  5470. let selectedImage = UIImageView()
  5471. cell.contentView.addSubview(selectedImage)
  5472. selectedImage.translatesAutoresizingMaskIntoConstraints = false
  5473. selectedImage.frame.size = CGSize(width: 20, height: 20)
  5474. var leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: -20)
  5475. selectedImage.isHidden = true
  5476. if copySession || forwardSession || deleteSession {
  5477. leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15)
  5478. selectedImage.isHidden = false
  5479. }
  5480. NSLayoutConstraint.activate([
  5481. leading,
  5482. selectedImage.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor),
  5483. selectedImage.widthAnchor.constraint(equalToConstant: 20),
  5484. selectedImage.heightAnchor.constraint(equalToConstant: 20)
  5485. ])
  5486. selectedImage.circle()
  5487. selectedImage.layer.borderWidth = 2
  5488. selectedImage.layer.borderColor = UIColor.mainColor.cgColor
  5489. if dataMessages[indexPath.row]["isSelected"] as! Bool {
  5490. selectedImage.image = UIImage(systemName: "checkmark.circle.fill")
  5491. }
  5492. selectedImage.tintColor = .mainColor
  5493. }
  5494. }
  5495. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5496. containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cell.contentView.leadingAnchor, constant: 60).isActive = true
  5497. if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5498. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40 - 20).isActive = true
  5499. } else {
  5500. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5 - 20).isActive = true
  5501. }
  5502. if isContactCenter {
  5503. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  5504. containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
  5505. } else {
  5506. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5507. containerMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
  5508. }
  5509. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5510. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  5511. containerMessage.backgroundColor = .clear
  5512. } else {
  5513. containerMessage.backgroundColor = .blueBubbleColor
  5514. }
  5515. containerMessage.layer.cornerRadius = 10.0
  5516. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  5517. containerMessage.clipsToBounds = true
  5518. timeMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  5519. if (dataMessages[indexPath.row]["lock"] as? String == "0" || (dataMessages[indexPath.row]["lock"] as? String ?? "").isEmpty) {
  5520. cell.contentView.addSubview(statusMessage)
  5521. statusMessage.translatesAutoresizingMaskIntoConstraints = false
  5522. statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  5523. statusMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  5524. statusMessage.widthAnchor.constraint(equalToConstant: 15).isActive = true
  5525. statusMessage.heightAnchor.constraint(equalToConstant: 15).isActive = true
  5526. if dataMessages[indexPath.row]["status"]! as? String ?? "" == "0" {
  5527. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  5528. } else if dataMessages[indexPath.row]["status"]! as? String ?? "" == "1" {
  5529. statusMessage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
  5530. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "2" ) {
  5531. statusMessage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  5532. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "3") {
  5533. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  5534. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "8") {
  5535. statusMessage.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5536. } else {
  5537. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  5538. }
  5539. }
  5540. } else {
  5541. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  5542. if isContactCenter {
  5543. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  5544. } else {
  5545. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
  5546. }
  5547. let newMessagesView = UIView()
  5548. cell.contentView.addSubview(newMessagesView)
  5549. newMessagesView.translatesAutoresizingMaskIntoConstraints = false
  5550. NSLayoutConstraint.activate([
  5551. newMessagesView.topAnchor.constraint(equalTo: newMessagesView.topAnchor),
  5552. newMessagesView.bottomAnchor.constraint(equalTo: containerMessage.topAnchor),
  5553. newMessagesView.centerXAnchor.constraint(equalTo: cell.contentView.centerXAnchor),
  5554. newMessagesView.heightAnchor.constraint(equalToConstant: 30),
  5555. newMessagesView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  5556. ])
  5557. newMessagesView.backgroundColor = .greenColor
  5558. newMessagesView.layer.cornerRadius = 15.0
  5559. newMessagesView.clipsToBounds = true
  5560. let labelNewMessages = UILabel()
  5561. newMessagesView.addSubview(labelNewMessages)
  5562. labelNewMessages.translatesAutoresizingMaskIntoConstraints = false
  5563. NSLayoutConstraint.activate([
  5564. labelNewMessages.centerYAnchor.constraint(equalTo: newMessagesView.centerYAnchor),
  5565. labelNewMessages.centerXAnchor.constraint(equalTo: newMessagesView.centerXAnchor),
  5566. labelNewMessages.leadingAnchor.constraint(equalTo: newMessagesView.leadingAnchor, constant: 10),
  5567. labelNewMessages.trailingAnchor.constraint(equalTo: newMessagesView.trailingAnchor, constant: -10),
  5568. ])
  5569. labelNewMessages.textAlignment = .center
  5570. labelNewMessages.textColor = .secondaryColor
  5571. labelNewMessages.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  5572. labelNewMessages.text = "Unread Messages".localized()
  5573. } else {
  5574. if isContactCenter {
  5575. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  5576. } else {
  5577. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5578. }
  5579. }
  5580. if isContactCenter {
  5581. containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
  5582. } else {
  5583. if copySession || forwardSession || deleteSession {
  5584. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
  5585. } else {
  5586. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5587. }
  5588. }
  5589. if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5590. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40 - 20).isActive = true
  5591. } else {
  5592. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5 - 20).isActive = true
  5593. }
  5594. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
  5595. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5596. if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  5597. containerMessage.backgroundColor = .clear
  5598. } else {
  5599. containerMessage.backgroundColor = .whiteBubbleColor
  5600. }
  5601. containerMessage.layer.cornerRadius = 10.0
  5602. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  5603. containerMessage.clipsToBounds = true
  5604. timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  5605. }
  5606. let imageStared = UIImageView()
  5607. if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" == "0") {
  5608. cell.contentView.addSubview(imageStared)
  5609. imageStared.translatesAutoresizingMaskIntoConstraints = false
  5610. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5611. imageStared.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
  5612. imageStared.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  5613. } else {
  5614. imageStared.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  5615. imageStared.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  5616. }
  5617. imageStared.widthAnchor.constraint(equalToConstant: 15).isActive = true
  5618. imageStared.heightAnchor.constraint(equalToConstant: 15).isActive = true
  5619. imageStared.image = UIImage(systemName: "star.fill")
  5620. imageStared.backgroundColor = .clear
  5621. imageStared.tintColor = .systemYellow
  5622. }
  5623. if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
  5624. let imageAckView = UIImageView()
  5625. var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5626. if dataMessages[indexPath.row]["status"] as? String == "8" {
  5627. imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5628. }
  5629. imageAckView.image = imageAck
  5630. cell.contentView.addSubview(imageAckView)
  5631. imageAckView.translatesAutoresizingMaskIntoConstraints = false
  5632. imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5633. imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5634. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5635. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5636. imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  5637. } else {
  5638. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5639. imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  5640. let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
  5641. tap.indexPath = indexPath
  5642. imageAckView.addGestureRecognizer(tap)
  5643. imageAckView.isUserInteractionEnabled = true
  5644. }
  5645. }
  5646. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" {
  5647. let imageCredentialView = UIImageView()
  5648. let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5649. imageCredentialView.image = imageCredential
  5650. cell.contentView.addSubview(imageCredentialView)
  5651. imageCredentialView.translatesAutoresizingMaskIntoConstraints = false
  5652. imageCredentialView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5653. imageCredentialView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5654. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5655. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5656. imageCredentialView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  5657. } else {
  5658. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5659. imageCredentialView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  5660. }
  5661. }
  5662. if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
  5663. let editedText = UILabel()
  5664. editedText.text = "Edited".localized()
  5665. editedText.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  5666. editedText.textColor = .lightGray
  5667. cell.contentView.addSubview(editedText)
  5668. editedText.translatesAutoresizingMaskIntoConstraints = false
  5669. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5670. editedText.trailingAnchor.constraint(equalTo: timeMessage.leadingAnchor, constant: -2).isActive = true
  5671. } else {
  5672. editedText.leadingAnchor.constraint(equalTo: timeMessage.trailingAnchor, constant: 2).isActive = true
  5673. }
  5674. editedText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor).isActive = true
  5675. }
  5676. let messageText = UITextView()
  5677. messageText.isEditable = false
  5678. messageText.isSelectable = true
  5679. messageText.dataDetectorTypes = []
  5680. messageText.backgroundColor = .clear
  5681. messageText.isScrollEnabled = false
  5682. messageText.textContainerInset = UIEdgeInsets.zero
  5683. messageText.contentInset = UIEdgeInsets.zero
  5684. messageText.textDragInteraction?.isEnabled = false
  5685. containerMessage.addSubview(messageText)
  5686. messageText.translatesAutoresizingMaskIntoConstraints = false
  5687. let topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15)
  5688. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5689. messageText.font = .systemFont(ofSize: 12 + offset())
  5690. if dataMessages[indexPath.row]["attachment_flag"] as? String == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String == "26" ||
  5691. dataMessages[indexPath.row]["attachment_flag"] as? String == "25" || dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
  5692. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
  5693. let imageLS = UIImageView()
  5694. containerMessage.addSubview(imageLS)
  5695. imageLS.translatesAutoresizingMaskIntoConstraints = false
  5696. NSLayoutConstraint.activate([
  5697. imageLS.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15.0),
  5698. imageLS.trailingAnchor.constraint(equalTo: messageText.leadingAnchor, constant: -10.0),
  5699. imageLS.centerYAnchor.constraint(equalTo: containerMessage.centerYAnchor),
  5700. imageLS.heightAnchor.constraint(equalToConstant: 60.0)
  5701. ])
  5702. if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "26" {
  5703. imageLS.image = UIImage(named: "pb_seminar_wpr", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5704. } else if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "27" {
  5705. imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5706. } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "25" {
  5707. imageLS.image = UIImage(named: "pb_vroom", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5708. } else if dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
  5709. imageLS.image = UIImage(systemName: "doc.richtext.fill")
  5710. imageLS.tintColor = .mainColor
  5711. }
  5712. } else if !audioChat.isEmpty {
  5713. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 60).isActive = true
  5714. } else {
  5715. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5716. }
  5717. if dataMessages[indexPath.row]["f_pin"] as? String == "-999" && (dataMessages[indexPath.row]["blog_id"] as? String) != nil && !(dataMessages[indexPath.row]["blog_id"] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["message_text"] as? String ?? "").contains("Berikut QR Code dan detil booking Anda") {
  5718. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -115).isActive = true
  5719. let imageQR = UIImageView()
  5720. containerMessage.addSubview(imageQR)
  5721. imageQR.translatesAutoresizingMaskIntoConstraints = false
  5722. NSLayoutConstraint.activate([
  5723. imageQR.centerXAnchor.constraint(equalTo: containerMessage.centerXAnchor),
  5724. imageQR.topAnchor.constraint(equalTo: messageText.bottomAnchor),
  5725. imageQR.widthAnchor.constraint(equalToConstant: 100.0),
  5726. imageQR.heightAnchor.constraint(equalToConstant: 100.0)
  5727. ])
  5728. imageQR.image = generateQRCode(from: dataMessages[indexPath.row]["blog_id"] as? String ?? "")
  5729. } else {
  5730. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -15).isActive = true
  5731. }
  5732. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5733. var textChat = (dataMessages[indexPath.row]["message_text"] as? String) ?? ""
  5734. let originalMessageText = textChat
  5735. if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
  5736. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5737. textChat = "🚫 _"+"You were deleted this message".localized()+"_"
  5738. } else {
  5739. textChat = "🚫 _"+"This message was deleted".localized()+"_"
  5740. }
  5741. }
  5742. if dataMessages[indexPath.row]["lock"] as? String == "2" {
  5743. textChat = "🚫 _"+"Message has expired".localized()+"_"
  5744. }
  5745. if !audioChat.isEmpty {
  5746. textChat = textChat.components(separatedBy: "|")[0]
  5747. }
  5748. let imageSticker = UIImageView()
  5749. var stringLS = ""
  5750. if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  5751. if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
  5752. let data = textChat
  5753. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5754. let title = json["title"] as? String ?? ""
  5755. let description = json["description"] as? String ?? ""
  5756. let start = json["time"] as? Int64 ?? 0
  5757. let by = json["by"] as? String ?? ""
  5758. let textLS = "Live Streaming".localized()
  5759. var type = "*\(textLS)*"
  5760. if attachmentFlag == "26" {
  5761. let textSeminar = "Seminar".localized()
  5762. type = "*\(textSeminar)*"
  5763. }
  5764. if let c = User.getData(pin: by) {
  5765. let name = c.fullName
  5766. stringLS = "\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: \(name)"
  5767. } else {
  5768. stringLS = ("\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))")
  5769. }
  5770. messageText.attributedText = stringLS.richText()
  5771. messageText.isUserInteractionEnabled = false
  5772. }
  5773. }
  5774. else if attachmentFlag == "25" {
  5775. let data = textChat
  5776. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5777. let title = json["title"] as? String ?? ""
  5778. let blog = json["blog"] as? String ?? ""
  5779. let by = json["by"] as? String ?? ""
  5780. let start = json["time"] as? Int64 ?? 0
  5781. let textVCR = "Video Conference Room".localized()
  5782. var type = "*\(textVCR)*"
  5783. if let c = User.getData(pin: by) {
  5784. let name = c.fullName
  5785. stringLS = "\(type) \nTitle: \(title) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nInitiator: \(name) \n\n*^Room ID: ^*\n*^\(blog)^*"
  5786. }
  5787. messageText.attributedText = stringLS.richText()
  5788. messageText.isUserInteractionEnabled = false
  5789. }
  5790. }
  5791. else if attachmentFlag == "11" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  5792. messageText.text = ""
  5793. topMarginText.constant = topMarginText.constant + 100
  5794. containerMessage.addSubview(imageSticker)
  5795. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  5796. let data = queryMessageReply(message_id: reffChat)
  5797. if reffChat.isEmpty || data.count == 0 {
  5798. imageSticker.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  5799. imageSticker.widthAnchor.constraint(equalToConstant: 80).isActive = true
  5800. } else {
  5801. imageSticker.widthAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true
  5802. }
  5803. imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5804. imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  5805. imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5806. var imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5807. if imageStickerBundle == nil {
  5808. imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  5809. }
  5810. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  5811. imageSticker.contentMode = .scaleAspectFit
  5812. } else if dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" == "18" {
  5813. let data = textChat
  5814. if let jsonForm = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5815. let form_title = jsonForm["form_title"] as? String ?? ""
  5816. let club_type = jsonForm["club_type"] as? String ?? ""
  5817. let province = jsonForm["province"] as? String ?? ""
  5818. let club = jsonForm["club"] as? String ?? ""
  5819. messageText.attributedText = "*\(form_title.replacingOccurrences(of: "+", with: " "))* \nClub Type: \(club_type) \nProvince: \(province) \nClub Name: \(club) ".richText()
  5820. messageText.isUserInteractionEnabled = false
  5821. }
  5822. }
  5823. else {
  5824. messageText.attributedText = textChat.richText()
  5825. modifyText()
  5826. }
  5827. } else {
  5828. messageText.attributedText = textChat.richText()
  5829. modifyText()
  5830. }
  5831. func modifyText() {
  5832. if !textChat.isEmpty {
  5833. if textChat.contains("■"){
  5834. textChat = textChat.components(separatedBy: "■")[0]
  5835. textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
  5836. }
  5837. if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "2" {
  5838. textChat = textChat.components(separatedBy: "|")[1]
  5839. }
  5840. let finalAtribute = textChat.richText()
  5841. textChat = finalAtribute.string
  5842. let urlPattern = "(https?://|www\\.)\\S+"
  5843. if let regex = try? NSRegularExpression(pattern: urlPattern, options: []) {
  5844. let matches = regex.matches(in: textChat, options: [], range: NSRange(textChat.startIndex..., in: textChat))
  5845. for match in matches {
  5846. if let range = Range(match.range, in: textChat) {
  5847. let linkText = String(textChat[range])
  5848. let nsRange = NSRange(range, in: textChat)
  5849. finalAtribute.addAttribute(.link, value: linkText, range: nsRange)
  5850. finalAtribute.addAttribute(.foregroundColor, value: UIColor.blue, range: nsRange)
  5851. finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
  5852. }
  5853. }
  5854. }
  5855. messageText.attributedText = finalAtribute
  5856. messageText.delegate = self
  5857. }
  5858. }
  5859. if !copySession && !forwardSession && !deleteSession && !self.removed {
  5860. let interaction = UIContextMenuInteraction(delegate: self)
  5861. containerMessage.addInteraction(interaction)
  5862. containerMessage.isUserInteractionEnabled = true
  5863. }
  5864. if isSearching && textSearch.count > 1 {
  5865. messageText.attributedText = stringLS.isEmpty ? textChat.richText(isSearching: true, textSearch: textSearch) : stringLS.richText(isSearching: true, textSearch: textSearch)
  5866. }
  5867. let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
  5868. if !stringDate.isEmpty {
  5869. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" && dataMessages[indexPath.row]["lock"] as? String != "1" {
  5870. if dataTimer != nil {
  5871. if dataTimer! >= 10 {
  5872. timeMessage.text = "00:\(dataTimer!)"
  5873. } else {
  5874. timeMessage.text = "00:0\(dataTimer!)"
  5875. }
  5876. timeMessage.textColor = .systemRed
  5877. }
  5878. } else {
  5879. let date = Date(milliseconds: Int64(stringDate) ?? 100)
  5880. let formatter = DateFormatter()
  5881. formatter.dateFormat = "HH:mm"
  5882. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  5883. timeMessage.text = formatter.string(from: date as Date)
  5884. timeMessage.textColor = .lightGray
  5885. }
  5886. timeMessage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  5887. }
  5888. let imageThumb = UIImageView()
  5889. let containerViewFile = UIView()
  5890. let imageGif = SDAnimatedImageView()
  5891. if !audioChat.isEmpty {
  5892. messageText.isHidden = true
  5893. let imageAudio = UIImageView()
  5894. imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
  5895. containerMessage.addSubview(imageAudio)
  5896. imageAudio.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 15, centerY: containerMessage.centerYAnchor)
  5897. imageAudio.tintColor = .mainColor
  5898. let playButtonAudio = UIButton(type: .system)
  5899. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  5900. playButtonAudio.tintColor = .gray
  5901. containerMessage.addSubview(playButtonAudio)
  5902. playButtonAudio.anchor(left: containerMessage.leftAnchor, paddingLeft: 60, centerY: containerMessage.centerYAnchor, width: 20, height: 20)
  5903. let progressSliderAudio = UISlider()
  5904. progressSliderAudio.minimumValue = 0
  5905. progressSliderAudio.maximumValue = 1
  5906. let thumbImage = UIImage(systemName: "circle.fill")?.withTintColor(UIColor.mainColor)
  5907. .resize(target: CGSize(width: 15, height: 15))
  5908. progressSliderAudio.setThumbImage(thumbImage, for: .normal)
  5909. containerMessage.addSubview(progressSliderAudio)
  5910. progressSliderAudio.anchor(left: playButtonAudio.rightAnchor, right: containerMessage.rightAnchor, paddingLeft: 10, paddingRight: 15, centerY: containerMessage.centerYAnchor, height: 15)
  5911. let timeLabelAudio = UILabel()
  5912. timeLabelAudio.text = "0:00"
  5913. timeLabelAudio.font = .systemFont(ofSize: 10 + offset())
  5914. timeLabelAudio.textColor = .gray
  5915. containerMessage.addSubview(timeLabelAudio)
  5916. timeLabelAudio.anchor(top: playButtonAudio.bottomAnchor, left: playButtonAudio.rightAnchor, paddingLeft: 10, width: 100, height: 12)
  5917. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5918. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5919. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5920. if let dirPath = paths.first {
  5921. let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(audioChat)
  5922. var url = audioURL
  5923. if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: audioChat) {
  5924. let activityIndicator = UIActivityIndicatorView(style: .medium)
  5925. activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  5926. activityIndicator.startAnimating()
  5927. playButtonAudio.setImage(nil, for: .normal)
  5928. playButtonAudio.addSubview(activityIndicator)
  5929. NSLayoutConstraint.activate([
  5930. activityIndicator.centerXAnchor.constraint(equalTo: playButtonAudio.centerXAnchor),
  5931. activityIndicator.centerYAnchor.constraint(equalTo: playButtonAudio.centerYAnchor)
  5932. ])
  5933. Download().startHTTP(forKey: audioChat, isImage: false) { (name, progress) in
  5934. guard progress == 100 else {
  5935. return
  5936. }
  5937. tableView.reloadRows(at: [indexPath], with: .none)
  5938. }
  5939. } else {
  5940. if !FileManager.default.fileExists(atPath: audioURL.path) {
  5941. do {
  5942. if let audioData = try FileEncryption.shared.readSecure(filename: audioChat) {
  5943. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  5944. let tempPath = cachesDirectory.appendingPathComponent(audioChat)
  5945. try audioData.write(to: tempPath)
  5946. url = tempPath
  5947. }
  5948. } catch {
  5949. }
  5950. }
  5951. if audioPlayers[indexPath] == nil {
  5952. do {
  5953. let audioPlayer = try AVAudioPlayer(contentsOf: url)
  5954. audioPlayers[indexPath] = audioPlayer
  5955. audioPlayer.delegate = self
  5956. progressSliderAudio.maximumValue = Float(audioPlayer.duration)
  5957. timeLabelAudio.text = formatTime(audioPlayer.duration)
  5958. } catch {
  5959. print("Error loading audio: \(error)")
  5960. }
  5961. }
  5962. let audioPlayer = audioPlayers[indexPath]
  5963. if playingIndexPath == indexPath, let player = audioPlayer, player.isPlaying {
  5964. playButtonAudio.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  5965. } else {
  5966. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  5967. }
  5968. // Play/Pause Button Action
  5969. playButtonAudio.addAction(UIAction { _ in
  5970. self.playPauseAudio(indexPath: indexPath, playButton: playButtonAudio, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  5971. }, for: .touchUpInside)
  5972. progressSliderAudio.addAction(UIAction { _ in
  5973. self.sliderChanged(indexPath: indexPath, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  5974. }, for: .valueChanged)
  5975. }
  5976. }
  5977. }
  5978. if (!thumbChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5979. if let listImages = groupImages[messageIdChat] {
  5980. timeMessage.isHidden = true
  5981. statusMessage.isHidden = true
  5982. imageStared.isHidden = true
  5983. topMarginText.constant = topMarginText.constant + 225
  5984. let listImageThumb: [UIImageView] = [UIImageView(), UIImageView(), UIImageView(), UIImageView()]
  5985. for i in 0..<4 {
  5986. containerMessage.addSubview(listImageThumb[i])
  5987. listImageThumb[i].layer.cornerRadius = 5.0
  5988. listImageThumb[i].clipsToBounds = true
  5989. listImageThumb[i].contentMode = .scaleAspectFill
  5990. let widthHeightImage: CGFloat = 120
  5991. switch i {
  5992. case 0:
  5993. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: 5, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
  5994. case 1:
  5995. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  5996. case 2:
  5997. listImageThumb[i].anchor(left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingLeft: 5, paddingBottom: 5, width: widthHeightImage, height: widthHeightImage)
  5998. default:
  5999. listImageThumb[i].anchor(left: listImageThumb[2].rightAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  6000. }
  6001. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6002. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6003. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6004. if let dirPath = paths.first {
  6005. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].thumbId)
  6006. let image : UIImage? = {
  6007. if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
  6008. return img
  6009. }
  6010. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6011. Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
  6012. return img
  6013. }
  6014. return nil
  6015. }()
  6016. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  6017. listImageThumb[i].image = image
  6018. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
  6019. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  6020. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  6021. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  6022. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[i].frame.size.width, height: listImageThumb[i].frame.size.height)
  6023. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6024. listImageThumb[i].addSubview(blurEffectView)
  6025. }
  6026. }
  6027. let containerTimeStatus = UIView()
  6028. listImageThumb[i].addSubview(containerTimeStatus)
  6029. containerTimeStatus.anchor(bottom: listImageThumb[i].bottomAnchor, right: listImageThumb[i].rightAnchor, height: 15)
  6030. let widthcontainerTimeStatus = containerTimeStatus.widthAnchor.constraint(equalToConstant: 50)
  6031. widthcontainerTimeStatus.isActive = true
  6032. containerTimeStatus.layer.cornerRadius = 5.0
  6033. containerTimeStatus.layer.masksToBounds = true
  6034. containerTimeStatus.backgroundColor = .black.withAlphaComponent(0.15)
  6035. let timeInImage = UILabel()
  6036. containerTimeStatus.addSubview(timeInImage)
  6037. let date = Date(milliseconds: Int64(listImages[i].time) ?? 100)
  6038. let formatter = DateFormatter()
  6039. formatter.dateFormat = "HH:mm"
  6040. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  6041. timeInImage.text = formatter.string(from: date as Date)
  6042. timeInImage.textColor = .white
  6043. timeInImage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  6044. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6045. let statusInImage = UIImageView()
  6046. containerTimeStatus.addSubview(statusInImage)
  6047. statusInImage.anchor(right: containerTimeStatus.rightAnchor, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  6048. if listImages[i].status == "0" {
  6049. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  6050. } else if listImages[i].status == "1" {
  6051. statusInImage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
  6052. } else if listImages[i].status == "2" {
  6053. statusInImage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  6054. } else if listImages[i].status == "3" {
  6055. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  6056. } else {
  6057. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  6058. }
  6059. timeInImage.anchor(right: statusInImage.leftAnchor, centerY: containerTimeStatus.centerYAnchor, height: 15)
  6060. } else {
  6061. timeInImage.anchor(right: containerTimeStatus.rightAnchor, paddingRight: 5, centerY: containerTimeStatus.centerYAnchor, height: 15)
  6062. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant - 10
  6063. }
  6064. if listImages[i].dataMessage["is_stared"] as? String == "1" {
  6065. let iconStar = UIImageView()
  6066. containerTimeStatus.addSubview(iconStar)
  6067. iconStar.anchor(right: timeInImage.leftAnchor, paddingRight: 2, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  6068. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant + 15
  6069. iconStar.image = UIImage(systemName: "star.fill")
  6070. iconStar.tintColor = .white
  6071. }
  6072. if !copySession && !forwardSession && !deleteSession {
  6073. let objectTap = ObjectGesture(target: self, action: #selector(imageGroupingTapped(_:)))
  6074. listImageThumb[i].isUserInteractionEnabled = true
  6075. listImageThumb[i].addGestureRecognizer(objectTap)
  6076. objectTap.indexImageTapped = i
  6077. objectTap.listImageFromGrouping = listImages
  6078. objectTap.isInitiator = dataMessages[indexPath.row]["f_pin"] as? String == idMe
  6079. }
  6080. }
  6081. if listImages.count > 4 {
  6082. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
  6083. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  6084. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[3].frame.size.width, height: listImageThumb[3].frame.size.height)
  6085. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6086. listImageThumb[3].addSubview(blurEffectView)
  6087. let countRestImages = UILabel()
  6088. listImageThumb[3].addSubview(countRestImages)
  6089. countRestImages.anchor(centerX: listImageThumb[3].centerXAnchor, centerY: listImageThumb[3].centerYAnchor)
  6090. countRestImages.font = UIFont.systemFont(ofSize: 30, weight: .medium)
  6091. countRestImages.text = "+\(listImages.count - 3)"
  6092. countRestImages.textColor = .white
  6093. }
  6094. } else {
  6095. let getHeightImage: CGFloat = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.height
  6096. let getWidthImage: CGFloat = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.width
  6097. topMarginText.constant = topMarginText.constant + (getHeightImage < 40 ? 45 : getHeightImage + 5)
  6098. containerMessage.addSubview(imageThumb)
  6099. imageThumb.frame = CGRect(x: 0, y: 0, width: getWidthImage, height: getHeightImage)
  6100. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  6101. let data = queryMessageReply(message_id: reffChat)
  6102. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  6103. imageThumb.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6104. }
  6105. imageThumb.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6106. imageThumb.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6107. imageThumb.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6108. imageThumb.widthAnchor.constraint(equalToConstant: getWidthImage).isActive = true
  6109. imageThumb.layer.cornerRadius = 5.0
  6110. imageThumb.clipsToBounds = true
  6111. imageThumb.contentMode = .scaleAspectFill
  6112. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6113. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6114. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6115. if let dirPath = paths.first {
  6116. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
  6117. DispatchQueue.main.async {
  6118. let image : UIImage? = {
  6119. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  6120. return img
  6121. }
  6122. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6123. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  6124. return img
  6125. }
  6126. return nil
  6127. }()
  6128. imageThumb.image = image
  6129. }
  6130. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  6131. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
  6132. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  6133. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  6134. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  6135. blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
  6136. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6137. imageThumb.addSubview(blurEffectView)
  6138. if !imageChat.isEmpty {
  6139. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
  6140. imageThumb.addSubview(imageDownload)
  6141. imageDownload.tintColor = .black.withAlphaComponent(0.3)
  6142. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  6143. imageDownload.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  6144. imageDownload.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  6145. }
  6146. }
  6147. }
  6148. if (videoChat != "" && gifChat.isEmpty) {
  6149. let imagePlay = UIImageView(image: UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .bold, scale: .default))?.imageWithInsets(insets: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))?.withTintColor(.white))
  6150. imagePlay.circle()
  6151. imageThumb.addSubview(imagePlay)
  6152. imagePlay.backgroundColor = .black.withAlphaComponent(0.3)
  6153. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  6154. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  6155. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  6156. } else if !gifChat.isEmpty {
  6157. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6158. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6159. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6160. if let dirPath = paths.first {
  6161. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifChat)
  6162. if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifChat) {
  6163. Download().startHTTP(forKey: gifChat, isImage: false) { (name, progress) in
  6164. guard progress == 100 else {
  6165. return
  6166. }
  6167. tableView.reloadRows(at: [indexPath], with: .none)
  6168. }
  6169. } else {
  6170. imageThumb.addSubview(imageGif)
  6171. imageGif.translatesAutoresizingMaskIntoConstraints = false
  6172. imageGif.anchor(top: imageThumb.topAnchor, left: imageThumb.leftAnchor, bottom: imageThumb.bottomAnchor, right: imageThumb.rightAnchor)
  6173. if FileManager.default.fileExists(atPath: gifURL.path) {
  6174. imageGif.image = SDAnimatedImage(contentsOfFile: gifURL.path)
  6175. // imageGif.shouldCustomLoopCount = true
  6176. // imageGif.animationRepeatCount = 4
  6177. } else if FileEncryption.shared.isSecureExists(filename: gifChat){
  6178. do {
  6179. let data = try FileEncryption.shared.readSecure(filename: gifChat)
  6180. if let imageData = SDAnimatedImage(data: data!) {
  6181. imageGif.image = imageData
  6182. // imageGif.shouldCustomLoopCount = true
  6183. // imageGif.animationRepeatCount = 4
  6184. }
  6185. }
  6186. catch {
  6187. print("Error reading secure file")
  6188. }
  6189. }
  6190. }
  6191. }
  6192. }
  6193. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0 && dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6194. let container = UIView()
  6195. imageThumb.addSubview(container)
  6196. container.translatesAutoresizingMaskIntoConstraints = false
  6197. container.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  6198. container.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  6199. container.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6200. container.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6201. container.backgroundColor = .white.withAlphaComponent(0.1)
  6202. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 10, y: 20), radius: 15, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  6203. let trackShape = CAShapeLayer()
  6204. trackShape.path = circlePath.cgPath
  6205. trackShape.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
  6206. trackShape.lineWidth = 3
  6207. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  6208. container.backgroundColor = .clear
  6209. container.layer.addSublayer(trackShape)
  6210. let shapeLoading = CAShapeLayer()
  6211. shapeLoading.path = circlePath.cgPath
  6212. shapeLoading.fillColor = UIColor.clear.cgColor
  6213. shapeLoading.lineWidth = 3
  6214. shapeLoading.strokeEnd = 0
  6215. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  6216. container.layer.addSublayer(shapeLoading)
  6217. let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6218. imageupload.tintColor = .white
  6219. container.addSubview(imageupload)
  6220. imageupload.translatesAutoresizingMaskIntoConstraints = false
  6221. imageupload.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  6222. imageupload.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  6223. imageupload.widthAnchor.constraint(equalToConstant: 20).isActive = true
  6224. imageupload.heightAnchor.constraint(equalToConstant: 20).isActive = true
  6225. }
  6226. if !copySession && !forwardSession && !deleteSession {
  6227. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  6228. imageThumb.isUserInteractionEnabled = true
  6229. imageThumb.addGestureRecognizer(objectTap)
  6230. objectTap.image_id = imageChat
  6231. objectTap.video_id = videoChat
  6232. objectTap.gif_id = gifChat
  6233. objectTap.imageView = imageThumb
  6234. objectTap.indexPath = indexPath
  6235. }
  6236. }
  6237. }
  6238. if (fileChat != "" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  6239. topMarginText.constant = topMarginText.constant + 55
  6240. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6241. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6242. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6243. let arrExtFile = (originalMessageText.components(separatedBy: "|")[0]).split(separator: ".")
  6244. let finalExtFile = arrExtFile[arrExtFile.count - 1]
  6245. if let dirPath = paths.first {
  6246. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
  6247. if FileManager.default.fileExists(atPath: fileURL.path) {
  6248. if let dataFile = try? Data(contentsOf: fileURL), textChat.isEmpty {
  6249. var sizeOfFile = Int(dataFile.count / 1000000)
  6250. if (sizeOfFile < 1) {
  6251. sizeOfFile = Int(dataFile.count / 1000)
  6252. if (finalExtFile.count > 4) {
  6253. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  6254. }else {
  6255. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  6256. }
  6257. } else {
  6258. if (finalExtFile.count > 4) {
  6259. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  6260. }else {
  6261. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  6262. }
  6263. }
  6264. }
  6265. }
  6266. else if FileEncryption.shared.isSecureExists(filename: fileChat) {
  6267. if let dataFile = try? FileEncryption.shared.readSecure(filename: fileChat), textChat.isEmpty {
  6268. var sizeOfFile = Int(dataFile.count / 1000000)
  6269. if (sizeOfFile < 1) {
  6270. sizeOfFile = Int(dataFile.count / 1000)
  6271. if (finalExtFile.count > 4) {
  6272. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  6273. }else {
  6274. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  6275. }
  6276. } else {
  6277. if (finalExtFile.count > 4) {
  6278. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  6279. }else {
  6280. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  6281. }
  6282. }
  6283. }
  6284. }
  6285. }
  6286. containerMessage.addSubview(containerViewFile)
  6287. containerViewFile.translatesAutoresizingMaskIntoConstraints = false
  6288. let data = queryMessageReply(message_id: reffChat)
  6289. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  6290. containerViewFile.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6291. }
  6292. containerViewFile.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6293. containerViewFile.bottomAnchor.constraint(equalTo:messageText.topAnchor, constant: -5).isActive = true
  6294. containerViewFile.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6295. // containerViewFile.heightAnchor.constraint(equalToConstant: 50).isActive = true
  6296. containerViewFile.backgroundColor = .black.withAlphaComponent(0.2)
  6297. containerViewFile.layer.cornerRadius = 5.0
  6298. containerViewFile.clipsToBounds = true
  6299. let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .bold, scale: .default)))
  6300. containerViewFile.addSubview(imageFile)
  6301. let nameFile = UILabel()
  6302. containerViewFile.addSubview(nameFile)
  6303. imageFile.translatesAutoresizingMaskIntoConstraints = false
  6304. imageFile.leadingAnchor.constraint(equalTo: containerViewFile.leadingAnchor, constant: 5).isActive = true
  6305. imageFile.trailingAnchor.constraint(equalTo: nameFile.leadingAnchor, constant: -5).isActive = true
  6306. imageFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  6307. imageFile.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6308. imageFile.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6309. imageFile.tintColor = .docColor
  6310. nameFile.translatesAutoresizingMaskIntoConstraints = false
  6311. nameFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  6312. nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
  6313. nameFile.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  6314. nameFile.textColor = .white
  6315. nameFile.text = originalMessageText.components(separatedBy: "|")[0]
  6316. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
  6317. let containerLoading = UIView()
  6318. containerViewFile.addSubview(containerLoading)
  6319. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  6320. containerLoading.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  6321. containerLoading.leadingAnchor.constraint(equalTo: nameFile.trailingAnchor, constant: 5).isActive = true
  6322. containerLoading.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  6323. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6324. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6325. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  6326. let trackShape = CAShapeLayer()
  6327. trackShape.path = circlePath.cgPath
  6328. trackShape.fillColor = UIColor.clear.cgColor
  6329. trackShape.lineWidth = 5
  6330. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  6331. containerLoading.layer.addSublayer(trackShape)
  6332. let shapeLoading = CAShapeLayer()
  6333. shapeLoading.path = circlePath.cgPath
  6334. shapeLoading.fillColor = UIColor.clear.cgColor
  6335. shapeLoading.lineWidth = 3
  6336. shapeLoading.strokeEnd = 0
  6337. shapeLoading.strokeColor = UIColor.secondaryColor.cgColor
  6338. containerLoading.layer.addSublayer(shapeLoading)
  6339. var imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6340. if dataMessages[indexPath.row]["f_pin"] as? String != idMe {
  6341. imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6342. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  6343. }
  6344. imageupload.tintColor = .white
  6345. containerLoading.addSubview(imageupload)
  6346. imageupload.translatesAutoresizingMaskIntoConstraints = false
  6347. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  6348. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  6349. } else {
  6350. nameFile.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  6351. }
  6352. if !copySession && !forwardSession && !deleteSession {
  6353. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  6354. containerViewFile.addGestureRecognizer(objectTap)
  6355. objectTap.containerFile = containerViewFile
  6356. objectTap.labelFile = nameFile
  6357. objectTap.file_id = fileChat
  6358. objectTap.indexPath = indexPath
  6359. }
  6360. }
  6361. let containerLinkMessage = UIView()
  6362. var isLoadingShowLink = false
  6363. if thumbChat.isEmpty && fileChat.isEmpty && !textChat.isEmpty {
  6364. var text = ""
  6365. let listTextSplitBreak = textChat.components(separatedBy: "\n")
  6366. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  6367. if indexFirstLinkSplitBreak != nil {
  6368. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  6369. let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
  6370. if indexFirstLinkSplitSpace != nil {
  6371. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  6372. }
  6373. }
  6374. if !text.isEmpty {
  6375. isLoadingShowLink = true
  6376. func showLink() {
  6377. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  6378. let title = data["title"] as? String ?? ""
  6379. let description = data["description"] as? String ?? ""
  6380. let imageUrl = data["imageUrl"] as? String
  6381. let link = data["link"] as? String ?? ""
  6382. topMarginText.constant = topMarginText.constant + 85
  6383. containerMessage.addSubview(containerLinkMessage)
  6384. containerLinkMessage.translatesAutoresizingMaskIntoConstraints = false
  6385. containerLinkMessage.leadingAnchor.constraint(equalTo:containerMessage.leadingAnchor, constant: 15).isActive = true
  6386. containerLinkMessage.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6387. containerLinkMessage.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6388. containerLinkMessage.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  6389. containerLinkMessage.backgroundColor = .gray.withAlphaComponent(0.2)
  6390. let imagePreview = UIImageView()
  6391. if imageUrl != nil {
  6392. containerLinkMessage.addSubview(imagePreview)
  6393. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  6394. imagePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor).isActive = true
  6395. imagePreview.bottomAnchor.constraint(equalTo: containerLinkMessage.bottomAnchor).isActive = true
  6396. imagePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor).isActive = true
  6397. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  6398. imagePreview.loadImageAsync(with: imageUrl)
  6399. imagePreview.contentMode = .scaleToFill
  6400. }
  6401. let titlePreview = UILabel()
  6402. containerLinkMessage.addSubview(titlePreview)
  6403. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  6404. if imageUrl != nil {
  6405. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  6406. } else {
  6407. titlePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  6408. }
  6409. titlePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: 10.0).isActive = true
  6410. titlePreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  6411. titlePreview.text = title
  6412. titlePreview.font = UIFont.systemFont(ofSize: 14.0 + offset(), weight: .bold)
  6413. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  6414. let descPreview = UILabel()
  6415. containerLinkMessage.addSubview(descPreview)
  6416. descPreview.translatesAutoresizingMaskIntoConstraints = false
  6417. if imageUrl != nil {
  6418. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  6419. } else {
  6420. descPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  6421. }
  6422. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  6423. descPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  6424. descPreview.text = description
  6425. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  6426. descPreview.textColor = .gray
  6427. descPreview.numberOfLines = 1
  6428. let linkPreview = UILabel()
  6429. containerLinkMessage.addSubview(linkPreview)
  6430. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  6431. if imageUrl != nil {
  6432. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  6433. } else {
  6434. linkPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  6435. }
  6436. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  6437. linkPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  6438. linkPreview.text = link
  6439. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  6440. linkPreview.textColor = .gray
  6441. linkPreview.numberOfLines = 1
  6442. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
  6443. showForwardedSign()
  6444. }
  6445. if !copySession && !forwardSession && !deleteSession {
  6446. let objectTap = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
  6447. objectTap.message_id = text
  6448. containerLinkMessage.addGestureRecognizer(objectTap)
  6449. }
  6450. }
  6451. }
  6452. var dataURL = ""
  6453. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  6454. do {
  6455. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
  6456. if let data = cursor.string(forColumnIndex: 0) {
  6457. dataURL = data
  6458. }
  6459. cursor.close()
  6460. }
  6461. } catch {
  6462. rollback.pointee = true
  6463. print("Access database error: \(error.localizedDescription)")
  6464. }
  6465. })
  6466. if dataURL.isEmpty {
  6467. let urlConfig = URLSessionConfiguration.default
  6468. let sessionDelegate = SelfSignedURLSessionDelegate()
  6469. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  6470. let slp = SwiftLinkPreview(session: session,
  6471. workQueue: SwiftLinkPreview.defaultWorkQueue,
  6472. responseQueue: DispatchQueue.main,
  6473. cache: DisabledCache.instance)
  6474. let preview = slp.preview(text,
  6475. onSuccess: { result in
  6476. let title = result.title ?? "No Title"
  6477. let description = text.contains("google.com") ? "" : result.description
  6478. let imageUrl = result.icon
  6479. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  6480. do {
  6481. var dataJson: [String: Any] = [:]
  6482. dataJson["title"] = title
  6483. dataJson["description"] = description
  6484. dataJson["imageUrl"] = imageUrl
  6485. dataJson["link"] = text
  6486. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  6487. return
  6488. }
  6489. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  6490. "id" : "\(Date().currentTimeMillis().toHex())",
  6491. "link" : text,
  6492. "data_link" : json,
  6493. "retry": 0
  6494. ], replace: true)
  6495. dataURL = json
  6496. showLink()
  6497. DispatchQueue.main.async {
  6498. tableView.reloadRows(at: [indexPath], with: .none)
  6499. }
  6500. } catch {
  6501. rollback.pointee = true
  6502. print("Access database error: \(error.localizedDescription)")
  6503. }
  6504. })
  6505. }, onError: { error in
  6506. })
  6507. } else {
  6508. showLink()
  6509. }
  6510. }
  6511. }
  6512. if (reffChat != "" && dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" != "18") {
  6513. let data = queryMessageReply(message_id: reffChat)
  6514. if data.count != 0 {
  6515. topMarginText.constant = topMarginText.constant + 55
  6516. let containerReply = UIView()
  6517. containerMessage.addSubview(containerReply)
  6518. containerReply.translatesAutoresizingMaskIntoConstraints = false
  6519. containerReply.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6520. containerReply.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6521. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6522. containerReply.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  6523. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6524. containerReply.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  6525. } else if containerMessage.subviews.contains(containerLinkMessage) {
  6526. containerReply.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  6527. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6528. containerReply.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  6529. } else {
  6530. containerReply.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6531. }
  6532. containerReply.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6533. containerReply.heightAnchor.constraint(equalToConstant: 50).isActive = true
  6534. containerReply.backgroundColor = .black.withAlphaComponent(0.2)
  6535. containerReply.layer.cornerRadius = 5
  6536. containerReply.clipsToBounds = true
  6537. let leftReply = UIView()
  6538. containerReply.addSubview(leftReply)
  6539. leftReply.translatesAutoresizingMaskIntoConstraints = false
  6540. leftReply.leadingAnchor.constraint(equalTo: containerReply.leadingAnchor).isActive = true
  6541. leftReply.topAnchor.constraint(equalTo: containerReply.topAnchor).isActive = true
  6542. leftReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor).isActive = true
  6543. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  6544. leftReply.layer.cornerRadius = 5
  6545. leftReply.clipsToBounds = true
  6546. leftReply.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
  6547. let titleReply = UILabel()
  6548. containerReply.addSubview(titleReply)
  6549. titleReply.translatesAutoresizingMaskIntoConstraints = false
  6550. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  6551. titleReply.topAnchor.constraint(equalTo: containerReply.topAnchor, constant: 10).isActive = true
  6552. titleReply.trailingAnchor.constraint(lessThanOrEqualTo: containerReply.trailingAnchor, constant: -20).isActive = true
  6553. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  6554. if (data["f_pin"] as? String == idMe) {
  6555. titleReply.text = "You".localized()
  6556. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  6557. titleReply.textColor = .white
  6558. leftReply.backgroundColor = .white
  6559. } else {
  6560. titleReply.textColor = .mainColor
  6561. leftReply.backgroundColor = .mainColor
  6562. }
  6563. } else {
  6564. if isContactCenter {
  6565. let user: [User] = users.filter({$0.pin == data["f_pin"] as? String})
  6566. titleReply.text = user.first!.fullName
  6567. } else {
  6568. titleReply.text = self.dataPerson["name"]!!
  6569. }
  6570. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  6571. titleReply.textColor = .white
  6572. leftReply.backgroundColor = .white
  6573. } else {
  6574. titleReply.textColor = .mainColor
  6575. leftReply.backgroundColor = .mainColor
  6576. }
  6577. }
  6578. let contentReply = UILabel()
  6579. containerReply.addSubview(contentReply)
  6580. contentReply.translatesAutoresizingMaskIntoConstraints = false
  6581. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  6582. contentReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor, constant: -10).isActive = true
  6583. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  6584. let message_text = data["message_text"] as? String ?? ""
  6585. let attachment_flag = data["attachment_flag"] as? String ?? ""
  6586. let thumb_chat = data["thumb_id"] as? String ?? ""
  6587. let image_chat = data["image_id"] as? String ?? ""
  6588. let video_chat = data["video_id"] as? String ?? ""
  6589. let file_chat = data["file_id"] as? String ?? ""
  6590. if (attachment_flag == "0" && thumb_chat == "") {
  6591. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  6592. contentReply.attributedText = message_text.richText()
  6593. } else if (attachment_flag == "1" || image_chat != "") {
  6594. if (message_text == "") {
  6595. contentReply.text = "📷 Photo".localized()
  6596. } else {
  6597. contentReply.attributedText = message_text.richText()
  6598. }
  6599. } else if (attachment_flag == "2" || video_chat != "") {
  6600. if (message_text == "") {
  6601. contentReply.text = "📹 Video".localized()
  6602. } else {
  6603. contentReply.attributedText = message_text.richText()
  6604. }
  6605. } else if (attachment_flag == "6" || file_chat != ""){
  6606. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  6607. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  6608. } else if (attachment_flag == "11") {
  6609. contentReply.text = "❤️ Sticker"
  6610. }
  6611. contentReply.textColor = .white.withAlphaComponent(0.8)
  6612. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  6613. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6614. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6615. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6616. if let dirPath = paths.first {
  6617. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  6618. DispatchQueue.main.async {
  6619. let image : UIImage? = {
  6620. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  6621. return img
  6622. }
  6623. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6624. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  6625. return img
  6626. }
  6627. return nil
  6628. }()
  6629. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  6630. let imageThumb = UIImageView(image: image)
  6631. containerReply.addSubview(imageThumb)
  6632. imageThumb.layer.cornerRadius = 2.0
  6633. imageThumb.clipsToBounds = true
  6634. imageThumb.contentMode = .scaleAspectFill
  6635. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  6636. imageThumb.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  6637. imageThumb.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  6638. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6639. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6640. if (attachment_flag == "2") {
  6641. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  6642. imageThumb.addSubview(imagePlay)
  6643. imagePlay.clipsToBounds = true
  6644. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  6645. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  6646. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  6647. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  6648. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  6649. imagePlay.tintColor = .white
  6650. }
  6651. titleReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  6652. contentReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  6653. }
  6654. }
  6655. }
  6656. if (attachment_flag == "11" && message_text.components(separatedBy: "/").count > 1) {
  6657. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  6658. containerReply.addSubview(imageSticker)
  6659. imageSticker.layer.cornerRadius = 2.0
  6660. imageSticker.clipsToBounds = true
  6661. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  6662. imageSticker.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  6663. imageSticker.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  6664. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6665. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6666. titleReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  6667. contentReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  6668. }
  6669. if !copySession && !forwardSession && !deleteSession {
  6670. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  6671. containerReply.addGestureRecognizer(objectTap)
  6672. objectTap.indexPath = indexPath
  6673. objectTap.message_id = data["message_id"] as? String ?? ""
  6674. }
  6675. }
  6676. }
  6677. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 && !isLoadingShowLink {
  6678. showForwardedSign()
  6679. }
  6680. func showForwardedSign() {
  6681. topMarginText.constant = topMarginText.constant + 20
  6682. let containerForwarded = UIView()
  6683. containerMessage.addSubview(containerForwarded)
  6684. containerForwarded.translatesAutoresizingMaskIntoConstraints = false
  6685. containerForwarded.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6686. containerForwarded.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6687. containerForwarded.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6688. containerForwarded.heightAnchor.constraint(equalToConstant: 20).isActive = true
  6689. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6690. containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  6691. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6692. containerForwarded.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  6693. } else if containerMessage.subviews.contains(containerLinkMessage) {
  6694. containerForwarded.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  6695. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6696. containerForwarded.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  6697. }
  6698. let imageForwarded = UIImageView()
  6699. containerForwarded.addSubview(imageForwarded)
  6700. imageForwarded.anchor(top: containerForwarded.topAnchor, left: containerForwarded.leftAnchor, width: 15, height: 15)
  6701. imageForwarded.image = UIImage(systemName: "arrowshape.turn.up.right.fill")
  6702. imageForwarded.tintColor = .gray
  6703. let titleForwarded = UILabel()
  6704. containerForwarded.addSubview(titleForwarded)
  6705. titleForwarded.anchor(top: containerForwarded.topAnchor, left: imageForwarded.rightAnchor, right: containerForwarded.rightAnchor, height: 15)
  6706. titleForwarded.font = .systemFont(ofSize: 15)
  6707. let textForwarded = "Forwarded".localized()
  6708. titleForwarded.attributedText = " $\(textForwarded)$".richText()
  6709. }
  6710. topMarginText.isActive = true
  6711. // let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureCellAction))
  6712. // panGestureRecognizer.delegate = self
  6713. // cellMessage.addGestureRecognizer(panGestureRecognizer)
  6714. return cell
  6715. }
  6716. func playPauseAudio(indexPath: IndexPath, playButton: UIButton, progressSlider: UISlider, timeLabel: UILabel) {
  6717. guard let audioPlayer = audioPlayers[indexPath] else { return }
  6718. if audioPlayer.isPlaying {
  6719. // Pause Audio
  6720. audioPlayer.pause()
  6721. playButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
  6722. timers[indexPath]?.invalidate()
  6723. } else {
  6724. // Stop other players if one is already playing
  6725. if let currentPlayingIndexPath = playingIndexPath, let currentAudioPlayer = audioPlayers[currentPlayingIndexPath] {
  6726. if currentPlayingIndexPath != indexPath {
  6727. currentAudioPlayer.pause()
  6728. timers[currentPlayingIndexPath]?.invalidate()
  6729. timers[currentPlayingIndexPath] = nil
  6730. audioPlayers[currentPlayingIndexPath] = nil
  6731. tableChatView.reloadRows(at: [currentPlayingIndexPath], with: .none)
  6732. }
  6733. }
  6734. do {
  6735. try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
  6736. try AVAudioSession.sharedInstance().setActive(true)
  6737. } catch {
  6738. }
  6739. // Play new audio
  6740. audioPlayer.play()
  6741. playButton.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  6742. playingIndexPath = indexPath
  6743. // Start timer to update progress
  6744. timers[indexPath] = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
  6745. progressSlider.value = Float(audioPlayer.currentTime)
  6746. timeLabel.text = self.formatTime(audioPlayer.currentTime)
  6747. }
  6748. }
  6749. }
  6750. func sliderChanged(indexPath: IndexPath, progressSlider: UISlider, timeLabel: UILabel) {
  6751. guard let audioPlayer = audioPlayers[indexPath] else { return }
  6752. audioPlayer.currentTime = TimeInterval(progressSlider.value)
  6753. timeLabel.text = formatTime(audioPlayer.currentTime)
  6754. }
  6755. func formatTime(_ time: TimeInterval) -> String {
  6756. let roundedTime = time.rounded(.up)
  6757. let minutes = Int(roundedTime) / 60
  6758. let seconds = Int(roundedTime) % 60
  6759. return String(format: "%d:%02d", minutes, seconds)
  6760. }
  6761. public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  6762. if let finishedIndexPath = audioPlayers.first(where: { $0.value == player })?.key {
  6763. DispatchQueue.main.async {
  6764. self.timers[finishedIndexPath]?.invalidate()
  6765. self.timers[finishedIndexPath] = nil
  6766. self.playingIndexPath = nil
  6767. self.audioPlayers[finishedIndexPath] = nil
  6768. self.tableChatView.reloadRows(at: [finishedIndexPath], with: .none)
  6769. }
  6770. }
  6771. }
  6772. @objc func imageGroupingTapped(_ sender: ObjectGesture) {
  6773. let listGroupingImages = ListGroupImages()
  6774. listGroupingImages.imageTapped = sender.indexImageTapped
  6775. listGroupingImages.listGroupingImages = sender.listImageFromGrouping
  6776. listGroupingImages.titleName = titleText
  6777. listGroupingImages.isInitiator = sender.isInitiator
  6778. listGroupingImages.updateEditor = { [self] updatedData, replyData, isUpdateDelete in
  6779. if replyData.count == 0 {
  6780. if updatedData.count != 0 && !isUpdateDelete {
  6781. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  6782. } else if updatedData.count > 0 {
  6783. let deletedForEveryoneData = updatedData.filter({ $0.dataMessage["lock"] as? String == "1" })
  6784. if deletedForEveryoneData.count != 0 {
  6785. if groupImages[sender.listImageFromGrouping[0].messageId] != nil {
  6786. var dataWillEmpty = updatedData
  6787. while dataWillEmpty.count > 0 {
  6788. if let lastIdx = dataWillEmpty.lastIndex(where: { $0.dataMessage["lock"] as? String == "1" }) {
  6789. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6790. if dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId {
  6791. self.dataMessages.remove(at: idx)
  6792. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx)
  6793. } else {
  6794. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx + 1)
  6795. }
  6796. let subData = Array(updatedData[lastIdx+1..<dataWillEmpty.count])
  6797. if subData.count >= 4 {
  6798. groupImages[subData[0].messageId] = subData
  6799. self.dataMessages.insert(subData[0].dataMessage, at: lastIdx + 1)
  6800. } else {
  6801. if subData.count > 0 {
  6802. self.dataMessages.insert(contentsOf: subData.map({ $0.dataMessage }), at: idx + (dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId ? 1 : 2))
  6803. }
  6804. }
  6805. }
  6806. dataWillEmpty.removeSubrange(lastIdx..<dataWillEmpty.count)
  6807. } else if dataWillEmpty.count >= 4 {
  6808. groupImages[dataWillEmpty[0].messageId] = dataWillEmpty
  6809. dataWillEmpty.removeAll()
  6810. } else {
  6811. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6812. self.dataMessages.remove(at: idx)
  6813. self.dataMessages.insert(contentsOf: dataWillEmpty.map({ $0.dataMessage }), at: idx)
  6814. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6815. }
  6816. dataWillEmpty.removeAll()
  6817. }
  6818. }
  6819. } else {
  6820. }
  6821. } else {
  6822. if updatedData.count >= 4 {
  6823. if updatedData[0].messageId == sender.listImageFromGrouping[0].messageId {
  6824. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  6825. } else {
  6826. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6827. self.dataMessages.remove(at: idx)
  6828. self.dataMessages.insert(updatedData[0].dataMessage, at: idx)
  6829. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6830. groupImages[updatedData[0].messageId] = updatedData
  6831. }
  6832. }
  6833. } else {
  6834. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6835. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6836. self.dataMessages.remove(at: idx)
  6837. let dataMessageInGrouping = updatedData.map({ $0.dataMessage })
  6838. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx)
  6839. }
  6840. }
  6841. }
  6842. } else {
  6843. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6844. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6845. self.dataMessages.remove(at: idx)
  6846. }
  6847. }
  6848. DispatchQueue.main.async { [self] in
  6849. tableChatView.reloadData()
  6850. }
  6851. } else if replyData.count != 0 {
  6852. handleReply(indexPath: IndexPath(row: 0, section: 0), dataMessagesImage: replyData)
  6853. }
  6854. }
  6855. self.navigationController?.pushViewController(listGroupingImages, animated: true)
  6856. }
  6857. @objc func tapAck(_ sender: ObjectGesture) {
  6858. if blocking == "1" {
  6859. self.view.makeToast("You blocked this user".localized(), duration: 3)
  6860. return
  6861. }
  6862. if blocking == "-1" {
  6863. self.view.makeToast("You have been blocked by this user".localized(), duration: 3)
  6864. return
  6865. }
  6866. let indexPath = sender.indexPath
  6867. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  6868. if dataMessages[indexPath.row]["status"] as? String ?? "" == "8" {
  6869. return
  6870. }
  6871. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  6872. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  6873. imageView.tintColor = .white
  6874. let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  6875. banner.show()
  6876. return
  6877. }
  6878. DispatchQueue.global().async {
  6879. let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as? String ?? "", message_id: dataMessages[indexPath.row]["message_id"] as? String ?? "", l_pin: dataMessages[indexPath.row]["l_pin"] as? String ?? "", server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as? String ?? "", longitude: self.longitude, latitude: self.latitude, description: ""))
  6880. if result != nil {
  6881. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  6882. do {
  6883. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  6884. "status" : "8"
  6885. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  6886. } catch {
  6887. rollback.pointee = true
  6888. print("Access database error: \(error.localizedDescription)")
  6889. }
  6890. })
  6891. DispatchQueue.main.async {
  6892. if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
  6893. self.dataMessages[index]["status"] = "8"
  6894. let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"] as? String ?? "")
  6895. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String ?? "" == self.dataMessages[index]["message_id"] as? String ?? ""})
  6896. if row != nil && section != nil {
  6897. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  6898. }
  6899. self.view.makeToast("Confirmation Success.".localized(), duration: 3)
  6900. }
  6901. }
  6902. }
  6903. }
  6904. }
  6905. // public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  6906. // let velocity : CGPoint = gestureRecognizer.location(in: tableChatView)
  6907. // if velocity.x < 0 {
  6908. // return false
  6909. // }
  6910. // return abs(Float(velocity.x)) > abs(Float(velocity.y))
  6911. // }
  6912. //
  6913. // @objc func panGestureCellAction(recognizer: UIPanGestureRecognizer) {
  6914. // let translation = recognizer.translation(in: tableChatView)
  6915. // let x = recognizer.view?.frame.origin.x ?? 0
  6916. // if x >= -(recognizer.view?.frame.size.width ?? 0) * 0.05 {
  6917. // recognizer.view?.center = CGPoint(
  6918. // x: (recognizer.view?.center.x ?? 0) + translation.x,
  6919. // y: (recognizer.view?.center.y ?? 0))
  6920. // recognizer.setTranslation(CGPoint(x: 0, y: 0), in: view)
  6921. // if (recognizer.view?.frame.origin.x ?? 0) > UIScreen.main.bounds.size.width * 0.9 {
  6922. // UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
  6923. // recognizer.view?.frame = CGRect(x: 0, y: recognizer.view?.frame.origin.y ?? 0, width: recognizer.view?.frame.size.width ?? 0, height: recognizer.view?.frame.size.height ?? 0)
  6924. // })
  6925. // }
  6926. // }
  6927. // if x <= -(recognizer.view?.frame.size.width ?? 0) * 0.05 {
  6928. // let idMe = User.getMyPin() as String?
  6929. // let indexPath = self.tableChatView.indexPath(for: recognizer.view! as! UITableViewCell)
  6930. // let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  6931. // if (dataMessages[indexPath!.row]["f_pin"] as? String == idMe) {
  6932. // let messageInfoVC = MessageInfo()
  6933. // messageInfoVC.data = dataMessages[indexPath!.row]
  6934. // self.navigationController?.pushViewController(messageInfoVC, animated: true)
  6935. // return
  6936. // }
  6937. // }
  6938. // if x >= ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  6939. // if !hapticSwipeLeft {
  6940. // UINotificationFeedbackGenerator().notificationOccurred(.success)
  6941. // }
  6942. // hapticSwipeLeft = true
  6943. // } else if x < ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  6944. // hapticSwipeLeft = false
  6945. // }
  6946. // if recognizer.state == .ended {
  6947. // UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
  6948. // recognizer.view?.frame = CGRect(x: 0, y: recognizer.view?.frame.origin.y ?? 0, width: recognizer.view?.frame.size.width ?? 0, height: recognizer.view?.frame.size.height ?? 0)
  6949. // } completion: { (finished) in
  6950. // if x > ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  6951. // self.hapticSwipeLeft = false
  6952. //
  6953. // }
  6954. // }
  6955. // }
  6956. // }
  6957. public func numberOfSections(in tableView: UITableView) -> Int {
  6958. dataDates.count
  6959. }
  6960. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  6961. let count = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section] }).count
  6962. return count
  6963. }
  6964. @objc func contentMessageTapped(_ sender: ObjectGesture) {
  6965. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6966. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6967. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6968. if (sender.image_id != "") {
  6969. if let dirPath = paths.first {
  6970. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  6971. if FileManager.default.fileExists(atPath: imageURL.path) {
  6972. let image = UIImage(contentsOfFile: imageURL.path)
  6973. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  6974. previewImageVC.image = image
  6975. previewImageVC.isHiddenTextField = true
  6976. previewImageVC.modalPresentationStyle = .custom
  6977. previewImageVC.modalTransitionStyle = .crossDissolve
  6978. self.present(previewImageVC, animated: true, completion: nil)
  6979. } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
  6980. do {
  6981. let data = try FileEncryption.shared.readSecure(filename: sender.image_id)
  6982. let image = UIImage(data: data!)
  6983. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  6984. previewImageVC.image = image
  6985. previewImageVC.isHiddenTextField = true
  6986. previewImageVC.modalPresentationStyle = .custom
  6987. previewImageVC.modalTransitionStyle = .crossDissolve
  6988. self.present(previewImageVC, animated: true, completion: nil)
  6989. }
  6990. catch {
  6991. print("Error reading secure file")
  6992. }
  6993. } else {
  6994. for view in sender.imageView.subviews {
  6995. if view is UIImageView {
  6996. view.removeFromSuperview()
  6997. }
  6998. }
  6999. let activityIndicator = UIActivityIndicatorView(style: .large)
  7000. activityIndicator.color = .mainColor
  7001. activityIndicator.hidesWhenStopped = true
  7002. activityIndicator.center = CGPoint(x:sender.imageView.frame.width/2,
  7003. y: sender.imageView.frame.height/2)
  7004. activityIndicator.startAnimating()
  7005. sender.imageView.addSubview(activityIndicator)
  7006. Download().startHTTP(forKey: sender.image_id) { (name, progress) in
  7007. guard progress == 100 else {
  7008. return
  7009. }
  7010. do {
  7011. let secureName = try FileEncryption.shared.writeSecure(filename: name)?[0] as? String ?? ""
  7012. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7013. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7014. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7015. if let dirPath = paths.first {
  7016. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  7017. if FileManager.default.fileExists(atPath: imageURL.path) {
  7018. let image = UIImage(contentsOfFile: imageURL.path)
  7019. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7020. if save {
  7021. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  7022. }
  7023. }
  7024. else if FileEncryption.shared.isSecureExists(filename: secureName) {
  7025. if let secureData = try FileEncryption.shared.readSecure(filename: secureName) {
  7026. let image = UIImage(data: secureData)
  7027. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7028. if save {
  7029. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  7030. }
  7031. }
  7032. }
  7033. }
  7034. } catch {
  7035. }
  7036. DispatchQueue.main.async {
  7037. activityIndicator.stopAnimating()
  7038. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  7039. }
  7040. }
  7041. }
  7042. }
  7043. } else if (sender.gif_id != "") {
  7044. if let dirPath = paths.first {
  7045. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.gif_id)
  7046. if FileManager.default.fileExists(atPath: gifURL.path) {
  7047. do {
  7048. let data = try Data(contentsOf: gifURL)
  7049. APIS.openImageNexilis(image: UIImage(), data: data, isGIF: true)
  7050. } catch {
  7051. }
  7052. } else if FileEncryption.shared.isSecureExists(filename: sender.gif_id) {
  7053. do {
  7054. if let secureData = try FileEncryption.shared.readSecure(filename: sender.gif_id) {
  7055. APIS.openImageNexilis(image: UIImage(), data: secureData, isGIF: true)
  7056. }
  7057. } catch {
  7058. }
  7059. }
  7060. }
  7061. } else if (sender.video_id != "") {
  7062. if let dirPath = paths.first {
  7063. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  7064. if FileManager.default.fileExists(atPath: videoURL.path) {
  7065. let player = AVPlayer(url: videoURL as URL)
  7066. let playerVC = AVPlayerViewController()
  7067. playerVC.modalPresentationStyle = .custom
  7068. playerVC.player = player
  7069. self.present(playerVC, animated: true, completion: nil)
  7070. } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
  7071. do {
  7072. if let secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
  7073. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7074. let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
  7075. try secureData.write(to: tempPath)
  7076. let player = AVPlayer(url: tempPath as URL)
  7077. let playerVC = AVPlayerViewController()
  7078. playerVC.modalPresentationStyle = .custom
  7079. playerVC.player = player
  7080. self.present(playerVC, animated: true, completion: nil)
  7081. }
  7082. } catch {
  7083. }
  7084. } else {
  7085. for view in sender.imageView.subviews {
  7086. if view is UIImageView {
  7087. view.removeFromSuperview()
  7088. }
  7089. }
  7090. let container = UIView()
  7091. sender.imageView.addSubview(container)
  7092. container.translatesAutoresizingMaskIntoConstraints = false
  7093. container.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  7094. container.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  7095. container.widthAnchor.constraint(equalToConstant: 50).isActive = true
  7096. container.heightAnchor.constraint(equalToConstant: 50).isActive = true
  7097. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 20, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  7098. let trackShape = CAShapeLayer()
  7099. trackShape.path = circlePath.cgPath
  7100. trackShape.fillColor = UIColor.clear.cgColor
  7101. trackShape.lineWidth = 10
  7102. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  7103. container.backgroundColor = .clear
  7104. container.layer.addSublayer(trackShape)
  7105. let shapeLoading = CAShapeLayer()
  7106. shapeLoading.path = circlePath.cgPath
  7107. shapeLoading.fillColor = UIColor.clear.cgColor
  7108. shapeLoading.lineWidth = 10
  7109. shapeLoading.strokeEnd = 0
  7110. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  7111. container.layer.addSublayer(shapeLoading)
  7112. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  7113. imageDownload.tintColor = .white
  7114. container.addSubview(imageDownload)
  7115. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  7116. imageDownload.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  7117. imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  7118. imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7119. imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7120. Download().startHTTP(forKey: sender.video_id, isImage: false) { (name, progress) in
  7121. DispatchQueue.main.async {
  7122. guard progress == 100 else {
  7123. shapeLoading.strokeEnd = CGFloat(progress / 100)
  7124. return
  7125. }
  7126. do {
  7127. if let secureName = try FileEncryption.shared.writeSecure(filename: name)?[0] as? String {
  7128. let secureData = try FileEncryption.shared.readSecure(filename: secureName)
  7129. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7130. let tempPath = cachesDirectory.appendingPathComponent(name)
  7131. try secureData!.write(to: tempPath)
  7132. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7133. if save {
  7134. PHPhotoLibrary.shared().performChanges({
  7135. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
  7136. }) { saved, error in
  7137. }
  7138. }
  7139. } else {
  7140. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7141. if save {
  7142. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  7143. PHPhotoLibrary.shared().performChanges({
  7144. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
  7145. }) { saved, error in
  7146. }
  7147. }
  7148. }
  7149. } catch {
  7150. }
  7151. let idx = self.dataMessages.firstIndex(where: { $0["video_id"] as? String ?? "" == sender.video_id})
  7152. if idx != nil {
  7153. self.dataMessages[idx!]["progress"] = progress
  7154. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  7155. }
  7156. }
  7157. }
  7158. }
  7159. }
  7160. } else if (sender.file_id != "") {
  7161. if let dirPath = paths.first {
  7162. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id)
  7163. if FileManager.default.fileExists(atPath: fileURL.path) {
  7164. self.previewItem = fileURL as NSURL
  7165. let previewController = QLPreviewController()
  7166. let rightBarButton = UIBarButtonItem()
  7167. previewController.navigationItem.rightBarButtonItem = rightBarButton
  7168. previewController.dataSource = self
  7169. previewController.modalPresentationStyle = .custom
  7170. self.present(previewController, animated: true)
  7171. } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
  7172. do {
  7173. if let docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
  7174. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7175. let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
  7176. try docData.write(to: tempPath)
  7177. self.previewItem = tempPath as NSURL
  7178. let previewController = QLPreviewController()
  7179. let rightBarButton = UIBarButtonItem()
  7180. previewController.navigationItem.rightBarButtonItem = rightBarButton
  7181. previewController.dataSource = self
  7182. previewController.modalPresentationStyle = .custom
  7183. self.present(previewController,animated: true)
  7184. }
  7185. }
  7186. catch {
  7187. }
  7188. } else {
  7189. for view in sender.containerFile.subviews {
  7190. if !(view is UIImageView) && !(view is UILabel) {
  7191. view.removeFromSuperview()
  7192. }
  7193. }
  7194. let containerLoading = UIView()
  7195. sender.containerFile.addSubview(containerLoading)
  7196. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  7197. containerLoading.centerYAnchor.constraint(equalTo: sender.containerFile.centerYAnchor).isActive = true
  7198. containerLoading.leadingAnchor.constraint(equalTo: sender.labelFile.trailingAnchor, constant: 5).isActive = true
  7199. containerLoading.trailingAnchor.constraint(equalTo: sender.containerFile.trailingAnchor, constant: -5).isActive = true
  7200. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7201. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7202. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  7203. let trackShape = CAShapeLayer()
  7204. trackShape.path = circlePath.cgPath
  7205. trackShape.fillColor = UIColor.clear.cgColor
  7206. trackShape.lineWidth = 5
  7207. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  7208. containerLoading.layer.addSublayer(trackShape)
  7209. let shapeLoading = CAShapeLayer()
  7210. shapeLoading.path = circlePath.cgPath
  7211. shapeLoading.fillColor = UIColor.clear.cgColor
  7212. shapeLoading.lineWidth = 3
  7213. shapeLoading.strokeEnd = 0
  7214. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  7215. containerLoading.layer.addSublayer(shapeLoading)
  7216. let imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  7217. imageupload.tintColor = .white
  7218. containerLoading.addSubview(imageupload)
  7219. imageupload.translatesAutoresizingMaskIntoConstraints = false
  7220. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  7221. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  7222. Download().startHTTP(forKey: sender.file_id, isImage: false) { (name, progress) in
  7223. DispatchQueue.main.async {
  7224. guard progress == 100 else {
  7225. shapeLoading.strokeEnd = CGFloat(progress / 100)
  7226. return
  7227. }
  7228. do {
  7229. try FileEncryption.shared.writeSecure(filename: name)
  7230. } catch {
  7231. }
  7232. let idx = self.dataMessages.firstIndex(where: { $0["file_id"] as? String ?? "" == sender.file_id})
  7233. if idx != nil {
  7234. self.dataMessages[idx!]["progress"] = progress
  7235. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  7236. }
  7237. }
  7238. }
  7239. }
  7240. }
  7241. } else {
  7242. DispatchQueue.main.async {
  7243. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == sender.message_id})
  7244. if idx == nil {
  7245. return
  7246. }
  7247. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  7248. if section == nil {
  7249. return
  7250. }
  7251. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[idx!]["message_id"] as? String})
  7252. if row == nil {
  7253. return
  7254. }
  7255. let indexPath = IndexPath(row: row!, section: section!)
  7256. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  7257. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  7258. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  7259. let containerMessage = cell.contentView.subviews[0]
  7260. let idMe = User.getMyPin() as String?
  7261. if (self.dataMessages[idx!]["f_pin"] as? String == idMe) {
  7262. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  7263. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7264. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  7265. containerMessage.backgroundColor = .clear
  7266. } else {
  7267. containerMessage.backgroundColor = .blueBubbleColor
  7268. }
  7269. }
  7270. } else {
  7271. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  7272. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7273. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  7274. containerMessage.backgroundColor = .clear
  7275. } else {
  7276. containerMessage.backgroundColor = .whiteBubbleColor
  7277. }
  7278. }
  7279. }
  7280. }
  7281. }
  7282. }
  7283. }
  7284. }
  7285. func highlightedText(for text: String, textView: UITextView) -> NSAttributedString {
  7286. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  7287. if let range = textView.attributedText.string.range(of: text) {
  7288. let nsRange = NSRange(range, in: textView.attributedText.string)
  7289. mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.lightGray.withAlphaComponent(0.5), range: NSRange(range, in: text))
  7290. }
  7291. return mutableAttributedString
  7292. }
  7293. func removeHighlightedText(for text: String, textView: UITextView) -> NSAttributedString {
  7294. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  7295. if let range = textView.attributedText.string.range(of: text) {
  7296. let nsRange = NSRange(range, in: textView.attributedText.string)
  7297. mutableAttributedString.removeAttribute(.backgroundColor, range: NSRange(range, in: text))
  7298. }
  7299. return mutableAttributedString
  7300. }
  7301. @objc func tapMessageText(_ sender: ObjectGesture) {
  7302. var stringURl = sender.message_id
  7303. if stringURl.lowercased().starts(with: "www.") {
  7304. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  7305. }
  7306. if Nexilis.checkingAccess(key: "secure_browser") {
  7307. APIS.openUrl(url: stringURl)
  7308. } else {
  7309. guard let url = URL(string: stringURl) else { return }
  7310. UIApplication.shared.open(url)
  7311. }
  7312. }
  7313. // public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  7314. // if copySession || forwardSession || deleteSession {
  7315. // return nil
  7316. // }
  7317. // let idMe = User.getMyPin() as String?
  7318. // if (dataMessages[indexPath.row]["f_pin"] as? String != idMe) {
  7319. // return nil
  7320. // }
  7321. // let messageInfoVC = MessageInfo()
  7322. // messageInfoVC.data = dataMessages[indexPath.row]
  7323. // self.navigationController?.show(messageInfoVC, sender: nil)
  7324. // return UISwipeActionsConfiguration()
  7325. // }
  7326. //
  7327. // public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  7328. // if copySession || forwardSession || deleteSession {
  7329. // return nil
  7330. // }
  7331. // let action = UIContextualAction(style: .normal,
  7332. // title: "") { [weak self] (action, view, completionHandler) in
  7333. // self?.handleReply(indexPath: indexPath)
  7334. // completionHandler(true)
  7335. // }
  7336. // action.backgroundColor = .white
  7337. // action.image = UIImage(systemName: "arrowshape.turn.up.left.fill")?.withTintColor(.black, renderingMode: .alwaysOriginal)
  7338. // return UISwipeActionsConfiguration(actions: [action])
  7339. // }
  7340. private func handleReply(indexPath: IndexPath, dataMessagesImage: [String: Any?] = [:], reffId: String = "") {
  7341. var dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  7342. if reffId.isEmpty {
  7343. self.deleteReplyView()
  7344. if dataMessagesImage.count != 0 {
  7345. dataMessages = [dataMessagesImage]
  7346. } else {
  7347. self.textFieldSend.becomeFirstResponder()
  7348. }
  7349. self.reffId = dataMessages[indexPath.row]["message_id"] as? String
  7350. } else {
  7351. dataMessages = self.dataMessages.filter({ $0["message_id"] as? String ?? "" == reffId })
  7352. self.reffId = reffId
  7353. }
  7354. if dataMessages.count == 0 {
  7355. self.deleteReplyView()
  7356. return
  7357. }
  7358. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  7359. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
  7360. }, completion: nil)
  7361. if (self.currentIndexpath != nil) {
  7362. DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
  7363. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  7364. }
  7365. } else {
  7366. self.tableChatView.scrollToBottom()
  7367. }
  7368. self.viewTextfield.addSubview(self.containerPreviewReply)
  7369. self.containerPreviewReply.translatesAutoresizingMaskIntoConstraints = false
  7370. self.containerPreviewReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  7371. self.containerPreviewReply.topAnchor.constraint(equalTo: self.viewTextfield.topAnchor).isActive = true
  7372. if !self.containerLink.isDescendant(of: self.viewTextfield) {
  7373. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  7374. } else {
  7375. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  7376. }
  7377. self.bottomAnchorPreviewReply.isActive = true
  7378. self.containerPreviewReply.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  7379. self.containerPreviewReply.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  7380. let leftReply = UIView()
  7381. self.containerPreviewReply.addSubview(leftReply)
  7382. leftReply.translatesAutoresizingMaskIntoConstraints = false
  7383. leftReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  7384. leftReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor).isActive = true
  7385. leftReply.bottomAnchor.constraint(equalTo: self.containerPreviewReply.bottomAnchor).isActive = true
  7386. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  7387. leftReply.backgroundColor = .orangeColor
  7388. let titleReply = UILabel()
  7389. self.containerPreviewReply.addSubview(titleReply)
  7390. titleReply.translatesAutoresizingMaskIntoConstraints = false
  7391. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  7392. titleReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor, constant: 10).isActive = true
  7393. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  7394. let idMe = User.getMyPin() as String?
  7395. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7396. titleReply.text = "You".localized()
  7397. } else {
  7398. if self.isContactCenter {
  7399. let user: [User] = self.users.filter({$0.pin == dataMessages[indexPath.row]["f_pin"] as? String})
  7400. titleReply.text = user.first!.fullName
  7401. } else {
  7402. titleReply.text = self.dataPerson["name"]!!
  7403. }
  7404. }
  7405. titleReply.textColor = .orangeColor
  7406. let contentReply = UILabel()
  7407. self.containerPreviewReply.addSubview(contentReply)
  7408. contentReply.translatesAutoresizingMaskIntoConstraints = false
  7409. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  7410. contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor).isActive = true
  7411. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  7412. let message_text = dataMessages[indexPath.row]["message_text"] as? String ?? ""
  7413. let attachment_flag = dataMessages[indexPath.row]["attachment_flag"] as? String ?? ""
  7414. let thumb_chat = dataMessages[indexPath.row]["thumb_id"] as? String ?? ""
  7415. let image_chat = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  7416. let video_chat = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  7417. let file_chat = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  7418. if (attachment_flag == "0" && thumb_chat == "") {
  7419. contentReply.attributedText = message_text.richText()
  7420. } else if (attachment_flag == "1" || image_chat != "") {
  7421. if (message_text == "") {
  7422. contentReply.text = "📷 Photo".localized()
  7423. } else {
  7424. contentReply.attributedText = message_text.richText()
  7425. }
  7426. } else if (attachment_flag == "2" || video_chat != "") {
  7427. if (message_text == "") {
  7428. contentReply.text = "📹 Video".localized()
  7429. } else {
  7430. contentReply.attributedText = message_text.richText()
  7431. }
  7432. } else if (attachment_flag == "6" || file_chat != ""){
  7433. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  7434. } else if (attachment_flag == "11") {
  7435. contentReply.text = "❤️ Sticker"
  7436. }
  7437. contentReply.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .gray
  7438. let buttonCancelReply = UIButton(type: .custom)
  7439. self.containerPreviewReply.addSubview(buttonCancelReply)
  7440. buttonCancelReply.translatesAutoresizingMaskIntoConstraints = false
  7441. buttonCancelReply.trailingAnchor.constraint(equalTo: self.containerPreviewReply.trailingAnchor, constant: -10).isActive = true
  7442. buttonCancelReply.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  7443. buttonCancelReply.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  7444. buttonCancelReply.addTarget(nil, action: #selector(self.deleteReplyView), for: .touchUpInside)
  7445. buttonCancelReply.backgroundColor = .clear
  7446. buttonCancelReply.tintColor = .mainColor
  7447. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  7448. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7449. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7450. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7451. if let dirPath = paths.first {
  7452. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  7453. let image : UIImage? = {
  7454. if let img = Nexilis.imageCache.object(forKey: thumb_chat as NSString) {
  7455. return img
  7456. }
  7457. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  7458. Nexilis.imageCache.setObject(img, forKey: thumb_chat as NSString)
  7459. return img
  7460. }
  7461. return nil
  7462. }()
  7463. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  7464. let imageThumb = UIImageView(image: image)
  7465. self.containerPreviewReply.addSubview(imageThumb)
  7466. imageThumb.layer.cornerRadius = 2.0
  7467. imageThumb.clipsToBounds = true
  7468. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  7469. imageThumb.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  7470. imageThumb.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  7471. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7472. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7473. if (attachment_flag == "2") {
  7474. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  7475. imageThumb.addSubview(imagePlay)
  7476. imagePlay.clipsToBounds = true
  7477. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  7478. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  7479. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  7480. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  7481. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  7482. imagePlay.tintColor = .white
  7483. }
  7484. }
  7485. }
  7486. if (attachment_flag == "11") {
  7487. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  7488. self.containerPreviewReply.addSubview(imageSticker)
  7489. imageSticker.layer.cornerRadius = 2.0
  7490. imageSticker.clipsToBounds = true
  7491. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  7492. imageSticker.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  7493. imageSticker.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  7494. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7495. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7496. }
  7497. }
  7498. func scrollToFirstSearchMessage(indexScroll: Int = 1) {
  7499. if textSearch.count < 2 {
  7500. return
  7501. }
  7502. var lastIndex = 0
  7503. let messageTextForSearch: [[String: Any?]] = self.dataMessages.reversed()
  7504. for idx in 0..<messageTextForSearch.count {
  7505. if (messageTextForSearch[idx]["message_text"] as? String ?? "").lowercased().contains(textSearch) {
  7506. lastIndex += 1
  7507. if lastIndex < indexScroll {
  7508. continue
  7509. }
  7510. lastScrollIdxSearch = lastIndex
  7511. let section = self.dataDates.firstIndex(of: messageTextForSearch[idx]["chat_date"] as? String ?? "")
  7512. if section == nil {
  7513. return
  7514. }
  7515. let row = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == messageTextForSearch[idx]["message_id"] as? String})
  7516. if row == nil {
  7517. return
  7518. }
  7519. let indexPath = IndexPath(row: row!, section: section!)
  7520. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  7521. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  7522. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  7523. let containerMessage = cell.contentView.subviews[0]
  7524. let idMe = User.getMyPin() as String?
  7525. if (messageTextForSearch[idx]["f_pin"] as? String == idMe) {
  7526. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  7527. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7528. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  7529. containerMessage.backgroundColor = .clear
  7530. } else {
  7531. containerMessage.backgroundColor = .blueBubbleColor
  7532. }
  7533. }
  7534. } else {
  7535. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  7536. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7537. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  7538. containerMessage.backgroundColor = .clear
  7539. } else {
  7540. containerMessage.backgroundColor = .whiteBubbleColor
  7541. }
  7542. }
  7543. }
  7544. }
  7545. }
  7546. titleSearchMatches.isHidden = false
  7547. if countMatchesSearch != 0 {
  7548. if countMatchesSearch > 1 {
  7549. titleSearchMatches.text = "\(lastScrollIdxSearch) " + "of".localized() + " \(countMatchesSearch) " + "matches".localized()
  7550. } else {
  7551. titleSearchMatches.text = "\(countMatchesSearch) " + "matches".localized()
  7552. }
  7553. } else {
  7554. titleSearchMatches.text = "Not found".localized()
  7555. }
  7556. if lastScrollIdxSearch == countMatchesSearch || countMatchesSearch == 0 {
  7557. buttonUp.isEnabled = false
  7558. buttonUp.tintColor = .gray
  7559. } else {
  7560. buttonUp.isEnabled = true
  7561. buttonUp.tintColor = .mainColor
  7562. }
  7563. if countMatchesSearch == 0 || lastScrollIdxSearch == 1 || countMatchesSearch == 1 {
  7564. buttonDown.isEnabled = false
  7565. buttonDown.tintColor = .gray
  7566. } else {
  7567. buttonDown.isEnabled = true
  7568. buttonDown.tintColor = .mainColor
  7569. }
  7570. break
  7571. }
  7572. }
  7573. }
  7574. public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  7575. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  7576. if indexPath != nil {
  7577. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  7578. let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  7579. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && indexPath!.row > 0 {
  7580. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  7581. let headerView = listViewOnSection[sect]
  7582. headerView.isHidden = true
  7583. }
  7584. }
  7585. }
  7586. public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  7587. if !decelerate {
  7588. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  7589. if indexPath != nil {
  7590. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  7591. let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  7592. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  7593. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  7594. let headerView = listViewOnSection[sect]
  7595. headerView.isHidden = true
  7596. }
  7597. }
  7598. }
  7599. }
  7600. }
  7601. extension UITableView {
  7602. func scrollToBottom(isAnimated:Bool = true){
  7603. DispatchQueue.main.asyncAfter(deadline: .now() + (isAnimated ? 0 : 0.6)) { [weak self] in
  7604. guard let self = self, self.numberOfSections > 0 else { return }
  7605. let lastSection = self.numberOfSections - 1
  7606. let numberOfRows = self.numberOfRows(inSection: lastSection)
  7607. guard numberOfRows > 0 else { return }
  7608. let indexPath = IndexPath(row: numberOfRows - 1, section: lastSection)
  7609. self.scrollToRow(at: indexPath, at: .bottom, animated: isAnimated)
  7610. }
  7611. }
  7612. func scrollToTop(isAnimated:Bool = true) {
  7613. DispatchQueue.main.async {
  7614. let indexPath = IndexPath(row: 0, section: 0)
  7615. if indexPath.row != -1 {
  7616. self.scrollToRow(at: indexPath, at: .top, animated: isAnimated)
  7617. }
  7618. }
  7619. }
  7620. }
  7621. extension UIImage {
  7622. public func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
  7623. UIGraphicsBeginImageContextWithOptions(
  7624. CGSize(width: self.size.width + insets.left + insets.right,
  7625. height: self.size.height + insets.top + insets.bottom), false, self.scale)
  7626. let _ = UIGraphicsGetCurrentContext()
  7627. let origin = CGPoint(x: insets.left, y: insets.top)
  7628. self.draw(at: origin)
  7629. let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
  7630. UIGraphicsEndImageContext()
  7631. return imageWithInsets
  7632. }
  7633. }
  7634. extension EditorPersonal: UISearchBarDelegate {
  7635. public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
  7636. timerSearch?.invalidate()
  7637. if searchText.count > 1 {
  7638. timerSearch = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {[self] _ in
  7639. textSearch = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
  7640. titleSearchMatches.isHidden = true
  7641. countMatchesSearch = Chat.getCountSearchMessage(key: textSearch, pin: unique_l_pin, isPersonal: true)
  7642. tableChatView.reloadData()
  7643. scrollToFirstSearchMessage()
  7644. })
  7645. }
  7646. }
  7647. }
  7648. public class ObjectGesture: UITapGestureRecognizer {
  7649. public var message_id = ""
  7650. public var image_id = ""
  7651. public var video_id = ""
  7652. public var file_id = ""
  7653. public var audio_id = ""
  7654. public var gif_id = ""
  7655. public var imageView = UIImageView()
  7656. public var containerFile = UIView()
  7657. public var labelFile = UILabel()
  7658. public var videoURL: NSURL?
  7659. public var indexPath = IndexPath()
  7660. public var indexImageTapped: Int!
  7661. public var listImageFromGrouping: [ImageGrouping]!
  7662. public var isInitiator: Bool!
  7663. }
  7664. class navigationQLPreviewDocument: UIBarButtonItem {
  7665. var navigation = UINavigationController()
  7666. }
  7667. class segmentedControllerObject: UISegmentedControl {
  7668. var navigation = UINavigationController()
  7669. }
  7670. public class ImageGrouping {
  7671. public var messageId = ""
  7672. public var thumbId = ""
  7673. public var imageId = ""
  7674. public var status = ""
  7675. public var time = ""
  7676. public var lPin = ""
  7677. public var dataMessage: [String: Any?] = [:]
  7678. public var dataPerson: [String: String?] = [:]
  7679. public var dataGroup: [String: Any?] = [:]
  7680. public var dataTopic: [String: Any?] = [:]
  7681. public var isSelected = false
  7682. public init(messageId: String, thumbId: String, imageId: String, status: String, time: String, lPin: String, dataMessage: [String: Any?], dataPerson: [String: String?], dataGroup: [String: Any?], dataTopic: [String: Any?]) {
  7683. self.messageId = messageId
  7684. self.thumbId = thumbId
  7685. self.imageId = imageId
  7686. self.status = status
  7687. self.time = time
  7688. self.lPin = lPin
  7689. self.dataMessage = dataMessage
  7690. self.dataPerson = dataPerson
  7691. self.dataGroup = dataGroup
  7692. self.dataTopic = dataTopic
  7693. }
  7694. }
  7695. public class TypeDataMessage {
  7696. public static let message_id = "message_id"
  7697. public static let f_pin = "f_pin"
  7698. public static let l_pin = "l_pin"
  7699. public static let message_scope_id = "message_scope_id"
  7700. public static let server_date = "server_date"
  7701. public static let status = "status"
  7702. public static let message_text = "message_text"
  7703. public static let audio_id = "audio_id"
  7704. public static let video_id = "video_id"
  7705. public static let image_id = "image_id"
  7706. public static let thumb_id = "thumb_id"
  7707. public static let read_receipts = "read_receipts"
  7708. public static let chat_id = "chat_id"
  7709. public static let file_id = "file_id"
  7710. public static let attachment_flag = "attachment_flag"
  7711. public static let reff_id = "reff_id"
  7712. public static let lock = "lock"
  7713. public static let is_stared = "is_stared"
  7714. public static let blog_id = "blog_id"
  7715. public static let credential = "credential"
  7716. public static let progress = "progress"
  7717. public static let chat_date = "chat_date"
  7718. public static let is_call_center = "is_call_center"
  7719. public static let call_center_id = "call_center_id"
  7720. public static let opposite_pin = "opposite_pin"
  7721. public static let last_edit = "last_edit"
  7722. public static let gif_id = "gif_id"
  7723. public static let is_forwarded = "is_forwarded"
  7724. public static let is_secret = "is_secret"
  7725. }