EditorGroup.swift 437 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231
  1. //
  2. // EditorGroup.swift
  3. // Qmera
  4. //
  5. // Created by Akhmad Al Qindi Irsyam on 20/09/21.
  6. //
  7. import UIKit
  8. import AVKit
  9. import AVFoundation
  10. import QuickLook
  11. import Photos
  12. import NotificationBannerSwift
  13. import nuSDKService
  14. import SwiftLinkPreview
  15. import SDWebImage
  16. import PhotosUI
  17. public class EditorGroup: UIViewController, 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 constraintBottomTableViewWithTextfield: NSLayoutConstraint!
  35. @IBOutlet weak var viewAttachment: UIStackView!
  36. @IBOutlet weak var tableMention: UITableView!
  37. @IBOutlet weak var heightTableMention: NSLayoutConstraint!
  38. @IBOutlet weak var contraintBottomMention: NSLayoutConstraint!
  39. public var dataGroup: [String: Any?] = [:]
  40. public var dataTopic: [String: Any?] = [:]
  41. var dataMessages: [[String: Any?]] = []
  42. var dataDates: [String] = []
  43. public var dataMessageForward: [[String: Any?]]?
  44. var imageVideoPicker: ImageVideoPicker!
  45. var documentPicker: DocumentPicker!
  46. var currentIndexpath: IndexPath?
  47. var previewItem: NSURL?
  48. var reffId: String?
  49. var stickers = [String]()
  50. public var unique_l_pin = ""
  51. public var fromNotification = false
  52. public var referenceMessageId = ""
  53. public var referenceChatDate = ""
  54. var isHistoryCC = false
  55. var complaintId = ""
  56. var counter = 0
  57. var markerCounter: String?
  58. var buttonScrollToBottom = UIButton()
  59. let indicatorCounterBSTB = UIView()
  60. let labelCounter = UILabel()
  61. let containerActionGroup = UIView()
  62. var removed = false
  63. var isConfidential = false
  64. var isAck = false
  65. var copySession = false
  66. var forwardSession = false
  67. var deleteSession = false
  68. var isSearching = false
  69. let containerMultpileSelectSession = UIView()
  70. let viewSticker = UIView()
  71. let containerLink = UIView()
  72. let containerPreviewReply = UIView()
  73. var bottomAnchorPreviewReply = NSLayoutConstraint()
  74. let containerAction = UIView()
  75. var allowTyping = true
  76. let contactChatNav = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
  77. var searchBar: UISearchBar!
  78. var constraintBottomContainerMultpileSelectSession = NSLayoutConstraint()
  79. var titleSearchMatches: UILabel!
  80. var textSearch = ""
  81. var countMatchesSearch = 0
  82. var lastScrollIdxSearch = 0
  83. var buttonUp: UIButton!
  84. var buttonDown: UIButton!
  85. var keyboardHeightForMention: CGFloat?
  86. var listMentionWithText:[User] = []
  87. var listMentionInTextField:[User] = []
  88. var tempListMentionWithText:[User] = []
  89. var tempListMentionInTextField:[User] = []
  90. var showingLink = ""
  91. var isAlwaysHideLinkPreview = false
  92. var timerCheckLink: Timer?
  93. var lastPositionCursorMention = 0
  94. var lastTextLength = 0
  95. var timerFakeProgress: Timer?
  96. var showMenuContext = false
  97. var touchedSubview = UIView()
  98. var listViewOnSection: [UIView] = []
  99. var fakeProgMultip = 0
  100. let maxFakeProgMultip = 2
  101. var groupImages: [String:[ImageGrouping]] = [:]
  102. var titleText: String!
  103. var lastY: CGFloat = 0
  104. var listTimerCredential: [String: Int] = [:]
  105. var timerCredential: [String: Timer] = [:]
  106. var editVC = UIViewController()
  107. var editTextView = CustomTextView()
  108. var isEditingMessage = false
  109. var constraintBottomeditTextView: NSLayoutConstraint!
  110. var constraintHeighteditTextView: NSLayoutConstraint!
  111. var constraintBottomSendEditTV: NSLayoutConstraint!
  112. let locationManager = CLLocationManager()
  113. var longitude = ""
  114. var latitude = ""
  115. var isBlackCancelButton = false
  116. let buttonSendEdit = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
  117. var audioPlayers: [IndexPath: AVAudioPlayer] = [:]
  118. var timers: [IndexPath: Timer] = [:]
  119. var playingIndexPath: IndexPath?
  120. var timerSearch: Timer?
  121. var downloadList: [String: IndexPath] = [:]
  122. var transitioningDelegateRef: ZoomTransitioningDelegate?
  123. var buttonSpec = UIButton(type: .custom)
  124. var tableViewConfigFile: UITableView!
  125. var specFileString = ""
  126. var tableMentionEdit = UITableView()
  127. var heightTableEditMention: NSLayoutConstraint!
  128. func offset() -> CGFloat{
  129. guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
  130. return CGFloat(fontSize)
  131. }
  132. public override func viewDidDisappear(_ animated: Bool) {
  133. if self.isMovingFromParent {
  134. removeAllObjectBeforeDismissVC()
  135. }
  136. }
  137. private func removeAllObjectBeforeDismissVC() {
  138. for timer in self.timerCredential.values {
  139. timer.invalidate()
  140. }
  141. SecureUserDefaults.shared.removeValue(forKey: "inEditorGroup")
  142. NotificationCenter.default.removeObserver(self)
  143. self.removeFromParent()
  144. var l_pin = self.dataGroup["group_id"] as? String ?? ""
  145. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  146. l_pin = self.dataTopic["chat_id"] as? String ?? ""
  147. }
  148. var data: [String: Any] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
  149. if listMentionInTextField.count > 0 {
  150. var dataMention: [[String: String]] = []
  151. for list in listMentionInTextField {
  152. var dataTemp: [String: String] = [:]
  153. dataTemp["f_pin_mention"] = list.pin
  154. dataTemp["upper"] = list.ex_block
  155. dataMention.append(dataTemp)
  156. }
  157. data["list_mention"] = dataMention
  158. }
  159. if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
  160. let jsonString = String(data: jsonData, encoding: .utf8) {
  161. SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
  162. }
  163. }
  164. public override func viewDidAppear(_ animated: Bool) {
  165. let navBarAppearance = UINavigationBarAppearance()
  166. navBarAppearance.configureWithOpaqueBackground()
  167. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  168. navigationController?.navigationBar.standardAppearance = navBarAppearance
  169. navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
  170. navigationController?.navigationBar.isTranslucent = false
  171. navigationController?.navigationBar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  172. navigationController?.navigationBar.tintColor = .white
  173. navigationController?.navigationBar.overrideUserInterfaceStyle = .dark
  174. self.setNeedsStatusBarAppearanceUpdate()
  175. navigationController?.navigationBar.barStyle = .black
  176. if self.navigationController?.isNavigationBarHidden ?? false {
  177. self.navigationController?.setNavigationBarHidden(false, animated: false)
  178. }
  179. navigationController?.navigationBar.prefersLargeTitles = false
  180. navigationController?.navigationItem.largeTitleDisplayMode = .never
  181. updateProfile()
  182. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  183. if indexPath != nil && currentIndexpath != nil {
  184. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  185. let isPinned = headerRect.origin.y <= tableChatView.contentOffset.y
  186. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  187. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  188. let headerView = listViewOnSection[sect]
  189. headerView.isHidden = true
  190. }
  191. }
  192. }
  193. public override func viewDidLoad() {
  194. super.viewDidLoad()
  195. // navigationController?.navigationBar.topItem?.title = ""
  196. Utils.addBackground(view: contactChatNav.view)
  197. if let dataWall = UserDefaults.standard.data(forKey: "chatWallpaper") {
  198. wallpaperView.image = UIImage(data: UserDefaults.standard.data(forKey: "chatWallpaper")!)
  199. }
  200. else {
  201. wallpaperView.isHidden = true
  202. }
  203. if Nexilis.fromMAB {
  204. Nexilis.floatingButton.isHidden = true
  205. }
  206. viewButton.layer.shadowColor = self.traitCollection.userInterfaceStyle == .dark ? UIColor.white.cgColor : UIColor.gray.cgColor
  207. viewButton.layer.shadowOpacity = 1
  208. viewButton.layer.shadowOffset = .zero
  209. viewButton.layer.shadowRadius = 3
  210. viewButton.addTopBorder(with: UIColor.lightGray, andWidth: 1.0)
  211. // buttonVoice.setImage(resizeImage(image: UIImage(named: "Voice-Record", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)), for: .normal)
  212. viewAttachment.backgroundColor = .white
  213. 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)
  214. 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)
  215. 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)
  216. 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)
  217. 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)
  218. buttonSendChat.circle()
  219. buttonSendChat.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
  220. buttonSendChat.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  221. buttonAckConfidential.circle()
  222. buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
  223. buttonAckConfidential.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  224. buttonAckConfidential.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  225. textFieldSend.backgroundColor = .white
  226. textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
  227. textFieldSend.layer.borderWidth = 1.0
  228. textFieldSend.text = "Send message".localized()
  229. textFieldSend.textColor = UIColor.lightGray
  230. textFieldSend.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  231. textFieldSend.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  232. textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  233. textFieldSend.font = UIFont.systemFont(ofSize: 12 + offset())
  234. textFieldSend.delegate = self
  235. textFieldSend.customDelegate = self
  236. textFieldSend.allowsEditingTextAttributes = true
  237. navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
  238. imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self)
  239. documentPicker = DocumentPicker(presentationController: self, delegate: self)
  240. let fm = FileManager.default
  241. if Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") != nil {
  242. let path = Bundle.resourceBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  243. let items = try! fm.contentsOfDirectory(atPath: path)
  244. for item in items {
  245. if item.hasPrefix("sticker") {
  246. stickers.append(item)
  247. }
  248. }
  249. } else {
  250. let path = Bundle.resourcesMediaBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  251. let items = try! fm.contentsOfDirectory(atPath: path)
  252. for item in items {
  253. if item.hasPrefix("sticker") {
  254. stickers.append(item)
  255. }
  256. }
  257. }
  258. tableChatView.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditorGroup")
  259. loadData()
  260. setRightButtonItem()
  261. let center: NotificationCenter = NotificationCenter.default
  262. center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  263. center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  264. center.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil)
  265. center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil)
  266. center.addObserver(self, selector: #selector(onUploadChat(notification:)), name: NSNotification.Name(rawValue: "onUploadChat"), object: nil)
  267. center.addObserver(self, selector: #selector(onMemberTopic(notification:)), name: NSNotification.Name(rawValue: "onMember"), object: nil)
  268. center.addObserver(self, selector: #selector(onGroup(notification:)), name: NSNotification.Name(rawValue: "onGroup"), object: nil)
  269. center.addObserver(self, selector: #selector(onMemberTopic(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil)
  270. center.addObserver(self, selector: #selector(onFailedSendMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.failedSendMessage), object: nil)
  271. locationManager.delegate = self
  272. locationManager.requestWhenInUseAuthorization()
  273. DispatchQueue.global().async { [self] in
  274. if CLLocationManager.locationServicesEnabled() {
  275. locationManager.desiredAccuracy = kCLLocationAccuracyBest
  276. locationManager.startUpdatingLocation()
  277. } else {
  278. print("Location services are not enabled.")
  279. }
  280. }
  281. if dataMessageForward != nil {
  282. for i in 0..<dataMessageForward!.count {
  283. let isForwarded = (dataMessageForward![i][TypeDataMessage.is_forwarded] as? Int) ?? 0
  284. sendChat(message_scope_id: MessageScope.GROUP, 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: "", is_call_center: "0", call_center_id: "", viewController: self, gif_id: dataMessageForward![i][TypeDataMessage.gif_id] as? String ?? "", is_forwarded: isForwarded + 1)
  285. }
  286. dataMessageForward = nil
  287. }
  288. tableMention.register(UITableViewCell.self, forCellReuseIdentifier: "cellMention")
  289. tableMention.dataSource = self
  290. tableMention.delegate = self
  291. tableMention.contentInset = UIEdgeInsets(top: -25, left: 0, bottom: 0, right: 0)
  292. }
  293. public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  294. guard let location = locations.last else { return }
  295. latitude = "\(location.coordinate.latitude)"
  296. longitude = "\(location.coordinate.longitude)"
  297. locationManager.stopUpdatingLocation()
  298. }
  299. public func afterUnfriend() {
  300. DispatchQueue.main.async {
  301. SecureUserDefaults.shared.removeValue(forKey: "inEditorGroup")
  302. NotificationCenter.default.removeObserver(self)
  303. }
  304. }
  305. private func updateProfile() {
  306. let idMe = User.getMyPin() as String?
  307. DispatchQueue.global().async {
  308. let message = CoreMessage_TMessageBank.getBatchBuddiesInfos(p_f_pin: idMe!, last_update: 0)
  309. let _ = Nexilis.write(message: message)
  310. }
  311. }
  312. private func setRightButtonItem() {
  313. navigationItem.rightBarButtonItems = nil
  314. navigationItem.rightBarButtonItem = nil
  315. let menu = UIMenu(title: "", children: [
  316. UIAction(title: "Delete Conversation".localized(), handler: {(_) in
  317. let alert = LibAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
  318. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.default, handler: nil))
  319. alert.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: {(_) in
  320. var l_pin = self.dataGroup["group_id"] as? String ?? ""
  321. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  322. do {
  323. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  324. l_pin = self.dataTopic["chat_id"] as? String ?? ""
  325. }
  326. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "(l_pin='\(self.dataGroup["group_id"]!!)' and chat_id='\(self.dataTopic["chat_id"]!!)') and message_scope_id='4'")
  327. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(l_pin)'")
  328. SecureUserDefaults.shared.removeValue(forKey: "new_saved_\(l_pin)")
  329. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  330. if self.fromNotification {
  331. self.didTapExit()
  332. } else {
  333. self.navigationController?.popViewController(animated: true)
  334. }
  335. } catch {
  336. rollback.pointee = true
  337. print("Access database error: \(error.localizedDescription)")
  338. }
  339. })
  340. }))
  341. self.present(alert, animated: true, completion: nil)
  342. }),
  343. ])
  344. if !isHistoryCC {
  345. let moreIcon = UIBarButtonItem(image: UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), menu: menu)
  346. let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(search(sender:)))
  347. navigationItem.rightBarButtonItems = [moreIcon,buttonSearch]
  348. } else {
  349. let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(search(sender:)))
  350. navigationItem.rightBarButtonItem = buttonSearch
  351. }
  352. }
  353. @objc func search(sender: UIBarButtonItem) {
  354. self.isSearching = true
  355. if self.reffId != nil {
  356. self.deleteReplyView()
  357. }
  358. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  359. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  360. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white], for: .normal)
  361. if !self.isHistoryCC {
  362. self.navigationItem.rightBarButtonItems = nil
  363. }
  364. self.navigationItem.rightBarButtonItem = cancelButton
  365. self.changeAppBar()
  366. self.addMultipleSelectSession()
  367. }
  368. }
  369. private func getOfficialGroup() {
  370. let query = "SELECT group_id, f_name, official, image_id FROM GROUPZ where group_type = 1 AND official = 1"
  371. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  372. do {
  373. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  374. if cursorData.next() {
  375. dataGroup["group_id"] = cursorData.string(forColumnIndex: 0)
  376. dataTopic["chat_id"] = ""
  377. dataGroup["f_name"] = cursorData.string(forColumnIndex: 1)
  378. dataGroup["image_id"] = cursorData.string(forColumnIndex: 3)
  379. dataGroup["official"] = cursorData.string(forColumnIndex: 2)
  380. }
  381. cursorData.close()
  382. }
  383. } catch {
  384. rollback.pointee = true
  385. print("Access database error: \(error.localizedDescription)")
  386. }
  387. })
  388. }
  389. func loadData() {
  390. if (unique_l_pin != "") {
  391. dataDates.removeAll()
  392. dataGroup.removeAll()
  393. dataTopic.removeAll()
  394. dataMessages.removeAll()
  395. tableChatView.reloadData()
  396. currentIndexpath = nil
  397. reffId = nil
  398. getDataGroup(unique_l_pin: unique_l_pin)
  399. }
  400. if removed {
  401. removed = false
  402. containerActionGroup.removeConstraints(containerActionGroup.constraints)
  403. containerActionGroup.removeFromSuperview()
  404. setRightButtonItem()
  405. }
  406. if !isHistoryCC {
  407. let groupId = dataGroup["group_id"] as? String ?? ""
  408. let chatId = dataTopic["chat_id"] as? String ?? ""
  409. let dataGT: [String] = [groupId, chatId]
  410. SecureUserDefaults.shared.set(dataGT, forKey: "inEditorGroup")
  411. if dataTopic["chat_id"] as? String ?? "" == "" {
  412. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [groupId])
  413. sendTyping(l_pin: groupId)
  414. } else {
  415. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [chatId])
  416. sendTyping(l_pin: chatId)
  417. }
  418. } else {
  419. getOfficialGroup()
  420. disableEditor()
  421. }
  422. if fromNotification {
  423. let imageButton = UIImageView(frame: CGRect(x: -16, y: 0, width: 20, height: 44))
  424. imageButton.image = UIImage(systemName: "chevron.backward", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default))?.withTintColor(.white)
  425. imageButton.contentMode = .left
  426. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapExit))
  427. imageButton.isUserInteractionEnabled = true
  428. imageButton.addGestureRecognizer(tapGestureRecognizer)
  429. let leftItem = UIBarButtonItem(customView: imageButton)
  430. self.navigationItem.leftBarButtonItem = leftItem
  431. }
  432. changeAppBar()
  433. getData()
  434. getCounter()
  435. if counter > 0 && dataMessages.count >= counter {
  436. markerCounter = dataMessages[dataMessages.count - counter]["message_id"] as? String
  437. }
  438. tableChatView.alpha = 0
  439. tableChatView.delegate = self
  440. tableChatView.dataSource = self
  441. tableChatView.reloadData()
  442. if !referenceMessageId.isEmpty {
  443. if dataMessages.firstIndex(where: {$0["message_id"] as? String == referenceMessageId} ) != 0 {
  444. DispatchQueue.main.async {
  445. let section = self.dataDates.firstIndex(of: self.referenceChatDate)
  446. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.referenceChatDate}).firstIndex(where: { $0["message_id"] as? String == self.referenceMessageId})
  447. if row != nil && section != nil {
  448. let indexPath = IndexPath(row: row!, section: section!)
  449. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: false)
  450. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .yellow
  451. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  452. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .clear
  453. })
  454. }
  455. }
  456. }
  457. } else if counter != 0 {
  458. if dataMessages.firstIndex(where: {$0["message_id"] as? String == markerCounter} ) != 0 {
  459. DispatchQueue.main.async {
  460. let data = self.dataMessages.filter({ $0["message_id"] as? String == self.markerCounter })
  461. if data.count > 0 {
  462. let section = self.dataDates.firstIndex(of: data[0]["chat_date"] as? String ?? "")
  463. 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})
  464. self.tableChatView.scrollToRow(at: IndexPath(row: row!, section: section!), at: .bottom, animated: false)
  465. }
  466. }
  467. } else {
  468. tableChatView.scrollToTop()
  469. }
  470. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in
  471. if currentIndexpath == nil && counter != 0 {
  472. let idMe = User.getMyPin() as String?
  473. if let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == markerCounter}) {
  474. for i in idx..<dataMessages.count {
  475. if dataMessages[i]["f_pin"] as? String != idMe {
  476. sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as? String ?? "", f_pin: dataMessages[i]["f_pin"] as? String ?? "", message_scope_id: MessageScope.GROUP, message_id: dataMessages[i]["message_id"] as? String ?? "")
  477. }
  478. }
  479. counter = 0
  480. updateCounter(counter: counter)
  481. }
  482. }
  483. }
  484. } else {
  485. var l_pin = self.dataGroup["group_id"] as? String ?? ""
  486. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  487. l_pin = self.dataTopic["chat_id"] as? String ?? ""
  488. }
  489. if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
  490. let data = dataSaved
  491. if let jsonData = data.data(using: .utf8),
  492. let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] {
  493. let last_m = dataJson["text"] as? String ?? ""
  494. let last_r = dataJson["reffId"] as? String ?? ""
  495. let list_m = dataJson["list_mention"] as? [[String: String]] ?? []
  496. if !last_m.isEmpty {
  497. textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  498. }
  499. if list_m.count > 0 {
  500. for list in list_m {
  501. let f_pin = list["f_pin_mention"] ?? ""
  502. let upper = list["upper"] ?? ""
  503. let userFromBuddy = User.getData(pin: f_pin, lPin: l_pin)
  504. if userFromBuddy != nil {
  505. userFromBuddy!.ex_block = upper
  506. listMentionInTextField.append(userFromBuddy!)
  507. }
  508. }
  509. }
  510. if !last_m.isEmpty {
  511. textFieldSend.attributedText = last_m.richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "", listMentionInTextField: listMentionInTextField)
  512. }
  513. if !last_r.isEmpty {
  514. handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
  515. }
  516. }
  517. }
  518. tableChatView.scrollToBottom(isAnimated: false)
  519. }
  520. tableChatView.keyboardDismissMode = .interactive
  521. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  522. tapGesture.cancelsTouchesInView = false
  523. tableChatView.addGestureRecognizer(tapGesture)
  524. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  525. if self.tableChatView.alpha != 1.0 {
  526. UIView.animate(withDuration: 0.5, animations: {
  527. self.tableChatView.alpha = 1.0
  528. })
  529. }
  530. })
  531. for data in listTimerCredential {
  532. if data.value > 0 {
  533. var second = data.value
  534. var timer = Timer()
  535. timerCredential[data.key] = timer
  536. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  537. second -= 1
  538. self.listTimerCredential[data.key] = second
  539. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key })
  540. if (idx != nil) {
  541. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  542. 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 })
  543. if second == 0 {
  544. timer.invalidate()
  545. self.listTimerCredential.removeValue(forKey: data.key)
  546. self.timerCredential.removeValue(forKey: data.key)
  547. SecureUserDefaults.shared.removeValue(forKey: data.key)
  548. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key})
  549. if idx != nil {
  550. self.dataMessages[idx!]["lock"] = "2"
  551. self.dataMessages[idx!]["reff_id"] = ""
  552. }
  553. DispatchQueue.global().async {
  554. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  555. do {
  556. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  557. "lock" : "2"
  558. ], _where: "message_id = '\(data.key)'")
  559. } catch {
  560. rollback.pointee = true
  561. print("Access database error: \(error.localizedDescription)")
  562. }
  563. })
  564. }
  565. }
  566. if row != nil && section != nil {
  567. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  568. }
  569. }
  570. })
  571. }
  572. }
  573. }
  574. func getDataProfile(f_pin: String, message_id: String) -> [String: String]{
  575. var data: [String: String] = [:]
  576. Database.shared.database?.inTransaction({ fmdb, rollback in
  577. if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, image_id from BUDDY where f_pin = '\(f_pin)'"), c.next() {
  578. data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
  579. data["image_id"] = c.string(forColumnIndex: 1)!
  580. c.close()
  581. }
  582. else if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name, thumb_id from GROUPZ_MEMBER where f_pin = '\(f_pin)' AND group_id = '\(dataGroup["group_id"]!!)'"), c.next() {
  583. data["name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
  584. data["image_id"] = c.string(forColumnIndex: 1)!
  585. c.close()
  586. } else if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
  587. data["name"] = c.string(forColumnIndex: 0)!
  588. data["image_id"] = ""
  589. c.close()
  590. } else {
  591. data["name"] = "Unknown".localized()
  592. }
  593. })
  594. return data
  595. }
  596. private func getDataGroup(unique_l_pin: String) {
  597. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  598. do {
  599. if let cursorGroup = Database.shared.getRecords(fmdb: fmdb, query: "SELECT group_id, f_name, image_id, official, parent FROM GROUPZ WHERE group_id='\(unique_l_pin)'"), cursorGroup.next() {
  600. dataGroup["group_id"] = cursorGroup.string(forColumnIndex: 0)
  601. dataGroup["f_name"] = cursorGroup.string(forColumnIndex: 1)
  602. dataGroup["image_id"] = cursorGroup.string(forColumnIndex: 2)
  603. dataGroup["official"] = cursorGroup.string(forColumnIndex: 3)
  604. dataGroup["parent"] = cursorGroup.string(forColumnIndex: 4)
  605. dataTopic["title"] = "Lounge".localized()
  606. dataTopic["chat_id"] = ""
  607. cursorGroup.close()
  608. } else if let cursorTopic = Database.shared.getRecords(fmdb: fmdb, query: "SELECT group_id, title FROM DISCUSSION_FORUM where chat_id = '\(unique_l_pin)'"), cursorTopic.next() {
  609. dataGroup["group_id"] = cursorTopic.string(forColumnIndex: 0)
  610. dataTopic["title"] = cursorTopic.string(forColumnIndex: 1)
  611. dataTopic["chat_id"] = unique_l_pin
  612. cursorTopic.close()
  613. if let cursorGroup = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_name, image_id, official, parent FROM GROUPZ where group_id = '\(dataGroup["group_id"] as? String ?? "")'"), cursorGroup.next() {
  614. dataGroup["f_name"] = cursorGroup.string(forColumnIndex: 0)
  615. dataGroup["image_id"] = cursorGroup.string(forColumnIndex: 1)
  616. dataGroup["official"] = cursorGroup.string(forColumnIndex: 2)
  617. dataGroup["parent"] = cursorGroup.string(forColumnIndex: 3)
  618. cursorGroup.close()
  619. }
  620. }
  621. } catch {
  622. rollback.pointee = true
  623. print("Access database error: \(error.localizedDescription)")
  624. }
  625. })
  626. }
  627. private func getData() {
  628. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  629. do {
  630. 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, last_edited, gif_id, is_forwarded_message, attachment_speciality FROM MESSAGE where chat_id='' AND l_pin='\(dataGroup["group_id"] as? String ?? "")' order by server_date asc"
  631. if isHistoryCC {
  632. 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 call_center_id='\(complaintId)' order by server_date asc"
  633. } else if (dataTopic["chat_id"] as? String ?? "" != "") {
  634. 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, last_edited, gif_id, is_forwarded_message FROM MESSAGE where chat_id='\(dataTopic["chat_id"] as? String ?? "")' order by server_date asc"
  635. }
  636. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  637. var tempImages: [ImageGrouping] = []
  638. while cursorData.next() {
  639. var row: [String: Any?] = [:]
  640. row["message_id"] = cursorData.string(forColumnIndex: 0)
  641. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  642. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  643. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  644. row["server_date"] = cursorData.string(forColumnIndex: 4)
  645. row["status"] = cursorData.string(forColumnIndex: 5)
  646. row["message_text"] = cursorData.string(forColumnIndex: 6)
  647. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  648. row["video_id"] = cursorData.string(forColumnIndex: 8)
  649. row["image_id"] = cursorData.string(forColumnIndex: 9)
  650. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  651. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  652. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  653. row["file_id"] = cursorData.string(forColumnIndex: 13)
  654. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  655. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  656. row["lock"] = cursorData.string(forColumnIndex: 16)
  657. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  658. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  659. row["credential"] = cursorData.string(forColumnIndex: 19)
  660. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 20)
  661. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 21)
  662. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 22))
  663. row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 23)
  664. row["isSelected"] = false
  665. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  666. let idMe = User.getMyPin()!
  667. if row["f_pin"] as? String ?? "" == idMe {
  668. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  669. if second > 60 {
  670. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  671. row["lock"] = "2"
  672. row["reff_id"] = ""
  673. DispatchQueue.global().async {
  674. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  675. do {
  676. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  677. "lock" : "2"
  678. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  679. } catch {
  680. rollback.pointee = true
  681. print("Access database error: \(error.localizedDescription)")
  682. }
  683. })
  684. }
  685. } else {
  686. let second = 60 - second
  687. listTimerCredential[row["message_id"] as? String ?? ""] = second
  688. }
  689. } else {
  690. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  691. if hasMessageId != nil {
  692. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  693. if second > 60 {
  694. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  695. row["lock"] = "2"
  696. row["reff_id"] = ""
  697. DispatchQueue.global().async {
  698. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  699. do {
  700. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  701. "lock" : "2"
  702. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  703. } catch {
  704. rollback.pointee = true
  705. print("Access database error: \(error.localizedDescription)")
  706. }
  707. })
  708. }
  709. } else {
  710. let second = 60 - second
  711. listTimerCredential[row["message_id"] as? String ?? ""] = second
  712. }
  713. } else {
  714. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  715. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  716. }
  717. }
  718. }
  719. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  720. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  721. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  722. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  723. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  724. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  725. if let dirPath = paths.first {
  726. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  727. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  728. if ((row["video_id"] as? String ?? "") != "") {
  729. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: row["video_id"] as? String ?? ""){
  730. row["progress"] = 100.0
  731. } else {
  732. row["progress"] = 0.0
  733. }
  734. } else {
  735. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: row["file_id"] as? String ?? ""){
  736. row["progress"] = 100.0
  737. } else {
  738. row["progress"] = 0.0
  739. }
  740. }
  741. }
  742. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  743. 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 ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["message_text"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["reff_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["read_receipts"] as? String ?? "") != "8" {
  744. 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 {
  745. if tempImages.count >= 4 {
  746. groupImages[tempImages[0].messageId] = tempImages
  747. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  748. for _ in 1..<tempImages.count {
  749. dataMessages.remove(at: idxTemp + 1)
  750. }
  751. }
  752. }
  753. tempImages.removeAll()
  754. }
  755. 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: [:], dataGroup: dataGroup, dataTopic: dataTopic))
  756. } else if tempImages.count >= 4 {
  757. groupImages[tempImages[0].messageId] = tempImages
  758. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  759. for _ in 1..<tempImages.count {
  760. dataMessages.remove(at: idxTemp + 1)
  761. }
  762. }
  763. tempImages.removeAll()
  764. } else if tempImages.count != 0 {
  765. tempImages.removeAll()
  766. }
  767. dataMessages.append(row)
  768. }
  769. // if isHistoryCC {
  770. // dataMessages.remove(at: 0)
  771. // }
  772. if tempImages.count >= 4 {
  773. if tempImages.count > 30 {
  774. tempImages.removeSubrange(30..<tempImages.count)
  775. }
  776. groupImages[tempImages[0].messageId] = tempImages
  777. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  778. for _ in 1..<tempImages.count {
  779. dataMessages.remove(at: idxTemp + 1)
  780. }
  781. }
  782. }
  783. cursorData.close()
  784. }
  785. } catch {
  786. rollback.pointee = true
  787. print("Access database error: \(error.localizedDescription)")
  788. }
  789. })
  790. }
  791. func getSecondsDifferenceFromTwoDates(start: Date, end: Date) -> Int {
  792. let diff = Int(end.timeIntervalSince1970 - start.timeIntervalSince1970)
  793. let hours = diff / 3600
  794. let seconds = (diff - hours * 3600)
  795. return seconds
  796. }
  797. private func getRealStatus(messageId: String) -> String {
  798. var status = "1"
  799. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  800. do {
  801. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status, f_pin FROM MESSAGE_STATUS WHERE message_id='\(messageId)'") {
  802. var listStatus: [Int] = []
  803. while cursorStatus.next() {
  804. listStatus.append(Int(cursorStatus.string(forColumnIndex: 0)!)!)
  805. }
  806. cursorStatus.close()
  807. status = "\(listStatus.min() ?? 2)"
  808. }
  809. } catch {
  810. rollback.pointee = true
  811. print("Access database error: \(error.localizedDescription)")
  812. }
  813. })
  814. return status
  815. }
  816. private func chatDate(stringDate: String) -> String {
  817. let date = Date(milliseconds: Int64(stringDate)!)
  818. let calendar = Calendar.current
  819. if (calendar.isDateInToday(date)) {
  820. if !dataDates.contains("Today".localized()){
  821. dataDates.append("Today".localized())
  822. }
  823. return "Today".localized()
  824. } else {
  825. let startOfNow = calendar.startOfDay(for: Date())
  826. let startOfTimeStamp = calendar.startOfDay(for: date)
  827. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  828. let day = -(components.day!)
  829. if day == 1{
  830. if !dataDates.contains("Yesterday".localized()){
  831. dataDates.append("Yesterday".localized())
  832. }
  833. return "Yesterday".localized()
  834. } else if day < 7 {
  835. let formatter = DateFormatter()
  836. formatter.dateFormat = "EEEE"
  837. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  838. if lang == "id" {
  839. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  840. }
  841. if !dataDates.contains(formatter.string(from: date)){
  842. dataDates.append(formatter.string(from: date))
  843. }
  844. return formatter.string(from: date)
  845. } else {
  846. let formatter = DateFormatter()
  847. formatter.dateFormat = "EE, dd MMM"
  848. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  849. if lang == "id" {
  850. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  851. }
  852. let stringFormat = formatter.string(from: date as Date)
  853. if !dataDates.contains(stringFormat){
  854. dataDates.append(stringFormat)
  855. }
  856. return stringFormat
  857. }
  858. }
  859. }
  860. private func changeAppBar() {
  861. let viewAppBar = UIView()
  862. viewAppBar.frame.size = CGSize(width: self.view.frame.size.width, height: 44)
  863. if !isSearching {
  864. let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  865. imageProfile.circle()
  866. imageProfile.clipsToBounds = true
  867. viewAppBar.addSubview(imageProfile)
  868. let pictureImage = dataGroup["image_id"] ?? ""
  869. if (pictureImage as? String ?? "" != "" && pictureImage != nil) {
  870. imageProfile.setImage(name: pictureImage! as? String ?? "")
  871. imageProfile.contentMode = .scaleAspectFill
  872. } else {
  873. imageProfile.image = UIImage(systemName: "person.3")
  874. imageProfile.contentMode = .scaleAspectFit
  875. imageProfile.backgroundColor = .lightGray
  876. }
  877. var widthTitle = viewAppBar.frame.size.width - 180
  878. if isHistoryCC {
  879. widthTitle = viewAppBar.frame.size.width - 150
  880. }
  881. let titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: widthTitle, height: 44))
  882. viewAppBar.addSubview(titleNavigation)
  883. if (dataGroup["official"] as? String ?? "" == "1") {
  884. if !isHistoryCC {
  885. titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(dataGroup["f_name"]!!) (\(dataTopic["title"]!!))", size: 15, y: -4)
  886. } else {
  887. titleNavigation.text = (dataGroup["f_name"] as? String)! + " " + "Contact Center".localized()
  888. }
  889. } else {
  890. titleNavigation.text = (dataGroup["f_name"] as? String ?? "") + " (\(dataTopic["title"] as? String ?? ""))"
  891. }
  892. titleNavigation.textColor = .white
  893. titleNavigation.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  894. navigationItem.titleView = viewAppBar
  895. titleText = titleNavigation.text
  896. } else {
  897. searchBar = UISearchBar()
  898. searchBar.autocapitalizationType = .none
  899. searchBar.delegate = self
  900. searchBar.searchTextField.tintColor = .mainColor
  901. searchBar.searchTextField.textColor = .mainColor
  902. // searchBar.updateHeight(height: 36, radius: 18)
  903. searchBar.showsCancelButton = false
  904. // searchBar.setMagnifyingGlassColorTo(color: .white)
  905. searchBar.setImage(UIImage(), for: .search, state: .normal)
  906. searchBar.setPositionAdjustment(UIOffset(horizontal: 10, vertical: 0), for: .search)
  907. searchBar.setCustomBackgroundImage(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
  908. navigationItem.titleView = searchBar
  909. self.definesPresentationContext = true
  910. }
  911. if copySession || forwardSession || deleteSession || isSearching {
  912. navigationItem.hidesBackButton = true
  913. navigationController?.interactivePopGestureRecognizer?.isEnabled = false
  914. } else {
  915. navigationItem.hidesBackButton = false
  916. navigationController?.interactivePopGestureRecognizer?.isEnabled = true
  917. }
  918. viewAppBar.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(seeProfileTapped)))
  919. }
  920. func updateProgress(_ data: [AnyHashable: Any]){
  921. var isImage = false
  922. var idx = dataMessages.lastIndex(where: { $0["video_id"] as? String ?? "" == data["name"] as? String ?? "" || $0["video_id"] as? String == data["video_id"] as? String })
  923. if (idx == nil) {
  924. idx = dataMessages.lastIndex(where: { $0["image_id"] as? String ?? "" == data["name"] as? String ?? "" || $0["image_id"] as? String == data["image_id"] as? String })
  925. isImage = true
  926. }
  927. if (idx != nil) {
  928. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  929. if section == nil {
  930. return
  931. }
  932. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String ?? "" == dataMessages[idx!]["message_id"] as? String ?? ""})
  933. if row == nil {
  934. return
  935. }
  936. DispatchQueue.main.async {
  937. let indexPath = IndexPath(row: row!, section: section!)
  938. if(self.fakeProgMultip < self.maxFakeProgMultip){
  939. self.fakeProgMultip = self.fakeProgMultip + 1
  940. }
  941. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  942. let progress = max(data["progress"] as! Double, fakeProgress)
  943. if(data["progress"] as! Double == 100.0){
  944. self.fakeProgMultip = 0
  945. }
  946. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  947. for view in cell.contentView.subviews {
  948. if !(view is UITextView) && !(view is UIImageView) {
  949. for viewInContainer in view.subviews {
  950. if viewInContainer is UIImageView {
  951. if viewInContainer.subviews.count == 0 {
  952. return
  953. }
  954. var containerView : UIView?
  955. if (isImage) {
  956. containerView = viewInContainer.subviews[0]
  957. } else if viewInContainer.subviews.count > 1 {
  958. containerView = viewInContainer.subviews[1]
  959. }
  960. if let loading = containerView?.layer.sublayers?[1] as? CAShapeLayer {
  961. loading.strokeEnd = CGFloat(progress / 100)
  962. if (progress == 100.0) {
  963. self.dataMessages[idx!]["progress"] = progress
  964. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  965. }
  966. }
  967. }
  968. }
  969. }
  970. }
  971. }
  972. }
  973. } else {
  974. idx = dataMessages.lastIndex(where: { $0["file_id"] as? String ?? "" == data["name"] as? String ?? "" || $0["file_id"] as? String == data["file_id"] as? String })
  975. if (idx != nil) {
  976. DispatchQueue.main.async {
  977. let section = 0
  978. let indexPath = IndexPath(row: idx!, section: section)
  979. if(self.fakeProgMultip < self.maxFakeProgMultip){
  980. self.fakeProgMultip = self.fakeProgMultip + 1
  981. }
  982. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  983. let progress = max(data["progress"] as! Double, fakeProgress)
  984. if(data["progress"] as! Double == 100.0){
  985. self.fakeProgMultip = 0
  986. }
  987. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  988. for view in cell.contentView.subviews {
  989. if !(view is UITextView) && !(view is UIImageView) {
  990. for viewSubviews in view.subviews {
  991. if !(viewSubviews is UITextView) {
  992. for viewInContainer in viewSubviews.subviews {
  993. if !(viewInContainer is UITextView) && !(viewInContainer is UIImageView) {
  994. if let cont = viewInContainer.layer.sublayers {
  995. if cont.count < 2 {
  996. return
  997. }
  998. }
  999. if let layers = viewInContainer.layer.sublayers {
  1000. if let loading = layers [1] as? CAShapeLayer {
  1001. loading.strokeEnd = CGFloat(progress / 100)
  1002. if (progress == 100.0) {
  1003. self.dataMessages[idx!]["progress"] = progress
  1004. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1005. }
  1006. }
  1007. }
  1008. }
  1009. }
  1010. }
  1011. }
  1012. }
  1013. }
  1014. }
  1015. }
  1016. }
  1017. }
  1018. }
  1019. @objc func onUploadChat(notification: NSNotification) {
  1020. let data:[AnyHashable : Any] = notification.userInfo!
  1021. updateProgress(data)
  1022. }
  1023. @objc func onReceiveMessage(notification: NSNotification) {
  1024. DispatchQueue.main.async {
  1025. let data:[AnyHashable : Any] = notification.userInfo!
  1026. if let dataMessage = data["message"] as? TMessage {
  1027. let chatData = dataMessage.mBodies
  1028. let group_id = self.dataGroup["group_id"] as? String ?? ""
  1029. let chat_id = self.dataTopic["chat_id"] as? String ?? ""
  1030. if chatData[CoreMessage_TMessageKey.L_PIN] == group_id && (chatData[CoreMessage_TMessageKey.CHAT_ID] ?? "") == chat_id {
  1031. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]})
  1032. if idx != nil {
  1033. self.dataMessages[idx!][TypeDataMessage.message_text] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1034. self.dataMessages[idx!][TypeDataMessage.last_edit] = Int64(chatData[CoreMessage_TMessageKey.LAST_EDIT]!)
  1035. self.dataMessages[idx!][TypeDataMessage.status] = chatData[CoreMessage_TMessageKey.STATUS]
  1036. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1037. 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 })
  1038. if row != nil && section != nil {
  1039. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1040. }
  1041. return
  1042. }
  1043. var row: [String: Any?] = [:]
  1044. row["message_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_ID]
  1045. row["f_pin"] = chatData[CoreMessage_TMessageKey.F_PIN]
  1046. row["l_pin"] = chatData[CoreMessage_TMessageKey.L_PIN]
  1047. row["message_scope_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]
  1048. row["server_date"] = chatData[CoreMessage_TMessageKey.SERVER_DATE]
  1049. row["status"] = chatData[CoreMessage_TMessageKey.STATUS]
  1050. row["message_text"] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1051. if (chatData.keys.contains(CoreMessage_TMessageKey.AUDIO_ID)) {
  1052. row["audio_id"] = chatData[CoreMessage_TMessageKey.AUDIO_ID]
  1053. } else {
  1054. row["audio_id"] = ""
  1055. }
  1056. if (chatData.keys.contains(CoreMessage_TMessageKey.GIF_ID)) {
  1057. row["gif_id"] = chatData[CoreMessage_TMessageKey.GIF_ID]
  1058. } else {
  1059. row["gif_id"] = ""
  1060. }
  1061. if (chatData.keys.contains(CoreMessage_TMessageKey.VIDEO_ID)) {
  1062. row["video_id"] = chatData[CoreMessage_TMessageKey.VIDEO_ID]
  1063. } else {
  1064. row["video_id"] = ""
  1065. }
  1066. if (chatData.keys.contains(CoreMessage_TMessageKey.IMAGE_ID)) {
  1067. row["image_id"] = chatData[CoreMessage_TMessageKey.IMAGE_ID]
  1068. } else {
  1069. row["image_id"] = ""
  1070. }
  1071. if (chatData.keys.contains(CoreMessage_TMessageKey.THUMB_ID)) {
  1072. row["thumb_id"] = chatData[CoreMessage_TMessageKey.THUMB_ID]
  1073. } else {
  1074. row["thumb_id"] = ""
  1075. }
  1076. if (chatData.keys.contains(CoreMessage_TMessageKey.CHAT_ID)) {
  1077. row["chat_id"] = chatData[CoreMessage_TMessageKey.CHAT_ID]
  1078. } else {
  1079. row["chat_id"] = ""
  1080. }
  1081. if (chatData.keys.contains(CoreMessage_TMessageKey.FILE_ID)) {
  1082. row["file_id"] = chatData[CoreMessage_TMessageKey.FILE_ID]
  1083. } else {
  1084. row["file_id"] = ""
  1085. }
  1086. if (chatData.keys.contains(CoreMessage_TMessageKey.READ_RECEIPTS)) {
  1087. row["read_receipts"] = chatData[CoreMessage_TMessageKey.READ_RECEIPTS]
  1088. } else {
  1089. row["read_receipts"] = ""
  1090. }
  1091. if (chatData.keys.contains(CoreMessage_TMessageKey.CREDENTIAL)) {
  1092. row["credential"] = chatData[CoreMessage_TMessageKey.CREDENTIAL]
  1093. } else {
  1094. row["credential"] = ""
  1095. }
  1096. row["progress"] = 0.0
  1097. row["attachment_flag"] = chatData[CoreMessage_TMessageKey.ATTACHMENT_FLAG]
  1098. row["reff_id"] = chatData[CoreMessage_TMessageKey.REF_ID] ?? ""
  1099. row["lock"] = ""
  1100. row["is_stared"] = "0"
  1101. row[TypeDataMessage.is_forwarded] = Int(chatData[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] ?? "0")
  1102. row[TypeDataMessage.spec_file] = chatData[CoreMessage_TMessageKey.ATTACHMENT_SPECIALITY]
  1103. row["isSelected"] = false
  1104. if !self.dataDates.contains("Today".localized()){
  1105. self.dataDates.append("Today".localized())
  1106. self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .fade)
  1107. }
  1108. row["chat_date"] = "Today".localized()
  1109. row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
  1110. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1111. self.listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1112. }
  1113. self.counter += 1
  1114. self.tableChatView.beginUpdates()
  1115. self.dataMessages.append(row)
  1116. 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: .fade)
  1117. self.tableChatView.endUpdates()
  1118. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1119. var timer = Timer()
  1120. var minute = 60
  1121. self.timerCredential[row["message_id"] as? String ?? ""] = timer
  1122. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1123. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  1124. minute -= 1
  1125. self.listTimerCredential[row["message_id"] as? String ?? ""] = minute
  1126. if minute == 0 {
  1127. timer.invalidate()
  1128. self.listTimerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1129. self.timerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1130. SecureUserDefaults.shared.removeValue(forKey: row["message_id"] as? String ?? "")
  1131. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == row["message_id"] as? String})
  1132. if idx != nil {
  1133. self.dataMessages[idx!]["lock"] = "2"
  1134. self.dataMessages[idx!]["reff_id"] = ""
  1135. }
  1136. DispatchQueue.global().async {
  1137. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1138. do {
  1139. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1140. "lock" : "2"
  1141. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1142. } catch {
  1143. rollback.pointee = true
  1144. print("Access database error: \(error.localizedDescription)")
  1145. }
  1146. })
  1147. }
  1148. }
  1149. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  1150. 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})
  1151. let indexPath = IndexPath(row: row!, section: section!)
  1152. if row != nil && section != nil{
  1153. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1154. }
  1155. })
  1156. }
  1157. if self.currentIndexpath?.row == (self.dataMessages.count - 2) {
  1158. if (self.viewIfLoaded?.window != nil) {
  1159. self.sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as? String ?? "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1160. }
  1161. self.tableChatView.scrollToBottom()
  1162. 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) {
  1163. self.counter = 0
  1164. self.updateCounter(counter: self.counter)
  1165. }
  1166. let lastMarkerCounter = self.markerCounter
  1167. if self.markerCounter != nil {
  1168. self.markerCounter = nil
  1169. }
  1170. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  1171. if indexMessage != nil {
  1172. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1173. 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 })
  1174. if row != nil && section != nil {
  1175. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1176. }
  1177. }
  1178. }
  1179. else if self.currentIndexpath == nil {
  1180. self.counter = 0
  1181. self.updateCounter(counter: self.counter)
  1182. if (self.viewIfLoaded?.window != nil) {
  1183. self.sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as? String ?? "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1184. }
  1185. }
  1186. else if self.counter != 0 {
  1187. if !self.indicatorCounterBSTB.isDescendant(of: self.view) && self.buttonScrollToBottom.isDescendant(of: self.view) {
  1188. self.markerCounter = row["message_id"] as? String
  1189. self.addCounterAtButttonScrollToBottom()
  1190. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.markerCounter })
  1191. if indexMessage != nil {
  1192. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1193. 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 })
  1194. if row != nil && section != nil {
  1195. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1196. }
  1197. }
  1198. } else if self.indicatorCounterBSTB.isDescendant(of: self.view) {
  1199. self.labelCounter.text = "\(self.counter)"
  1200. }
  1201. }
  1202. }
  1203. } else {
  1204. // NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1205. }
  1206. }
  1207. }
  1208. @objc func onStatusChat(notification: NSNotification) {
  1209. DispatchQueue.main.async {
  1210. let data:[AnyHashable : Any] = notification.userInfo!
  1211. if let dataMessage = data["message"] as? TMessage {
  1212. let idMe = User.getMyPin() as String?
  1213. let chatData = dataMessage.mBodies
  1214. if (chatData[CoreMessage_TMessageKey.F_PIN] == idMe || chatData[CoreMessage_TMessageKey.L_PIN] == self.dataGroup["group_id"] as? String || chatData[CoreMessage_TMessageKey.F_PIN] == self.dataGroup["group_id"] as? String) && chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == MessageScope.GROUP {
  1215. if (chatData.keys.contains(CoreMessage_TMessageKey.MESSAGE_ID) && !(chatData[CoreMessage_TMessageKey.MESSAGE_ID]!).contains("-2,")) {
  1216. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! })
  1217. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) }) {
  1218. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) {
  1219. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1220. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1221. }
  1222. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1223. }
  1224. if (idx != nil) {
  1225. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1226. self.updateStatusDelete(idx: idx, chatData: chatData)
  1227. } else {
  1228. self.updateStatusMessage(idx: idx, chatData: chatData)
  1229. }
  1230. }
  1231. }
  1232. else if (chatData.keys.contains("message_id")) {
  1233. var idMessage = dataMessage.getBody(key: "message_id")
  1234. if idMessage.contains("'") {
  1235. idMessage = idMessage.replacingOccurrences(of: "'", with: "")
  1236. }
  1237. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == idMessage })
  1238. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == idMessage }) }) {
  1239. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == idMessage }) {
  1240. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1241. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1242. }
  1243. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1244. }
  1245. if (idx != nil) {
  1246. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1247. self.updateStatusDelete(idx: idx, chatData: chatData)
  1248. } else {
  1249. self.updateStatusMessage(idx: idx, chatData: chatData)
  1250. }
  1251. }
  1252. }
  1253. else {
  1254. let listMessageId = chatData[CoreMessage_TMessageKey.MESSAGE_ID]!.split(separator: ",")
  1255. for i in 1..<listMessageId.count {
  1256. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == listMessageId[i] })
  1257. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == listMessageId[i] }) }) {
  1258. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == listMessageId[i] }) {
  1259. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1260. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1261. }
  1262. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1263. }
  1264. if (idx != nil) {
  1265. self.updateStatusMessage(idx: idx, chatData: chatData)
  1266. }
  1267. }
  1268. }
  1269. }
  1270. }
  1271. }
  1272. }
  1273. @objc func onFailedSendMessage(notification: NSNotification) {
  1274. DispatchQueue.main.async {
  1275. let data:[AnyHashable : Any] = notification.userInfo!
  1276. let messageId = data["message_id"] as? String ?? ""
  1277. let status = data["status"] as? String ?? ""
  1278. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  1279. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  1280. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  1281. self.groupImages[idxMessageIdParent].value[idxInImages].status = status
  1282. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = status
  1283. }
  1284. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1285. }
  1286. if (idx != nil) {
  1287. do {
  1288. self.dataMessages[idx!]["status"] = status
  1289. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1290. 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 })
  1291. if row != nil && section != nil {
  1292. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1293. }
  1294. } catch {
  1295. }
  1296. }
  1297. }
  1298. }
  1299. private func updateStatusDelete(idx: Int?, chatData: [String: String]) {
  1300. do {
  1301. if self.dataMessages[idx!]["lock"] != nil && self.dataMessages[idx!]["lock"] as? String ?? "" == "1" {
  1302. return
  1303. }
  1304. self.dataMessages[idx!]["lock"] = "1"
  1305. self.dataMessages[idx!]["reff_id"] = ""
  1306. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1307. 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 ?? "" })
  1308. if row != nil && section != nil {
  1309. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1310. }
  1311. if self.listTimerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""] != nil {
  1312. self.listTimerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1313. self.timerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""]?.invalidate()
  1314. self.timerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1315. SecureUserDefaults.shared.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1316. }
  1317. if self.reffId != nil && self.reffId == chatData["message_id"]! {
  1318. self.deleteReplyView()
  1319. }
  1320. } catch {
  1321. }
  1322. }
  1323. private func updateStatusMessage(idx: Int?, chatData: [String: String]) {
  1324. do {
  1325. if Int(self.dataMessages[idx!]["status"] as? String ?? "")! > Int(chatData[CoreMessage_TMessageKey.STATUS]!)! {
  1326. return
  1327. }
  1328. self.dataMessages[idx!]["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1329. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1330. 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 ?? "" })
  1331. if row != nil && section != nil {
  1332. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1333. }
  1334. } catch {
  1335. }
  1336. }
  1337. @objc func onMemberTopic(notification: NSNotification) {
  1338. let data:[AnyHashable : Any] = notification.userInfo!
  1339. DispatchQueue.main.async { [self] in
  1340. if data["member"] == nil || data["code"] as? String ?? "" == CoreMessage_TMessageCode.EXIT_GROUP && data["member"] as? String ?? "" == User.getMyPin()! && data["groupId"] as? String ?? "" == self.dataGroup["group_id"] as? String ?? "" && !containerActionGroup.isDescendant(of: self.view) {
  1341. dismissKeyboard()
  1342. let labelKicked = UILabel()
  1343. if data["member"] == nil && data["code"] as? String ?? "" == CoreMessage_TMessageCode.DELETE_CHAT && data["topicId"] as? String ?? "" == dataTopic["chat_id"] as? String ?? "" {
  1344. labelKicked.text = "This topic has been deleted".localized()
  1345. } else if data["member"] != nil && data["member"] as? String ?? "" == data["f_pin"] as? String ?? "" {
  1346. labelKicked.text = "You have left this group".localized()
  1347. } else if data["member"] != nil {
  1348. labelKicked.text = "You have been removed from this group".localized()
  1349. } else if data["code"] as? String ?? "" == CoreMessage_TMessageCode.UPDATE_CHAT {
  1350. dataGroup.removeAll()
  1351. dataTopic.removeAll()
  1352. getDataGroup(unique_l_pin: unique_l_pin)
  1353. changeAppBar()
  1354. return
  1355. } else {
  1356. return
  1357. }
  1358. removed = true
  1359. cancelAction()
  1360. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: { [self] in
  1361. navigationItem.rightBarButtonItem = nil
  1362. view.addSubview(containerActionGroup)
  1363. containerActionGroup.translatesAutoresizingMaskIntoConstraints = false
  1364. NSLayoutConstraint.activate([
  1365. containerActionGroup.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1366. containerActionGroup.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1367. containerActionGroup.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1368. containerActionGroup.heightAnchor.constraint(equalToConstant: 120)
  1369. ])
  1370. containerActionGroup.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1371. containerActionGroup.addSubview(labelKicked)
  1372. labelKicked.translatesAutoresizingMaskIntoConstraints = false
  1373. NSLayoutConstraint.activate([
  1374. labelKicked.centerYAnchor.constraint(equalTo: containerActionGroup.centerYAnchor),
  1375. labelKicked.centerXAnchor.constraint(equalTo: containerActionGroup.centerXAnchor),
  1376. ])
  1377. labelKicked.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1378. labelKicked.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1379. if contactChatNav.viewIfLoaded?.window != nil {
  1380. contactChatNav.dismiss(animated: true)
  1381. }
  1382. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  1383. if self.fromNotification {
  1384. self.didTapExit()
  1385. } else {
  1386. self.navigationController?.popViewController(animated: true)
  1387. }
  1388. })
  1389. })
  1390. }
  1391. }
  1392. }
  1393. @objc func onGroup(notification: NSNotification) {
  1394. let data:[AnyHashable : Any] = notification.userInfo!
  1395. if data["code"] as? String ?? "" == "A010" && data["groupId"] as? String ?? "" == self.dataGroup["group_id"] as? String ?? "" {
  1396. DispatchQueue.main.async {
  1397. Database.shared.database?.inTransaction({ fmdb, rollback in
  1398. if let c = Database().getRecords(fmdb: fmdb, query: "select f_name, image_id from GROUPZ where group_id = '\(self.dataGroup["group_id"]!!)'"), c.next() {
  1399. self.dataGroup["f_name"] = c.string(forColumnIndex: 0)!.trimmingCharacters(in: .whitespacesAndNewlines)
  1400. self.dataGroup["image_id"] = c.string(forColumnIndex: 1)!
  1401. c.close()
  1402. }
  1403. })
  1404. self.changeAppBar()
  1405. }
  1406. }
  1407. }
  1408. @IBAction func voiceTapped(_ sender: UIButton) {
  1409. if (self.constraintBottomAttachment.constant != 0.0) {
  1410. constraintBottomAttachment.constant = 0.0
  1411. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1412. self.viewSticker.removeFromSuperview()
  1413. }
  1414. }
  1415. @IBAction func imageTapped(_ sender: UIButton) {
  1416. if (self.constraintBottomAttachment.constant != 0.0) {
  1417. constraintBottomAttachment.constant = 0.0
  1418. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1419. self.viewSticker.removeFromSuperview()
  1420. }
  1421. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  1422. if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
  1423. alertController.addAction(action)
  1424. }
  1425. if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
  1426. alertController.addAction(action)
  1427. }
  1428. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  1429. self.present(alertController, animated: true)
  1430. }
  1431. private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
  1432. return UIAlertAction(title: title, style: .default) { [unowned self] _ in
  1433. switch type {
  1434. case "image":
  1435. var config = PHPickerConfiguration()
  1436. config.filter = .images
  1437. config.preferredAssetRepresentationMode = .automatic
  1438. let picker = PHPickerViewController(configuration: config)
  1439. picker.delegate = self
  1440. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  1441. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  1442. }
  1443. if !isBlackCancelButton {
  1444. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  1445. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  1446. }
  1447. present(picker, animated: true, completion: nil)
  1448. case "video":
  1449. var config = PHPickerConfiguration()
  1450. config.filter = .videos
  1451. config.preferredAssetRepresentationMode = .automatic
  1452. let picker = PHPickerViewController(configuration: config)
  1453. picker.delegate = self
  1454. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  1455. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  1456. }
  1457. if !isBlackCancelButton {
  1458. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  1459. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  1460. }
  1461. present(picker, animated: true, completion: nil)
  1462. case "imageCamera":
  1463. imageVideoPicker.present(source: .imageCamera)
  1464. case "videoCamera":
  1465. imageVideoPicker.present(source: .videoCamera)
  1466. default:
  1467. imageVideoPicker.present(source: .imageAlbum)
  1468. }
  1469. }
  1470. }
  1471. @IBAction func photoTapped(_ sender: UIButton) {
  1472. if (self.constraintBottomAttachment.constant != 0.0) {
  1473. constraintBottomAttachment.constant = 0.0
  1474. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1475. self.viewSticker.removeFromSuperview()
  1476. }
  1477. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  1478. if let action = self.actionImageVideo(for: "imageCamera", title: "Take Photo".localized()) {
  1479. alertController.addAction(action)
  1480. }
  1481. if let action = self.actionImageVideo(for: "videoCamera", title: "Take Video".localized()) {
  1482. alertController.addAction(action)
  1483. }
  1484. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  1485. self.present(alertController, animated: true)
  1486. }
  1487. @IBAction func stickerTapped(_ sender: UIButton) {
  1488. if textFieldSend.isFirstResponder {
  1489. dismissKeyboard()
  1490. }
  1491. DispatchQueue.main.async {
  1492. if !self.viewSticker.isDescendant(of: self.view) {
  1493. self.constraintBottomAttachment.constant = 200.0
  1494. self.view.addSubview(self.viewSticker)
  1495. self.viewSticker.translatesAutoresizingMaskIntoConstraints = false
  1496. NSLayoutConstraint.activate([
  1497. self.viewSticker.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1498. self.viewSticker.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1499. self.viewSticker.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1500. self.viewSticker.heightAnchor.constraint(equalToConstant: 200)
  1501. ])
  1502. let layout = UICollectionViewFlowLayout()
  1503. layout.scrollDirection = .vertical
  1504. let collectionSticker = UICollectionView(frame: .zero, collectionViewLayout: layout)
  1505. collectionSticker.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellSticker")
  1506. collectionSticker.delegate = self
  1507. collectionSticker.dataSource = self
  1508. collectionSticker.backgroundColor = .clear
  1509. self.viewSticker.addSubview(collectionSticker)
  1510. collectionSticker.translatesAutoresizingMaskIntoConstraints = false
  1511. NSLayoutConstraint.activate([
  1512. collectionSticker.topAnchor.constraint(equalTo: self.viewSticker.topAnchor, constant: 20),
  1513. collectionSticker.bottomAnchor.constraint(equalTo: self.viewSticker.bottomAnchor, constant: -20),
  1514. collectionSticker.leadingAnchor.constraint(equalTo: self.viewSticker.leadingAnchor, constant: 20),
  1515. collectionSticker.trailingAnchor.constraint(equalTo: self.viewSticker.trailingAnchor, constant: -20)
  1516. ])
  1517. if (self.currentIndexpath != nil) {
  1518. DispatchQueue.main.async {
  1519. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  1520. }
  1521. } else {
  1522. self.tableChatView.scrollToBottom()
  1523. }
  1524. } else {
  1525. self.constraintBottomAttachment.constant = 0.0
  1526. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1527. self.viewSticker.removeFromSuperview()
  1528. }
  1529. }
  1530. }
  1531. @IBAction func fileTapped(_ sender: UIButton) {
  1532. if (self.constraintBottomAttachment.constant != 0.0) {
  1533. constraintBottomAttachment.constant = 0.0
  1534. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1535. self.viewSticker.removeFromSuperview()
  1536. }
  1537. documentPicker.present()
  1538. }
  1539. @objc func didTapExit() {
  1540. self.dismiss(animated: true, completion: {
  1541. self.removeAllObjectBeforeDismissVC()
  1542. })
  1543. }
  1544. @objc func profilePersonTapped(_ sender: ObjectGesture) {
  1545. if isHistoryCC {
  1546. return
  1547. }
  1548. let idMe = User.getMyPin() as String?
  1549. if sender.message_id == idMe {
  1550. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
  1551. controller.data = sender.message_id
  1552. controller.flag = .me
  1553. navigationController?.show(controller, sender: nil)
  1554. } else {
  1555. let data = User.getDataCanNil(pin: sender.message_id)
  1556. if data != nil {
  1557. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
  1558. controller.flag = .friend
  1559. controller.user = data
  1560. controller.name = data!.fullName
  1561. controller.data = sender.message_id
  1562. controller.picture = data!.thumb
  1563. self.navigationController?.show(controller, sender: nil)
  1564. } else {
  1565. let dataUser = getDataProfile(f_pin: sender.message_id, message_id: "")
  1566. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
  1567. controller.flag = .invite
  1568. controller.user = nil
  1569. controller.name = dataUser["name"]!
  1570. controller.data = sender.message_id
  1571. controller.picture = dataUser["image_id"]!
  1572. self.navigationController?.show(controller, sender: nil)
  1573. }
  1574. }
  1575. }
  1576. @objc func seeProfileTapped() {
  1577. if isHistoryCC || removed || copySession || forwardSession || deleteSession || (dataGroup["official"] as? String == "1" && (dataGroup["parent"] as? String)!.isEmpty) {
  1578. return
  1579. }
  1580. dismissKeyboard()
  1581. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "groupDetailView") as! GroupDetailViewController
  1582. controller.data = dataGroup["group_id"] as? String ?? ""
  1583. controller.checkReadMessage = {
  1584. if self.currentIndexpath == nil {
  1585. var listData = self.dataMessages
  1586. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  1587. if listData.count != 0 {
  1588. let idMe = User.getMyPin() as String?
  1589. for i in 0...listData.count - 1 {
  1590. if listData[i]["f_pin"] as? String != idMe {
  1591. self.sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as? String ?? "", f_pin: listData[i]["f_pin"] as? String ?? "", message_scope_id: MessageScope.GROUP, message_id: listData[i]["message_id"] as? String ?? "")
  1592. }
  1593. }
  1594. }
  1595. } else {
  1596. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.currentIndexpath!.section] })
  1597. var listData = dataMessages
  1598. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  1599. if listData.count != 0 {
  1600. let idMe = User.getMyPin() as String?
  1601. for i in 0...listData.count - 1 {
  1602. if listData[i]["f_pin"] as? String != idMe {
  1603. self.sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as? String ?? "", f_pin: listData[i]["f_pin"] as? String ?? "", message_scope_id: MessageScope.GROUP, message_id: listData[i]["message_id"] as? String ?? "")
  1604. }
  1605. }
  1606. }
  1607. }
  1608. }
  1609. navigationController?.show(controller, sender: nil)
  1610. }
  1611. @objc func dismissKeyboard() {
  1612. if isSearching {
  1613. searchBar.resignFirstResponder()
  1614. } else {
  1615. textFieldSend.resignFirstResponder() // dismiss keyoard
  1616. if (self.constraintBottomAttachment.constant != 0.0) {
  1617. constraintBottomAttachment.constant = 0.0
  1618. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1619. self.viewSticker.removeFromSuperview()
  1620. }
  1621. }
  1622. }
  1623. @objc func keyboardWillShow(notification: NSNotification) {
  1624. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  1625. if (self.constraintBottomAttachment.constant != 0.0) {
  1626. self.constraintBottomAttachment.constant = 0.0
  1627. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  1628. self.viewSticker.removeFromSuperview()
  1629. }
  1630. let info:NSDictionary = notification.userInfo! as NSDictionary
  1631. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  1632. let keyboardHeight: CGFloat = keyboardSize.height
  1633. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  1634. if self.constraintBottomAttachment.constant != keyboardHeight || self.constraintViewTextField.constant != keyboardHeight - 60 {
  1635. // self.constraintViewTextField.constant = keyboardHeight - 60
  1636. self.constraintBottomAttachment.constant = keyboardHeight
  1637. if self.contraintBottomMention.constant > 0 {
  1638. self.contraintBottomMention.constant = 25 + constraintBottomAttachment.constant + self.heightTextFieldSend.constant + self.viewTextfield.bounds.height
  1639. }
  1640. self.keyboardHeightForMention = keyboardHeight
  1641. if isSearching {
  1642. self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
  1643. }
  1644. UIView.animate(withDuration: TimeInterval(duration), animations: {
  1645. self.view.layoutIfNeeded()
  1646. })
  1647. if (self.currentIndexpath != nil) {
  1648. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  1649. } else {
  1650. self.tableChatView.scrollToBottom()
  1651. }
  1652. }
  1653. } else if isEditingMessage {
  1654. let info:NSDictionary = notification.userInfo! as NSDictionary
  1655. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  1656. let keyboardHeight: CGFloat = keyboardSize.height
  1657. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  1658. let constant: CGFloat = 0 - keyboardHeight - 15
  1659. constraintBottomeditTextView.constant = constant
  1660. constraintBottomSendEditTV.constant = constant
  1661. UIView.animate(withDuration: TimeInterval(duration), animations: {
  1662. self.view.layoutIfNeeded()
  1663. })
  1664. }
  1665. }
  1666. @objc func keyboardWillHide(notification: NSNotification) {
  1667. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  1668. let info:NSDictionary = notification.userInfo! as NSDictionary
  1669. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  1670. self.constraintViewTextField.constant = 0
  1671. self.constraintBottomAttachment.constant = 0
  1672. self.constraintBottomContainerMultpileSelectSession.constant = 0
  1673. if self.contraintBottomMention.constant > 0 {
  1674. self.contraintBottomMention.constant = 25 + constraintBottomAttachment.constant + self.heightTextFieldSend.constant + self.viewTextfield.bounds.height
  1675. }
  1676. keyboardHeightForMention = nil
  1677. UIView.animate(withDuration: TimeInterval(duration), animations: {
  1678. self.view.layoutIfNeeded()
  1679. })
  1680. }
  1681. }
  1682. @objc func showChooserACKConfidential() {
  1683. // dismissKeyboard()
  1684. let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
  1685. 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)
  1686. 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)
  1687. let imageSticker = resizeImage(image: UIImage(named: "Sticker---Emoji", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  1688. let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
  1689. self.isConfidential = !self.isConfidential
  1690. if self.isConfidential {
  1691. self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
  1692. } else {
  1693. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  1694. }
  1695. if self.isAck {
  1696. self.isAck = false
  1697. }
  1698. })
  1699. let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
  1700. self.isAck = !self.isAck
  1701. if self.isAck {
  1702. self.buttonAckConfidential.setImage(imageAck, for: .normal)
  1703. } else {
  1704. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  1705. }
  1706. if self.isConfidential {
  1707. self.isConfidential = false
  1708. }
  1709. })
  1710. let stickerAction = UIAlertAction(title: "Open Sticker".localized(), style: .default, handler: { (UIAlertAction) in
  1711. self.stickerTapped(UIButton())
  1712. })
  1713. confidentialAction.setValue(imageConfidential, forKey: "image")
  1714. ackAction.setValue(imageAck, forKey: "image")
  1715. stickerAction.setValue(imageSticker, forKey: "image")
  1716. alertController.addAction(confidentialAction)
  1717. alertController.addAction(ackAction)
  1718. // alertController.addAction(stickerAction)
  1719. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
  1720. self.isConfidential = false
  1721. self.isAck = false
  1722. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  1723. }))
  1724. self.present(alertController, animated: true, completion: nil)
  1725. }
  1726. public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
  1727. self.isConfidential = isConfidential
  1728. self.isAck = isAck
  1729. let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  1730. let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  1731. if isAck {
  1732. buttonAckConfidential.setImage(imageAck, for: .normal)
  1733. } else if isConfidential {
  1734. buttonAckConfidential.setImage(imageConfidential, for: .normal)
  1735. } else {
  1736. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  1737. }
  1738. }
  1739. @objc func sendTapped() {
  1740. sendChat(message_text: textFieldSend.text!, viewController: self)
  1741. }
  1742. private func sendChat(message_scope_id:String = MessageScope.GROUP, status:String = "2", 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 = "", is_call_center: String = "0", call_center_id: String = "", viewController: UIViewController, gif_id: String = "", is_forwarded: Int = 0) {
  1743. if viewController is EditorGroup && file_id == "" && dataMessageForward == nil {
  1744. if ((textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray && attachment_flag != "11") || textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ) {
  1745. dismissKeyboard()
  1746. viewController.view.makeToast("Write Messages".localized(), duration: 3)
  1747. if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized()) {
  1748. textFieldSend.text = ""
  1749. }
  1750. if (self.heightTextFieldSend.constant != 40) {
  1751. self.heightTextFieldSend.constant = 40
  1752. }
  1753. return
  1754. }
  1755. }
  1756. var reff_id = reff_id
  1757. if (reffId != nil) {
  1758. reff_id = reffId!
  1759. }
  1760. var message_text = message_text
  1761. let bulletPoint = " • "
  1762. let numberPattern = #" \d+\.\ "#
  1763. let firstLine = message_text.components(separatedBy: .newlines).first ?? ""
  1764. // Check if text contains bullet points or numbered list using regex
  1765. if !message_text.isEmpty && !firstLine.contains(bulletPoint) && firstLine.range(of: numberPattern, options: .regularExpression) == nil {
  1766. message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
  1767. }
  1768. let idMe = User.getMyPin() as String?
  1769. var opposite_pin = self.dataGroup["group_id"] as? String ?? ""
  1770. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  1771. opposite_pin = self.dataTopic["chat_id"] as? String ?? ""
  1772. }
  1773. var credential = credential
  1774. if isConfidential {
  1775. credential = "1"
  1776. }
  1777. var read_receipts = read_receipts
  1778. if isAck {
  1779. read_receipts = "8"
  1780. }
  1781. if message_text.contains("@") && listMentionInTextField.count > 0 {
  1782. var diff: Int = 0
  1783. for i in 0..<listMentionInTextField.count {
  1784. let mention = listMentionInTextField[i]
  1785. guard let exBlockStr = mention.ex_block, let exBlock = Int(exBlockStr) else {
  1786. continue // skip if ex_block is nil or not an integer
  1787. }
  1788. let nameWithMention = ("@" + mention.fullName).trimmingCharacters(in: .whitespaces)
  1789. let pinString = "@\(mention.pin)"
  1790. let upperBound = exBlock + diff
  1791. let lowerBound = upperBound - nameWithMention.count + 1
  1792. guard lowerBound >= 0, upperBound < message_text.count else {
  1793. continue // prevent index out-of-range
  1794. }
  1795. var afterMention = ""
  1796. let nextCharIndex = message_text.index(message_text.startIndex, offsetBy: upperBound + 1, limitedBy: message_text.endIndex)
  1797. if let index = nextCharIndex, index < message_text.endIndex {
  1798. let nextChar = message_text[index]
  1799. if nextChar != "\n" && nextChar != " " {
  1800. afterMention = " "
  1801. }
  1802. }
  1803. let startIndex = message_text.index(message_text.startIndex, offsetBy: lowerBound)
  1804. let endIndex = message_text.index(message_text.startIndex, offsetBy: upperBound + 1)
  1805. let range = startIndex..<endIndex
  1806. if message_text[range] == nameWithMention {
  1807. message_text.replaceSubrange(range, with: pinString + afterMention)
  1808. diff += (pinString + afterMention).count - nameWithMention.count
  1809. }
  1810. }
  1811. }
  1812. let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"] as? String ?? "", 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: dataTopic["chat_id"] as? String ?? "", is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)", specFile: specFileString)
  1813. Nexilis.addQueueMessage(message: message)
  1814. let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1815. if credential == "1" {
  1816. self.listTimerCredential[messageId] = 60
  1817. }
  1818. var row: [String: Any?] = [:]
  1819. row["message_id"] = messageId
  1820. row["f_pin"] = idMe
  1821. row["l_pin"] = dataGroup["group_id"]!!
  1822. row["message_scope_id"] = message_scope_id
  1823. row["server_date"] = "\(Date().currentTimeMillis())"
  1824. row["status"] = status
  1825. row["message_text"] = message_text
  1826. row["audio_id"] = audio_id
  1827. row["video_id"] = video_id
  1828. row["image_id"] = image_id
  1829. row["thumb_id"] = thumb_id
  1830. row["credential"] = credential
  1831. row["read_receipts"] = read_receipts
  1832. row["chat_id"] = dataTopic["chat_id"]!!
  1833. row["file_id"] = file_id
  1834. row["attachment_flag"] = attachment_flag
  1835. row["reff_id"] = reff_id
  1836. row["progress"] = 0.0
  1837. row["lock"] = "0"
  1838. row["is_stared"] = "0"
  1839. row["isSelected"] = false
  1840. row["gif_id"] = gif_id
  1841. row[TypeDataMessage.is_forwarded] = is_forwarded
  1842. row[TypeDataMessage.is_call_center] = is_call_center
  1843. row[TypeDataMessage.call_center_id] = call_center_id
  1844. row[TypeDataMessage.opposite_pin] = opposite_pin
  1845. row[TypeDataMessage.spec_file] = specFileString
  1846. specFileString = ""
  1847. lastTextLength = 0
  1848. if !dataDates.contains("Today".localized()){
  1849. dataDates.append("Today".localized())
  1850. tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .fade)
  1851. }
  1852. row["chat_date"] = "Today".localized()
  1853. self.tableChatView.beginUpdates()
  1854. dataMessages.append(row)
  1855. tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .fade)
  1856. self.tableChatView.endUpdates()
  1857. if credential == "1" {
  1858. var timer = Timer()
  1859. var minute = 60
  1860. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  1861. minute -= 1
  1862. self.listTimerCredential[messageId] = minute
  1863. if minute == 0 {
  1864. timer.invalidate()
  1865. self.listTimerCredential.removeValue(forKey: messageId)
  1866. self.timerCredential.removeValue(forKey: messageId)
  1867. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
  1868. if idx != nil {
  1869. self.dataMessages[idx!]["lock"] = "2"
  1870. self.dataMessages[idx!]["reff_id"] = ""
  1871. }
  1872. DispatchQueue.global().async {
  1873. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1874. do {
  1875. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1876. "lock" : "2"
  1877. ], _where: "message_id = '\(messageId)'")
  1878. } catch {
  1879. rollback.pointee = true
  1880. print("Access database error: \(error.localizedDescription)")
  1881. }
  1882. })
  1883. }
  1884. }
  1885. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  1886. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).firstIndex(where: { $0["message_id"] as? String == messageId})
  1887. if row != nil && section != nil{
  1888. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1889. }
  1890. })
  1891. self.timerCredential[messageId] = timer
  1892. }
  1893. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintBottomAttachment.constant == 0 {
  1894. textFieldSend.text = "Send message".localized()
  1895. textFieldSend.textColor = UIColor.lightGray
  1896. } else if constraintBottomAttachment.constant != 0 {
  1897. textFieldSend.text = ""
  1898. heightTextFieldSend.constant = 40
  1899. }
  1900. deleteReplyView()
  1901. deleteLinkPreview()
  1902. listMentionInTextField.removeAll()
  1903. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1904. self.tableChatView.scrollToBottom()
  1905. if self.markerCounter != nil {
  1906. let lastMarkerCounter = self.markerCounter
  1907. self.markerCounter = nil
  1908. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  1909. if indexMessage != nil {
  1910. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1911. 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 })
  1912. if row != nil && section != nil {
  1913. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1914. }
  1915. }
  1916. }
  1917. }
  1918. private func getCounter() {
  1919. Database.shared.database?.inTransaction({ fmdb, rollback in
  1920. var l_pin = self.dataGroup["group_id"] as? String ?? ""
  1921. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  1922. l_pin = self.dataTopic["chat_id"] as? String ?? ""
  1923. }
  1924. if let c = Database().getRecords(fmdb: fmdb, query: "SELECT counter FROM MESSAGE_SUMMARY where l_pin='\(l_pin)'"), c.next() {
  1925. counter = Int(c.int(forColumnIndex: 0))
  1926. c.close()
  1927. }
  1928. })
  1929. }
  1930. private func updateCounter(counter: Int) {
  1931. DispatchQueue.global().async {
  1932. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1933. do {
  1934. var l_pin = self.dataGroup["group_id"]!!
  1935. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  1936. l_pin = self.dataTopic["chat_id"] as? String ?? ""
  1937. }
  1938. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", cvalues: [
  1939. "counter" : "\(counter)"
  1940. ], _where: "l_pin = '\(l_pin)'")
  1941. } catch {
  1942. rollback.pointee = true
  1943. print("Access database error: \(error.localizedDescription)")
  1944. }
  1945. })
  1946. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1947. }
  1948. }
  1949. private func disableEditor() {
  1950. view.addSubview(containerAction)
  1951. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1952. NSLayoutConstraint.activate([
  1953. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1954. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1955. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1956. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1957. ])
  1958. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1959. let labelDisable = UILabel()
  1960. containerAction.addSubview(labelDisable)
  1961. labelDisable.translatesAutoresizingMaskIntoConstraints = false
  1962. NSLayoutConstraint.activate([
  1963. labelDisable.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1964. labelDisable.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1965. ])
  1966. labelDisable.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1967. labelDisable.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1968. labelDisable.text = "Call Center Session has ended".localized()
  1969. }
  1970. private func addButtonScrollToBottom() {
  1971. if tableChatView.alpha != 1 || isSearching {
  1972. return
  1973. }
  1974. self.view.addSubview(buttonScrollToBottom)
  1975. buttonScrollToBottom.translatesAutoresizingMaskIntoConstraints = false
  1976. NSLayoutConstraint.activate([
  1977. buttonScrollToBottom.bottomAnchor.constraint(equalTo: buttonSendChat.topAnchor, constant: -50),
  1978. buttonScrollToBottom.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1979. buttonScrollToBottom.widthAnchor.constraint(equalToConstant: 60),
  1980. buttonScrollToBottom.heightAnchor.constraint(equalToConstant: 30.0)
  1981. ])
  1982. buttonScrollToBottom.backgroundColor = .greenColor
  1983. buttonScrollToBottom.setImage(UIImage(systemName: "chevron.down.circle"), for: .normal)
  1984. buttonScrollToBottom.imageView?.contentMode = .scaleAspectFit
  1985. buttonScrollToBottom.imageView?.tintColor = .white
  1986. buttonScrollToBottom.contentVerticalAlignment = .fill
  1987. buttonScrollToBottom.contentHorizontalAlignment = .fill
  1988. buttonScrollToBottom.imageEdgeInsets.top = 2.0
  1989. buttonScrollToBottom.imageEdgeInsets.bottom = 2.0
  1990. buttonScrollToBottom.layer.cornerRadius = 10.0
  1991. buttonScrollToBottom.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
  1992. buttonScrollToBottom.clipsToBounds = true
  1993. buttonScrollToBottom.addTarget(self, action: #selector(scrollTobottomAction), for: .touchUpInside)
  1994. }
  1995. private func addCounterAtButttonScrollToBottom() {
  1996. if tableChatView.alpha != 1 || isSearching {
  1997. return
  1998. }
  1999. self.view.addSubview(indicatorCounterBSTB)
  2000. indicatorCounterBSTB.translatesAutoresizingMaskIntoConstraints = false
  2001. indicatorCounterBSTB.backgroundColor = .systemRed
  2002. indicatorCounterBSTB.layer.cornerRadius = 7.5
  2003. indicatorCounterBSTB.clipsToBounds = true
  2004. indicatorCounterBSTB.layer.borderWidth = 0.5
  2005. indicatorCounterBSTB.layer.borderColor = UIColor.secondaryColor.cgColor
  2006. NSLayoutConstraint.activate([
  2007. indicatorCounterBSTB.bottomAnchor.constraint(equalTo: buttonScrollToBottom.topAnchor, constant: 5),
  2008. indicatorCounterBSTB.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -50),
  2009. indicatorCounterBSTB.widthAnchor.constraint(greaterThanOrEqualToConstant: 15),
  2010. indicatorCounterBSTB.heightAnchor.constraint(equalToConstant: 15)
  2011. ])
  2012. indicatorCounterBSTB.addSubview(labelCounter)
  2013. labelCounter.translatesAutoresizingMaskIntoConstraints = false
  2014. NSLayoutConstraint.activate([
  2015. labelCounter.leadingAnchor.constraint(equalTo: indicatorCounterBSTB.leadingAnchor, constant: 2),
  2016. labelCounter.trailingAnchor.constraint(equalTo: indicatorCounterBSTB.trailingAnchor, constant: -2),
  2017. labelCounter.centerXAnchor.constraint(equalTo: indicatorCounterBSTB.centerXAnchor),
  2018. ])
  2019. labelCounter.font = UIFont.systemFont(ofSize: 11 + offset())
  2020. labelCounter.text = "\(counter)"
  2021. labelCounter.textColor = .secondaryColor
  2022. labelCounter.textAlignment = .center
  2023. }
  2024. @objc func scrollTobottomAction() {
  2025. tableChatView.scrollToBottom()
  2026. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [self] in
  2027. if buttonScrollToBottom.isDescendant(of: self.view) {
  2028. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  2029. buttonScrollToBottom.removeFromSuperview()
  2030. if indicatorCounterBSTB.isDescendant(of: self.view) {
  2031. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  2032. indicatorCounterBSTB.removeFromSuperview()
  2033. }
  2034. }
  2035. }
  2036. }
  2037. private func sendReadMessageStatus(chat_id: String, f_pin: String, message_scope_id: String, message_id: String) {
  2038. let message = CoreMessage_TMessageBank.getUpdateRead(p_chat_id: chat_id, p_f_pin: f_pin, p_scope_id: message_scope_id, qty: 1)
  2039. let fPin = message.getBody(key: CoreMessage_TMessageKey.F_PIN)
  2040. let scope = message.getBody(key: CoreMessage_TMessageKey.SCOPE_ID)
  2041. message.mBodies[CoreMessage_TMessageKey.SERVER_DATE] = String(Date().currentTimeMillis())
  2042. if (fPin.elementsEqual("-999") || scope.elementsEqual("16") || scope.elementsEqual("15")){
  2043. return
  2044. }
  2045. DispatchQueue.global().async {
  2046. while API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() {
  2047. Thread.sleep(forTimeInterval: 1)
  2048. }
  2049. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2050. do {
  2051. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  2052. "status" : "4"
  2053. ], _where: "message_id = '\(message_id)'")
  2054. } catch {
  2055. rollback.pointee = true
  2056. print("Access database error: \(error.localizedDescription)")
  2057. }
  2058. })
  2059. message.mStatus = CoreMessage_TMessageUtil.getTID()
  2060. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  2061. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
  2062. _ = Nexilis.write(message: message)
  2063. }
  2064. if let index = dataMessages.firstIndex(where: {$0["message_id"] as? String == message_id}) {
  2065. dataMessages[index]["status"] = "4"
  2066. let auto: Bool = SecureUserDefaults.shared.value(forKey: "autoDownload") ?? false
  2067. if auto {
  2068. if dataMessages[index]["image_id"] as? String != nil && !((dataMessages[index]["image_id"] as? String)!.isEmpty) {
  2069. Download().startHTTP(forKey:dataMessages[index]["image_id"] as? String ?? "") { (name, progress) in
  2070. guard progress == 100 else {
  2071. return
  2072. }
  2073. do {
  2074. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  2075. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  2076. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  2077. if let dirPath = paths.first {
  2078. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["image_id"] as? String ?? "")
  2079. if FileManager.default.fileExists(atPath: imageURL.path) {
  2080. let image = UIImage(contentsOfFile: imageURL.path)
  2081. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  2082. if save {
  2083. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  2084. }
  2085. }
  2086. else if FileEncryption.shared.isSecureExists(filename: self.dataMessages[index]["image_id"] as? String ?? "") {
  2087. if var secureData = try FileEncryption.shared.readSecure(filename: self.dataMessages[index]["image_id"] as? String ?? "") {
  2088. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  2089. if dataDecrypt != nil {
  2090. secureData = dataDecrypt!
  2091. }
  2092. let image = UIImage(data: secureData)
  2093. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  2094. if save {
  2095. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  2096. }
  2097. }
  2098. }
  2099. }
  2100. } catch {
  2101. }
  2102. DispatchQueue.main.async { [self] in
  2103. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  2104. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  2105. if row != nil && section != nil{
  2106. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .automatic)
  2107. }
  2108. }
  2109. }
  2110. } else if dataMessages[index]["video_id"] as? String != nil && !((dataMessages[index]["video_id"] as? String)!.isEmpty){
  2111. Download().startHTTP(forKey: dataMessages[index]["video_id"] as? String ?? "") { (name, progress) in
  2112. guard progress == 100 else {
  2113. return
  2114. }
  2115. do {
  2116. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  2117. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  2118. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  2119. if let dirPath = paths.first {
  2120. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["video_id"] as? String ?? "")
  2121. if FileManager.default.fileExists(atPath: videoURL.path) {
  2122. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  2123. if save {
  2124. PHPhotoLibrary.shared().performChanges({
  2125. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
  2126. }) { saved, error in
  2127. }
  2128. }
  2129. }
  2130. else if FileEncryption.shared.isSecureExists(filename: self.dataMessages[index]["video_id"] as? String ?? "") {
  2131. if var secureData = try FileEncryption.shared.readSecure(filename: self.dataMessages[index]["video_id"] as? String ?? "") {
  2132. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  2133. if dataDecrypt != nil {
  2134. secureData = dataDecrypt!
  2135. }
  2136. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  2137. let tempPath = cachesDirectory.appendingPathComponent(name)
  2138. try secureData.write(to: tempPath)
  2139. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  2140. if save {
  2141. PHPhotoLibrary.shared().performChanges({
  2142. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
  2143. }) { saved, error in
  2144. }
  2145. }
  2146. }
  2147. }
  2148. }
  2149. DispatchQueue.main.async { [self] in
  2150. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  2151. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  2152. if row != nil && section != nil{
  2153. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .automatic)
  2154. }
  2155. }
  2156. }
  2157. catch {
  2158. }
  2159. }
  2160. }
  2161. else if dataMessages[index]["file_id"] as? String != nil && !((dataMessages[index]["file_id"] as? String)!.isEmpty) {
  2162. Download().startHTTP(forKey: dataMessages[index]["file_id"] as? String ?? "") { (name, progress) in
  2163. guard progress == 100 else {
  2164. return
  2165. }
  2166. DispatchQueue.main.async { [self] in
  2167. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  2168. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  2169. if row != nil && section != nil{
  2170. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .automatic)
  2171. }
  2172. }
  2173. }
  2174. }
  2175. }
  2176. }
  2177. }
  2178. private func sendTyping(l_pin: String, isTyping: Bool = false) {
  2179. DispatchQueue.global().async {
  2180. let tmessage = CoreMessage_TMessageBank.getUpdateTypingStatus(p_opposite: l_pin, p_scope: MessageScope.GROUP, p_status: isTyping ? "3": "4")
  2181. _ = Nexilis.write(message: tmessage)
  2182. }
  2183. }
  2184. private func checkNewMessage(tableView: UITableView) {
  2185. currentIndexpath = tableView.indexPathsForVisibleRows?.last
  2186. let indexFirst = tableView.indexPathsForVisibleRows?.first
  2187. if indexFirst != nil {
  2188. let dataMessages = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexFirst!.section] })
  2189. if dataMessages.count == 0 {
  2190. return
  2191. }
  2192. let contentHeight = tableView.contentSize.height
  2193. let scrollViewHeight = tableView.frame.height
  2194. let fullContentOffset = contentHeight - scrollViewHeight
  2195. let contentOffsetY = tableView.contentOffset.y
  2196. if ((currentIndexpath!.section == dataDates.count - 1 && indexFirst!.row != dataMessages.count - 1) || indexFirst!.section != dataDates.count - 1) && fullContentOffset - contentOffsetY > 100 {
  2197. if !buttonScrollToBottom.isDescendant(of: self.view) {
  2198. addButtonScrollToBottom()
  2199. addCounterAtButttonScrollToBottom()
  2200. }
  2201. } else if (indexFirst!.section == dataDates.count - 1 && indexFirst!.row == dataMessages.count - 1) || fullContentOffset - contentOffsetY < 50 {
  2202. if buttonScrollToBottom.isDescendant(of: self.view) {
  2203. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  2204. buttonScrollToBottom.removeFromSuperview()
  2205. if indicatorCounterBSTB.isDescendant(of: self.view) {
  2206. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  2207. indicatorCounterBSTB.removeFromSuperview()
  2208. }
  2209. }
  2210. }
  2211. let indexPathFirst = tableChatView.indexPathsForVisibleRows?.first
  2212. if indexPathFirst != nil && listViewOnSection.count != 0 && listViewOnSection.count - 1 >= indexPathFirst!.section {
  2213. let headerView = listViewOnSection[indexPathFirst!.section]
  2214. if headerView.isHidden {
  2215. headerView.isHidden = false
  2216. }
  2217. }
  2218. if dataMessages.count - 1 < currentIndexpath!.row {
  2219. return
  2220. }
  2221. var listData = dataMessages[0...currentIndexpath!.row]
  2222. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2223. if listData.count != 0 {
  2224. let idMe = User.getMyPin() as String?
  2225. for i in 0...listData.count - 1 {
  2226. if listData[i]["f_pin"] as? String != idMe {
  2227. sendReadMessageStatus(chat_id: self.dataTopic["chat_id"] as? String ?? "", f_pin: listData[i]["f_pin"] as? String ?? "", message_scope_id: MessageScope.GROUP, message_id: listData[i]["message_id"] as? String ?? "")
  2228. }
  2229. }
  2230. }
  2231. }
  2232. if counter == 0 && indicatorCounterBSTB.isDescendant(of: self.view) {
  2233. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  2234. indicatorCounterBSTB.removeFromSuperview()
  2235. } else if counter != 0 && currentIndexpath != nil {
  2236. let dataFilter = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  2237. if dataFilter.count == 0 {
  2238. return
  2239. }
  2240. let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == dataFilter[currentIndexpath!.row]["message_id"] as? String ?? ""})
  2241. if idx == nil {
  2242. return
  2243. }
  2244. if (dataMessages.count - counter) <= idx! {
  2245. let countUpdate = idx! - (dataMessages.count - counter)
  2246. counter = counter - (countUpdate + 1)
  2247. if indicatorCounterBSTB.isDescendant(of: self.view) {
  2248. labelCounter.text = "\(counter)"
  2249. }
  2250. updateCounter(counter: counter)
  2251. }
  2252. }
  2253. }
  2254. }
  2255. extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDelegate, PHPickerViewControllerDelegate {
  2256. public func didSelect(imagevideo: Any?) {
  2257. if (imagevideo != nil) {
  2258. let imageVideoData = imagevideo as! [UIImagePickerController.InfoKey: Any]
  2259. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  2260. previewImageVC.imageVideoData = imageVideoData
  2261. if (textFieldSend.textColor != .lightGray) {
  2262. previewImageVC.currentTextTextField = textFieldSend.text
  2263. }
  2264. previewImageVC.modalPresentationStyle = .custom
  2265. previewImageVC.delegate = self
  2266. previewImageVC.isGroup = true
  2267. previewImageVC.isAck = self.isAck
  2268. previewImageVC.isConfidential = self.isConfidential
  2269. self.present(previewImageVC, animated: true, completion: nil)
  2270. }
  2271. }
  2272. public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  2273. if !isBlackCancelButton {
  2274. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2275. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2276. }
  2277. guard let result = results.first else {
  2278. picker.dismiss(animated: true, completion: nil)
  2279. return
  2280. }
  2281. if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
  2282. picker.dismiss(animated: true, completion: {
  2283. Nexilis.showLoader(text: "Preparing...".localized())
  2284. result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
  2285. if let error = error {
  2286. print("Error loading GIF: \(error.localizedDescription)")
  2287. Nexilis.hideLoader() {
  2288. }
  2289. } else if let data = data {
  2290. DispatchQueue.main.async {
  2291. Nexilis.hideLoader() {
  2292. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  2293. if (self.textFieldSend.textColor != .lightGray) {
  2294. previewImageVC.currentTextTextField = self.textFieldSend.text
  2295. }
  2296. previewImageVC.fromCopy = true
  2297. previewImageVC.isGIF = true
  2298. previewImageVC.dataGIF = data
  2299. previewImageVC.modalPresentationStyle = .custom
  2300. previewImageVC.delegate = self
  2301. previewImageVC.isAck = self.isAck
  2302. previewImageVC.isConfidential = self.isConfidential
  2303. self.present(previewImageVC, animated: true, completion: nil)
  2304. }
  2305. }
  2306. }
  2307. }
  2308. })
  2309. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
  2310. picker.dismiss(animated: true, completion: {
  2311. Nexilis.showLoader(text: "Preparing...".localized())
  2312. result.itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { data, error in
  2313. if let data = data {
  2314. do {
  2315. DispatchQueue.main.async {
  2316. Nexilis.hideLoader {
  2317. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  2318. if (self.textFieldSend.textColor != .lightGray) {
  2319. previewImageVC.currentTextTextField = self.textFieldSend.text
  2320. }
  2321. previewImageVC.fromCopy = true
  2322. previewImageVC.image = UIImage(data: data)
  2323. previewImageVC.modalPresentationStyle = .custom
  2324. previewImageVC.delegate = self
  2325. previewImageVC.isAck = self.isAck
  2326. previewImageVC.isConfidential = self.isConfidential
  2327. self.present(previewImageVC, animated: true, completion: nil)
  2328. }
  2329. }
  2330. } catch {
  2331. print("Error loading image data: \(error)")
  2332. }
  2333. } else {
  2334. print("Error: \(String(describing: error))")
  2335. }
  2336. }
  2337. })
  2338. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
  2339. picker.dismiss(animated: true, completion: {
  2340. Nexilis.showLoader(text: "Preparing...".localized())
  2341. result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
  2342. if let url = url {
  2343. let fileManager = FileManager.default
  2344. let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
  2345. var nameFile = url.lastPathComponent
  2346. if nameFile.contains("&uuid"){
  2347. nameFile = UUID().uuidString + ".mov"
  2348. }
  2349. let destinationURL = documentsDirectory.appendingPathComponent(nameFile)
  2350. do {
  2351. if fileManager.fileExists(atPath: destinationURL.path) {
  2352. try fileManager.removeItem(at: destinationURL)
  2353. }
  2354. try fileManager.copyItem(at: url, to: destinationURL)
  2355. DispatchQueue.main.async {
  2356. Nexilis.hideLoader {
  2357. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  2358. if (self.textFieldSend.textColor != .lightGray) {
  2359. previewImageVC.currentTextTextField = self.textFieldSend.text
  2360. }
  2361. previewImageVC.modalPresentationStyle = .custom
  2362. previewImageVC.urlVideoPhpPicker = destinationURL
  2363. previewImageVC.delegate = self
  2364. previewImageVC.isAck = self.isAck
  2365. previewImageVC.isConfidential = self.isConfidential
  2366. self.present(previewImageVC, animated: true, completion: nil)
  2367. }
  2368. }
  2369. } catch {
  2370. print("Error copying video file: \(error.localizedDescription)")
  2371. }
  2372. }
  2373. }
  2374. })
  2375. }
  2376. }
  2377. func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController, specFile: String) {
  2378. specFileString = specFile
  2379. 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)
  2380. }
  2381. }
  2382. extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPreviewControllerDataSource {
  2383. public func didSelectDocument(document: Any?) {
  2384. if (document != nil) {
  2385. self.previewItem = (document as! [URL])[0] as NSURL
  2386. let previewController = QLPreviewController()
  2387. previewController.dataSource = self
  2388. let vcHandleFile = UIViewController()
  2389. let nc = UINavigationController(rootViewController: vcHandleFile)
  2390. let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
  2391. let navBarAppearance = UINavigationBarAppearance()
  2392. nc.defaultStyle()
  2393. nc.modalPresentationStyle = .pageSheet
  2394. navBarAppearance.configureWithOpaqueBackground()
  2395. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
  2396. navBarAppearance.titleTextAttributes = attributes
  2397. nc.navigationBar.standardAppearance = navBarAppearance
  2398. nc.navigationBar.scrollEdgeAppearance = navBarAppearance
  2399. let backButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
  2400. vcHandleFile.navigationItem.leftBarButtonItem = backButton
  2401. let sendButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
  2402. buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
  2403. buttonSpec.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
  2404. buttonSpec.addTarget(self, action: #selector(showConfigurationFile), for: .touchUpInside)
  2405. let barButtonItemSpec = UIBarButtonItem(customView: buttonSpec)
  2406. vcHandleFile.navigationItem.rightBarButtonItems = [sendButton, barButtonItemSpec]
  2407. backButton.navigation = nc
  2408. sendButton.navigation = nc
  2409. if let viewVc = vcHandleFile.view {
  2410. vcHandleFile.title = self.previewItem?.lastPathComponent
  2411. vcHandleFile.addChild(previewController)
  2412. previewController.dataSource = self
  2413. previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
  2414. previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  2415. viewVc.addSubview(previewController.view)
  2416. previewController.didMove(toParent: vcHandleFile)
  2417. self.present(nc, animated: true)
  2418. }
  2419. }
  2420. }
  2421. @objc private func showConfigurationFile() {
  2422. let modalVC = UIViewController()
  2423. if let viewModal = modalVC.view {
  2424. viewModal.backgroundColor = .whiteBubbleColor
  2425. let closeButton = UIButton(type: .close)
  2426. viewModal.addSubview(closeButton)
  2427. closeButton.anchor(top: viewModal.topAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingRight: 15, width: 30, height: 30)
  2428. closeButton.layer.cornerRadius = 15
  2429. closeButton.clipsToBounds = true
  2430. closeButton.backgroundColor = .lightGray.withAlphaComponent(0.1)
  2431. let config = UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
  2432. closeButton.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
  2433. closeButton.addAction(UIAction { _ in
  2434. modalVC.dismiss(animated: true)
  2435. }, for: .touchUpInside)
  2436. let imageSpec = UIButton(type: .custom)
  2437. viewModal.addSubview(imageSpec)
  2438. imageSpec.anchor(top: viewModal.topAnchor, left: viewModal.leftAnchor, paddingTop: 25, paddingLeft: 15, width: 40, height: 40)
  2439. imageSpec.layer.cornerRadius = 20
  2440. imageSpec.clipsToBounds = true
  2441. imageSpec.backgroundColor = .lightGray.withAlphaComponent(0.1)
  2442. imageSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 35, height: 35)), for: .normal)
  2443. let title = UILabel()
  2444. title.text = "Option for Attachment".localized()
  2445. viewModal.addSubview(title)
  2446. title.anchor(top: viewModal.topAnchor, left: imageSpec.rightAnchor, paddingTop: 23, paddingLeft: 10)
  2447. title.textColor = .label
  2448. title.font = .boldSystemFont(ofSize: 16)
  2449. let subtitle = UILabel()
  2450. subtitle.text = "Select option :".localized()
  2451. viewModal.addSubview(subtitle)
  2452. subtitle.anchor(top: title.bottomAnchor, left: imageSpec.rightAnchor, paddingLeft: 10)
  2453. subtitle.textColor = .gray
  2454. subtitle.font = .systemFont(ofSize: 14)
  2455. tableViewConfigFile = UITableView()
  2456. viewModal.addSubview(tableViewConfigFile)
  2457. tableViewConfigFile.backgroundColor = .white
  2458. tableViewConfigFile.layer.cornerRadius = 8.0
  2459. tableViewConfigFile.clipsToBounds = true
  2460. tableViewConfigFile.anchor(top: imageSpec.bottomAnchor, left: viewModal.leftAnchor, bottom: viewModal.bottomAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 80, paddingRight: 15)
  2461. tableViewConfigFile.register(UITableViewCell.self, forCellReuseIdentifier: "cellConfigFile")
  2462. tableViewConfigFile.dataSource = self
  2463. tableViewConfigFile.delegate = self
  2464. tableViewConfigFile.separatorStyle = .singleLine
  2465. tableViewConfigFile.tableFooterView = UIView()
  2466. if #available(iOS 15.0, *) {
  2467. tableViewConfigFile.sectionHeaderTopPadding = 0
  2468. }
  2469. if #available(iOS 15.0, *) {
  2470. if let sheet = modalVC.sheetPresentationController {
  2471. sheet.detents = [.medium()]
  2472. }
  2473. } else {
  2474. // Fallback on earlier versions
  2475. }
  2476. }
  2477. UIApplication.shared.visibleViewController?.present(modalVC, animated: true)
  2478. }
  2479. @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
  2480. sender.navigation.dismiss(animated: true, completion: nil)
  2481. }
  2482. @objc private func sendDocument(sender: navigationQLPreviewDocument) {
  2483. DispatchQueue.global().async {
  2484. if Nexilis.checkingAccess(key: "content_inspection") {
  2485. DispatchQueue.main.async {
  2486. Nexilis.showLoader(text: "Scanning File...".localized())
  2487. }
  2488. let result = (self.previewItem! as URL).validateFile()
  2489. DispatchQueue.main.async {
  2490. Nexilis.hideLoader {
  2491. sender.navigation.dismiss(animated: true, completion: {
  2492. if result == 1 {
  2493. sendIt()
  2494. } else {
  2495. APIS.showWarningFile(type: result)
  2496. }
  2497. })
  2498. }
  2499. }
  2500. } else {
  2501. DispatchQueue.main.async {
  2502. sendIt()
  2503. }
  2504. }
  2505. func sendIt() {
  2506. sender.navigation.dismiss(animated: true, completion: nil)
  2507. do {
  2508. let dataFile = try Data(contentsOf: self.previewItem! as URL)
  2509. let urlFile = self.previewItem?.absoluteString
  2510. var originaFileName = (urlFile! as NSString).lastPathComponent
  2511. originaFileName = NSString(string: originaFileName).removingPercentEncoding!
  2512. let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
  2513. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  2514. let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
  2515. if !FileManager.default.fileExists(atPath: fileURL.path) {
  2516. do {
  2517. try dataFile.write(to: fileURL)
  2518. } catch {
  2519. }
  2520. }
  2521. self.sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
  2522. } catch {
  2523. }
  2524. }
  2525. }
  2526. }
  2527. }
  2528. extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
  2529. func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?) {
  2530. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  2531. previewImageVC.image = image
  2532. previewImageVC.isGIF = image == nil
  2533. previewImageVC.fromCopy = true
  2534. previewImageVC.dataGIF = dataGIF
  2535. previewImageVC.currentTextTextField = textFieldSend.text
  2536. previewImageVC.modalPresentationStyle = .custom
  2537. previewImageVC.delegate = self
  2538. previewImageVC.isAck = self.isAck
  2539. previewImageVC.isConfidential = self.isConfidential
  2540. self.present(previewImageVC, animated: true, completion: nil)
  2541. }
  2542. public func textViewDidChangeSelection(_ textView: UITextView) {
  2543. lastPositionCursorMention = textView.selectedRange.location
  2544. var isShowMention = false
  2545. let fulltextForMention = textView.text.prefix(lastPositionCursorMention)
  2546. let lines = fulltextForMention.split(separator: "\n")
  2547. if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
  2548. let words = lines[lastLineIndex].split(separator: " ")
  2549. if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
  2550. let mentionText = words[lastWordIndex]
  2551. let lastChar = fulltextForMention.last
  2552. if lastChar != "\n" && lastChar != " " {
  2553. if mentionText.starts(with: "@") || (mentionText.count >= 2 && (self.textFieldSend.textColor != UIColor.lightGray || heightTableEditMention != nil)) {
  2554. showMention(text: mentionText.starts(with: "@") ? String(mentionText.dropFirst()) : String(mentionText))
  2555. isShowMention = true
  2556. }
  2557. }
  2558. }
  2559. }
  2560. if !isShowMention {
  2561. hideMention()
  2562. }
  2563. var nowTextFieldSend = self.textFieldSend
  2564. if isEditingMessage {
  2565. nowTextFieldSend = editTextView
  2566. }
  2567. let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
  2568. let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
  2569. if doubleCurrentLine.isFinite {
  2570. let currentLine = Int(ceil(doubleCurrentLine))
  2571. UIView.animate(withDuration: 0.3) {
  2572. let layoutManager = textView.layoutManager
  2573. var numberOfLines = 0
  2574. var index = 0
  2575. let numberOfGlyphs = layoutManager.numberOfGlyphs
  2576. while index < numberOfGlyphs {
  2577. var lineRange = NSRange()
  2578. layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
  2579. index = NSMaxRange(lineRange)
  2580. numberOfLines += 1
  2581. }
  2582. if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
  2583. if self.isEditingMessage {
  2584. self.constraintHeighteditTextView.constant = 40
  2585. } else {
  2586. self.heightTextFieldSend.constant = 40
  2587. }
  2588. } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
  2589. if self.isEditingMessage {
  2590. self.constraintHeighteditTextView.constant = 95.0
  2591. } else {
  2592. self.heightTextFieldSend.constant = 95.0
  2593. }
  2594. } else if currentLine < 4 && numberOfLines < 5 {
  2595. if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
  2596. if self.isEditingMessage {
  2597. self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
  2598. } else {
  2599. self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
  2600. }
  2601. }
  2602. }
  2603. }
  2604. }
  2605. if self.isEditingMessage && textView == editTextView {
  2606. if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  2607. buttonSendEdit.isEnabled = false
  2608. } else if !buttonSendEdit.isEnabled {
  2609. buttonSendEdit.isEnabled = true
  2610. }
  2611. }
  2612. //indention code:
  2613. let text = textView.text ?? ""
  2614. let cursorPositionIndent = textView.selectedRange.location
  2615. // Prevent moving cursor before the 2-space indent
  2616. var adjustedCursorPosition = cursorPositionIndent
  2617. for line in lines {
  2618. if let range = text.range(of: line), NSRange(range, in: text).contains(cursorPositionIndent) {
  2619. if line.hasPrefix(" •") || line.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  2620. let startOfLine = text.distance(from: text.startIndex, to: range.lowerBound)
  2621. let minCursorPosition = startOfLine + 2 // Prevent cursor before indentation
  2622. if cursorPositionIndent < minCursorPosition {
  2623. adjustedCursorPosition = minCursorPosition
  2624. }
  2625. }
  2626. break
  2627. }
  2628. }
  2629. if adjustedCursorPosition != cursorPositionIndent {
  2630. textView.selectedRange = NSRange(location: adjustedCursorPosition, length: 0)
  2631. }
  2632. }
  2633. public func textViewDidChange(_ textView: UITextView) {
  2634. if textView.text.count == 0 {
  2635. isAlwaysHideLinkPreview = false
  2636. }
  2637. if allowTyping {
  2638. allowTyping = false
  2639. if dataTopic["chat_id"] as? String ?? "" == "" {
  2640. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [dataGroup["group_id"] as? String ?? ""])
  2641. sendTyping(l_pin: dataGroup["group_id"] as? String ?? "")
  2642. } else {
  2643. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [dataTopic["chat_id"] as? String ?? ""])
  2644. sendTyping(l_pin: dataTopic["chat_id"] as? String ?? "")
  2645. }
  2646. DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
  2647. self.allowTyping = true
  2648. })
  2649. }
  2650. timerCheckLink?.invalidate()
  2651. timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
  2652. self.checkLink(fullText: textView.text)
  2653. })
  2654. //indention code:
  2655. let text = textView.text ?? ""
  2656. let cursorPosition = textView.selectedRange.location
  2657. let tempListMention = listMentionInTextField
  2658. if listMentionInTextField.count > 0 {
  2659. for j in 0..<listMentionInTextField.count {
  2660. var index = j
  2661. if tempListMention.count != listMentionInTextField.count {
  2662. index = j - (tempListMention.count - listMentionInTextField.count)
  2663. }
  2664. var upper = (Int(listMentionInTextField[index].ex_block ?? "0") ?? 0)
  2665. if cursorPosition <= upper {
  2666. upper += text.count - lastTextLength
  2667. listMentionInTextField[index].ex_block = "\(upper)"
  2668. }
  2669. let lower = upper - listMentionInTextField[index].fullName.count
  2670. let name = listMentionInTextField[index].fullName.trimmingCharacters(in: .whitespaces)
  2671. if textView.text.substring(from: lower, to: upper) != "@\(name)" {
  2672. listMentionInTextField.remove(at: index)
  2673. }
  2674. }
  2675. }
  2676. // Handle Bullets (- [space] + letter → • )
  2677. let bulletPattern = #"(?<=\n|^)- (\S)"#
  2678. if let match = text.range(of: bulletPattern, options: .regularExpression) {
  2679. let matchedText = text[match]
  2680. if let spaceIndex = matchedText.firstIndex(of: " ") {
  2681. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  2682. let replacedText = text.replacingOccurrences(of: matchedText, with: " • \(firstLetter)", range: match)
  2683. let newCursorPosition = cursorPosition + 2 // Adjust cursor position
  2684. textView.text = replacedText
  2685. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  2686. }
  2687. }
  2688. // Handle Numbered Lists (e.g., "1. " [space] + letter → " 1.")
  2689. let numberPattern = #"(?<=\n|^)(\d+)\. (\S)"# // Matches "1. X"
  2690. if let match = text.range(of: numberPattern, options: .regularExpression) {
  2691. let matchedText = text[match]
  2692. if let spaceIndex = matchedText.firstIndex(of: " ") {
  2693. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  2694. let replacedText = text.replacingOccurrences(of: matchedText, with: " \(matchedText)", range: match)
  2695. let newCursorPosition = cursorPosition + 2 // Adjust cursor
  2696. textView.text = replacedText
  2697. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  2698. }
  2699. }
  2700. handleRichText(textView)
  2701. lastTextLength = text.count
  2702. }
  2703. private func showMention(text: String) {
  2704. if self.contraintBottomMention.constant < 0 {
  2705. if !isEditingMessage {
  2706. self.contraintBottomMention.constant = 25 + constraintBottomAttachment.constant + self.heightTextFieldSend.constant + self.viewTextfield.bounds.height
  2707. UIView.animate(withDuration: 0.5, animations: {
  2708. self.view.layoutIfNeeded()
  2709. })
  2710. }
  2711. }
  2712. listMentionWithText.removeAll()
  2713. Database.shared.database?.inTransaction({ fmdb, rollback in
  2714. do {
  2715. let idMe = User.getMyPin()!
  2716. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where group_id='\(self.dataGroup["group_id"] as? String ?? "")' AND f_pin <> '\(idMe)' AND name LIKE '%\(text)%'") {
  2717. while cursor.next() {
  2718. let user = User(pin: "")
  2719. user.pin = cursor.string(forColumnIndex: 0) ?? ""
  2720. user.firstName = cursor.string(forColumnIndex: 1) ?? ""
  2721. if !user.pin.isEmpty {
  2722. let userFromBuddy = User.getDataCanNil(pin: user.pin, fmdb: fmdb)
  2723. if userFromBuddy != nil {
  2724. listMentionWithText.append(userFromBuddy!)
  2725. } else {
  2726. listMentionWithText.append(user)
  2727. }
  2728. }
  2729. }
  2730. cursor.close()
  2731. }
  2732. listMentionWithText.removeAll(where: { listMentionInTextField.contains($0) })
  2733. var nowTableMention = tableMention!
  2734. var nowHeightTableMention = heightTableMention!
  2735. if isEditingMessage {
  2736. nowTableMention = tableMentionEdit
  2737. if heightTableEditMention != nil {
  2738. nowHeightTableMention = heightTableEditMention
  2739. } else {
  2740. return
  2741. }
  2742. }
  2743. if listMentionWithText.count > 0 {
  2744. if listMentionWithText.count < 5 {
  2745. nowHeightTableMention.constant = CGFloat(44 * listMentionWithText.count)
  2746. } else {
  2747. nowHeightTableMention.constant = 44 * 4
  2748. }
  2749. nowTableMention.reloadData()
  2750. } else {
  2751. nowHeightTableMention.constant = 44
  2752. self.hideMention()
  2753. }
  2754. } catch {
  2755. rollback.pointee = true
  2756. print("Access database error: \(error.localizedDescription)")
  2757. }
  2758. })
  2759. }
  2760. private func hideMention() {
  2761. if self.contraintBottomMention.constant > 0 {
  2762. listMentionWithText.removeAll()
  2763. tableMention.reloadData()
  2764. self.contraintBottomMention.constant = 0 - self.heightTableMention.constant
  2765. UIView.animate(withDuration: 0.5, animations: {
  2766. self.view.layoutIfNeeded()
  2767. })
  2768. } else if self.heightTableEditMention != nil && self.heightTableEditMention.constant != 0 {
  2769. listMentionWithText.removeAll()
  2770. tableMentionEdit.reloadData()
  2771. self.heightTableEditMention.constant = 0
  2772. }
  2773. }
  2774. private func checkLink(fullText: String) {
  2775. if !isAlwaysHideLinkPreview {
  2776. var text = ""
  2777. let listTextSplitBreak = fullText.components(separatedBy: "\n")
  2778. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  2779. if indexFirstLinkSplitBreak != nil {
  2780. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  2781. 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) })
  2782. if indexFirstLinkSplitSpace != nil {
  2783. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  2784. }
  2785. }
  2786. if !text.isEmpty {
  2787. var stringURl = text
  2788. if stringURl.starts(with: "www.") {
  2789. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  2790. }
  2791. var dataURL = ""
  2792. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2793. do {
  2794. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
  2795. while cursor.next() {
  2796. if let data = cursor.string(forColumnIndex: 0) {
  2797. dataURL = data
  2798. }
  2799. }
  2800. cursor.close()
  2801. }
  2802. } catch {
  2803. rollback.pointee = true
  2804. print("Access database error: \(error.localizedDescription)")
  2805. }
  2806. })
  2807. if !dataURL.isEmpty {
  2808. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  2809. let title = data["title"] as? String ?? ""
  2810. let description = data["description"] as? String ?? ""
  2811. let imageUrl = data["imageUrl"] as? String
  2812. let link = data["link"] as? String ?? ""
  2813. if self.showingLink != text {
  2814. self.showingLink = text
  2815. self.deleteLinkPreview()
  2816. if !textFieldSend.text.isEmpty || textFieldSend.text.contains(text){
  2817. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  2818. }
  2819. }
  2820. }
  2821. } else {
  2822. let urlConfig = URLSessionConfiguration.default
  2823. let sessionDelegate = SelfSignedURLSessionDelegate()
  2824. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  2825. let slp = SwiftLinkPreview(session: session,
  2826. workQueue: SwiftLinkPreview.defaultWorkQueue,
  2827. responseQueue: DispatchQueue.main,
  2828. cache: DisabledCache.instance)
  2829. let preview = slp.preview(stringURl,
  2830. onSuccess: { result in
  2831. let title = result.title ?? "No Title"
  2832. let description = stringURl.contains("google.com") ? "" : result.description
  2833. let imageUrl = result.icon
  2834. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2835. do {
  2836. var dataJson: [String: Any] = [:]
  2837. dataJson["title"] = title
  2838. dataJson["description"] = description
  2839. dataJson["imageUrl"] = imageUrl
  2840. dataJson["link"] = text
  2841. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  2842. return
  2843. }
  2844. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  2845. "id" : "\(Date().currentTimeMillis().toHex())",
  2846. "link" : text,
  2847. "data_link" : json,
  2848. "retry": 0
  2849. ], replace: true)
  2850. } catch {
  2851. rollback.pointee = true
  2852. print("Access database error: \(error.localizedDescription)")
  2853. }
  2854. })
  2855. if self.showingLink != text {
  2856. self.showingLink = text
  2857. self.deleteLinkPreview()
  2858. if !self.textFieldSend.text.isEmpty || self.textFieldSend.text.contains(text){
  2859. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  2860. }
  2861. }
  2862. },
  2863. onError: { error in
  2864. self.deleteLinkPreview()
  2865. })
  2866. }
  2867. } else {
  2868. deleteLinkPreview()
  2869. }
  2870. }
  2871. }
  2872. private func buildPreviewLink(imageUrl: String?, title: String, description: String?, stringURl: String) {
  2873. if !self.viewTextfield.subviews.contains(self.containerLink){
  2874. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  2875. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
  2876. if self.contraintBottomMention.constant > 0 {
  2877. self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80 + self.heightTextFieldSend.constant
  2878. }
  2879. }, completion: nil)
  2880. }
  2881. self.viewTextfield.addSubview(self.containerLink)
  2882. self.containerLink.translatesAutoresizingMaskIntoConstraints = false
  2883. self.containerLink.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  2884. self.containerLink.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor).isActive = true
  2885. self.containerLink.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  2886. self.containerLink.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  2887. self.containerLink.backgroundColor = .secondaryColor
  2888. if self.reffId != nil {
  2889. self.bottomAnchorPreviewReply.isActive = false
  2890. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  2891. self.bottomAnchorPreviewReply.isActive = true
  2892. }
  2893. let imagePreview = UIImageView()
  2894. if imageUrl != nil {
  2895. self.containerLink.addSubview(imagePreview)
  2896. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  2897. imagePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor).isActive = true
  2898. imagePreview.bottomAnchor.constraint(equalTo: self.containerLink.bottomAnchor).isActive = true
  2899. imagePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor).isActive = true
  2900. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  2901. imagePreview.loadImageAsync(with: imageUrl)
  2902. imagePreview.contentMode = .scaleAspectFit
  2903. }
  2904. let titlePreview = UILabel()
  2905. self.containerLink.addSubview(titlePreview)
  2906. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  2907. if imageUrl != nil {
  2908. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  2909. } else {
  2910. titlePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  2911. }
  2912. titlePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor, constant: 25.0).isActive = true
  2913. titlePreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  2914. titlePreview.text = title
  2915. titlePreview.font = UIFont.systemFont(ofSize: 14.0, weight: .bold)
  2916. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  2917. let descPreview = UILabel()
  2918. self.containerLink.addSubview(descPreview)
  2919. descPreview.translatesAutoresizingMaskIntoConstraints = false
  2920. if imageUrl != nil {
  2921. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  2922. } else {
  2923. descPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  2924. }
  2925. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  2926. descPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  2927. descPreview.text = description
  2928. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  2929. descPreview.textColor = .gray
  2930. descPreview.numberOfLines = 1
  2931. let linkPreview = UILabel()
  2932. self.containerLink.addSubview(linkPreview)
  2933. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  2934. if imageUrl != nil {
  2935. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  2936. } else {
  2937. linkPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  2938. }
  2939. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  2940. linkPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  2941. linkPreview.text = stringURl
  2942. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  2943. linkPreview.textColor = .gray
  2944. linkPreview.numberOfLines = 1
  2945. let cancelPreview = UIButton(type: .custom)
  2946. self.containerLink.addSubview(cancelPreview)
  2947. cancelPreview.translatesAutoresizingMaskIntoConstraints = false
  2948. cancelPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -10).isActive = true
  2949. cancelPreview.centerYAnchor.constraint(equalTo: self.containerLink.centerYAnchor).isActive = true
  2950. cancelPreview.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  2951. cancelPreview.addTarget(nil, action: #selector(self.removeLinkPreviewUntilEmptyTextView), for: .touchUpInside)
  2952. cancelPreview.backgroundColor = .clear
  2953. cancelPreview.tintColor = .mainColor
  2954. }
  2955. public func textViewDidBeginEditing(_ textView: UITextView) {
  2956. if textView.textColor == UIColor.lightGray {
  2957. textView.text = nil
  2958. textView.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  2959. }
  2960. }
  2961. public func textViewDidEndEditing(_ textView: UITextView) {
  2962. if textView.text.isEmpty && textView != editTextView {
  2963. textView.textColor = UIColor.lightGray
  2964. textView.text = "Send message".localized()
  2965. }
  2966. }
  2967. public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  2968. if text.isEmpty {
  2969. if listMentionInTextField.count > 0 {
  2970. for i in 0..<listMentionInTextField.count {
  2971. if lastPositionCursorMention == Int(listMentionInTextField[i].ex_block!)! + 1 {
  2972. let fulltextForMention = textView.text.substring(from: 0, to: lastPositionCursorMention - 1)
  2973. let diff = textView.text.count - fulltextForMention.count
  2974. var text = textView.text ?? ""
  2975. let nameMention = listMentionInTextField[i].fullName.trimmingCharacters(in: .whitespaces)
  2976. let rangeReplacement = NSRange(location: lastPositionCursorMention - nameMention.count - 1, length: nameMention.count + 1)
  2977. let replacementText = ""
  2978. let copyAttributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "", listMentionInTextField: listMentionInTextField)
  2979. copyAttributedText.removeAttribute(.foregroundColor, range: rangeReplacement)
  2980. textView.attributedText = copyAttributedText
  2981. // Replace the old text with the new text using the replaceSubrange(_:with:) method
  2982. if let startIndex = text.index(text.startIndex, offsetBy: rangeReplacement.location, limitedBy: text.endIndex),
  2983. let endIndex = text.index(startIndex, offsetBy: rangeReplacement.length, limitedBy: text.endIndex) {
  2984. text.replaceSubrange(startIndex..<endIndex, with: replacementText)
  2985. }
  2986. listMentionInTextField.remove(at: i)
  2987. textView.attributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "", listMentionInTextField: listMentionInTextField)
  2988. let newPosition = textView.position(from: textView.beginningOfDocument, offset: textView.text.count - diff)
  2989. textView.selectedTextRange = textView.textRange(from: newPosition!, to: newPosition!)
  2990. textViewDidChangeSelection(textView)
  2991. handleRichText(textView)
  2992. return false
  2993. }
  2994. }
  2995. }
  2996. }
  2997. let indent = handleIndent(textView, range, text)
  2998. if !indent {
  2999. textViewDidChangeSelection(textView)
  3000. handleRichText(textView)
  3001. return indent
  3002. }
  3003. if (textView.text.count == 0) {
  3004. return text != "\n"
  3005. }
  3006. return true
  3007. }
  3008. private func handleIndent(_ textView: UITextView, _ range: NSRange, _ text: String) -> Bool {
  3009. guard let nsText = textView.text as NSString? else { return true }
  3010. let newText = nsText.replacingCharacters(in: range, with: text)
  3011. var lines = newText.components(separatedBy: "\n")
  3012. // Ensure range location is valid, considering Unicode scalars
  3013. guard let textRange = Range(range, in: textView.text) else { return true }
  3014. let prefixText = textView.text[..<textRange.lowerBound]
  3015. let affectedLineIndex = prefixText.components(separatedBy: "\n").count - 1
  3016. guard affectedLineIndex >= 0, affectedLineIndex < lines.count else { return true }
  3017. let affectedLine = lines[affectedLineIndex]
  3018. // Prevent deleting two-space indentation before bullet/number
  3019. if affectedLine.hasPrefix(" •") || affectedLine.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  3020. if let lineStart = textView.text.range(of: affectedLine)?.lowerBound,
  3021. let startIndex = textView.text.distance(of: lineStart) {
  3022. if range.location == startIndex || range.location == startIndex + 1 {
  3023. return false
  3024. }
  3025. }
  3026. }
  3027. // Auto-indent new lines based on previous line
  3028. if text == "\n" {
  3029. let previousLine = lines[affectedLineIndex]
  3030. if previousLine.hasPrefix(" •") {
  3031. let newBullet = "\n • "
  3032. textView.text = nsText.replacingCharacters(in: range, with: newBullet)
  3033. textView.selectedRange = NSRange(location: range.location + newBullet.utf16.count, length: 0)
  3034. return false
  3035. }
  3036. if let match = previousLine.range(of: #"^\s{2}(\d+)\."#, options: .regularExpression),
  3037. let numberMatch = previousLine[match].components(separatedBy: ".").first,
  3038. let number = Int(numberMatch.trimmingCharacters(in: .whitespaces)) {
  3039. let newNumber = "\n \(number + 1). "
  3040. textView.text = nsText.replacingCharacters(in: range, with: newNumber)
  3041. textView.selectedRange = NSRange(location: range.location + newNumber.utf16.count, length: 0)
  3042. return false
  3043. }
  3044. }
  3045. // Handle Backspace on Empty Bullet (Convert " • " → "- ")
  3046. if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
  3047. lines[affectedLineIndex] = "- " // Replace " • " with "- "
  3048. textView.text = lines.joined(separator: "\n")
  3049. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  3050. return false
  3051. }
  3052. // Handle Backspace on Numbered List
  3053. if text.isEmpty, affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) != nil {
  3054. lines[affectedLineIndex] = affectedLine.trimmingCharacters(in: .whitespaces)
  3055. textView.text = lines.joined(separator: "\n")
  3056. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  3057. return false
  3058. }
  3059. return true
  3060. }
  3061. private func handleRichText(_ textView: UITextView) {
  3062. textView.preserveCursorPosition(withChanges: { _ in
  3063. textView.attributedText = textView.text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "", listMentionInTextField: self.listMentionInTextField)
  3064. return .preserveCursor
  3065. })
  3066. }
  3067. public func textView(_ textView: UITextView, shouldInteractWith URL: URL?, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  3068. var urlString: String?
  3069. if let url = URL {
  3070. urlString = url.absoluteString
  3071. } else {
  3072. if let range = Range(characterRange, in: textView.text) {
  3073. let tappedText = String(textView.text[range])
  3074. urlString = tappedText
  3075. }
  3076. }
  3077. guard let finalURL = urlString else {
  3078. return false
  3079. }
  3080. switch interaction {
  3081. case .invokeDefaultAction:
  3082. let gesture = ObjectGesture()
  3083. gesture.message_id = finalURL
  3084. tapMessageText(gesture)
  3085. return false
  3086. case .presentActions:
  3087. UIPasteboard.general.string = finalURL
  3088. self.view.makeToast("Link Copied".localized(), duration: 3)
  3089. return false
  3090. case .preview:
  3091. return true
  3092. @unknown default:
  3093. return true
  3094. }
  3095. }
  3096. }
  3097. extension EditorGroup: UIContextMenuInteractionDelegate {
  3098. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
  3099. if showMenuContext {
  3100. showMenuContext = false
  3101. interaction.view!.removeInteraction(interaction)
  3102. }
  3103. }
  3104. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
  3105. if textFieldSend.isFirstResponder {
  3106. textFieldSend.resignFirstResponder()
  3107. }
  3108. let indexPath = self.tableChatView.indexPathForRow(at: interaction.view!.convert(location, to: self.tableChatView))
  3109. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  3110. var star: UIAction
  3111. if (dataMessages[indexPath!.row]["is_stared"] as? String ?? "" == "0") {
  3112. star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star"), handler: {(_) in
  3113. if self.removed {
  3114. return
  3115. }
  3116. DispatchQueue.global().async {
  3117. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3118. do {
  3119. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3120. "is_stared" : 1
  3121. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  3122. } catch {
  3123. rollback.pointee = true
  3124. print("Access database error: \(error.localizedDescription)")
  3125. }
  3126. })
  3127. }
  3128. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == dataMessages[indexPath!.row]["message_id"] as? String ?? ""})
  3129. if idx != nil{
  3130. self.dataMessages[idx!]["is_stared"] = "1"
  3131. }
  3132. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  3133. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  3134. })
  3135. } else {
  3136. star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash"), handler: {(_) in
  3137. if self.removed {
  3138. return
  3139. }
  3140. DispatchQueue.global().async {
  3141. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3142. do {
  3143. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3144. "is_stared" : 0
  3145. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  3146. } catch {
  3147. rollback.pointee = true
  3148. print("Access database error: \(error.localizedDescription)")
  3149. }
  3150. })
  3151. }
  3152. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == dataMessages[indexPath!.row]["message_id"] as? String ?? ""})
  3153. if idx != nil{
  3154. self.dataMessages[idx!]["is_stared"] = "0"
  3155. }
  3156. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  3157. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  3158. })
  3159. }
  3160. let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left"), handler: {(_) in
  3161. if self.removed {
  3162. return
  3163. }
  3164. if self.isSearching {
  3165. self.cancelAction()
  3166. }
  3167. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  3168. self.handleReply(indexPath: indexPath!)
  3169. })
  3170. })
  3171. let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right"), handler: {(_) in
  3172. if self.removed {
  3173. return
  3174. }
  3175. if self.isSearching {
  3176. self.cancelAction()
  3177. }
  3178. if self.reffId != nil {
  3179. self.deleteReplyView()
  3180. }
  3181. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  3182. self.forwardSession = true
  3183. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  3184. if !self.isHistoryCC {
  3185. self.navigationItem.rightBarButtonItems = nil
  3186. }
  3187. self.navigationItem.rightBarButtonItem = cancelButton
  3188. self.changeAppBar()
  3189. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  3190. if idx != nil{
  3191. self.dataMessages[idx!]["isSelected"] = true
  3192. }
  3193. self.addMultipleSelectSession()
  3194. self.tableChatView.reloadData()
  3195. }
  3196. })
  3197. let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc"), handler: {(_) in
  3198. if self.removed {
  3199. return
  3200. }
  3201. if self.isSearching {
  3202. self.cancelAction()
  3203. }
  3204. if self.reffId != nil {
  3205. self.deleteReplyView()
  3206. }
  3207. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  3208. self.copySession = true
  3209. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  3210. if !self.isHistoryCC {
  3211. self.navigationItem.rightBarButtonItems = nil
  3212. }
  3213. self.navigationItem.rightBarButtonItem = cancelButton
  3214. self.changeAppBar()
  3215. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  3216. if idx != nil{
  3217. self.dataMessages[idx!]["isSelected"] = true
  3218. }
  3219. self.addMultipleSelectSession()
  3220. self.tableChatView.reloadData()
  3221. }
  3222. })
  3223. let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil.tip.crop.circle"), handler: {(_) in
  3224. self.isEditingMessage = true
  3225. self.showEditMessageView(at: indexPath!)
  3226. })
  3227. let translate = UIAction(title: "Translate".localized(), image: UIImage(systemName: "t.bubble"), handler: {(_) in
  3228. self.view.makeToast("Translating...".localized(), duration: 3)
  3229. var translation: String = "English"
  3230. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  3231. if lang == "id" {
  3232. translation = "Indonesia"
  3233. }
  3234. let payload: [String : Any] = [
  3235. "role": "user",
  3236. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  3237. ]
  3238. let parameter: [String : Any] = [
  3239. "use_video": "0",
  3240. "translate": translation,
  3241. "payload": [payload]
  3242. ]
  3243. DispatchQueue.global().async {
  3244. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  3245. let response = response as? HTTPURLResponse
  3246. if response?.statusCode != 200 || error != nil {
  3247. DispatchQueue.main.async {
  3248. self.view.makeToast("There is an error occurred while translating your message. Please try again or check your network connection.".localized(), duration: 3)
  3249. }
  3250. return
  3251. }
  3252. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  3253. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
  3254. let dataContent = json["content"]!
  3255. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  3256. if idx != nil{
  3257. self.dataMessages[idx!][TypeDataMessage.message_text] = (dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "") + "\n\n" + "$\(dataContent)$"
  3258. }
  3259. DispatchQueue.main.async{
  3260. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  3261. }
  3262. }
  3263. }
  3264. })
  3265. }
  3266. })
  3267. let gcs = UIAction(title: "Get Chat Suggestion".localized(), image: UIImage(systemName: "exclamationmark.bubble"), handler: {(_) in
  3268. self.view.makeToast("Getting chat suggestion...".localized(), duration: 3)
  3269. let payload: [String : Any] = [
  3270. "role": "user",
  3271. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  3272. ]
  3273. let parameter: [String : Any] = [
  3274. "use_video": "0",
  3275. "suggest": "1",
  3276. "payload": [payload]
  3277. ]
  3278. DispatchQueue.global().async {
  3279. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  3280. let response = response as? HTTPURLResponse
  3281. if response?.statusCode != 200 || error != nil {
  3282. DispatchQueue.main.async {
  3283. 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)
  3284. }
  3285. return
  3286. }
  3287. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  3288. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
  3289. if let dataMessage = json["message"] as? [[String: Any]] {
  3290. if let dataContent = dataMessage[0]["content"] as? String {
  3291. DispatchQueue.main.async{
  3292. self.textFieldSend.text = dataContent
  3293. self.textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  3294. }
  3295. }
  3296. }
  3297. }
  3298. }
  3299. })
  3300. }
  3301. })
  3302. let more = UIMenu(title: "More...".localized(), children: [translate, gcs])
  3303. let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle"), handler: {(_) in
  3304. if self.removed {
  3305. return
  3306. }
  3307. let messageInfoVC = MessageInfo()
  3308. messageInfoVC.data = dataMessages[indexPath!.row]
  3309. messageInfoVC.dataGroup = self.dataGroup
  3310. messageInfoVC.isPersonal = false
  3311. self.navigationController?.pushViewController(messageInfoVC, animated: true)
  3312. })
  3313. let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash"), attributes: .destructive, handler: {(_) in
  3314. if self.removed {
  3315. return
  3316. }
  3317. if self.isSearching {
  3318. self.cancelAction()
  3319. }
  3320. if self.reffId != nil {
  3321. self.deleteReplyView()
  3322. }
  3323. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  3324. self.deleteSession = true
  3325. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  3326. if !self.isHistoryCC {
  3327. self.navigationItem.rightBarButtonItems = nil
  3328. }
  3329. self.navigationItem.rightBarButtonItem = cancelButton
  3330. self.changeAppBar()
  3331. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  3332. if idx != nil{
  3333. self.dataMessages[idx!]["isSelected"] = true
  3334. }
  3335. self.addMultipleSelectSession()
  3336. self.tableChatView.reloadData()
  3337. }
  3338. })
  3339. let resend = UIAction(title: "Resend".localized(), image: UIImage(systemName: "arrow.clockwise"), handler: {(_) in
  3340. let messageId = dataMessages[indexPath!.row][TypeDataMessage.message_id] as? String ?? ""
  3341. let status = dataMessages[indexPath!.row][TypeDataMessage.status] as? String ?? ""
  3342. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  3343. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  3344. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  3345. self.groupImages[idxMessageIdParent].value[idxInImages].status = "1"
  3346. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage[TypeDataMessage.status] = "1"
  3347. }
  3348. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  3349. }
  3350. if (idx != nil) {
  3351. do {
  3352. self.dataMessages[idx!][TypeDataMessage.status] = "1"
  3353. self.dataMessages[idx!][TypeDataMessage.progress] = 0.0
  3354. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  3355. 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 })
  3356. if row != nil && section != nil {
  3357. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3358. }
  3359. } catch {
  3360. }
  3361. }
  3362. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3363. do {
  3364. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3365. "status" : "1"
  3366. ], _where: "message_id = '\(messageId)'")
  3367. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
  3368. "status" : "1"
  3369. ], _where: "message_id = '\(messageId)'")
  3370. } catch {
  3371. rollback.pointee = true
  3372. print("Access database error: \(error.localizedDescription)")
  3373. }
  3374. })
  3375. let message = CoreMessage_TMessageBank.sendMessage(message_id: messageId,
  3376. l_pin: dataMessages[indexPath!.row][TypeDataMessage.l_pin] as? String ?? "",
  3377. message_scope_id: dataMessages[indexPath!.row][TypeDataMessage.message_scope_id] as? String ?? "",
  3378. status: "1",
  3379. message_text: dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "",
  3380. credential: dataMessages[indexPath!.row][TypeDataMessage.credential] as? String ?? "",
  3381. attachment_flag: dataMessages[indexPath!.row][TypeDataMessage.attachment_flag] as? String ?? "",
  3382. ex_blog_id: dataMessages[indexPath!.row][TypeDataMessage.blog_id] as? String ?? "",
  3383. message_large_text: "",
  3384. ex_format: "",
  3385. image_id: dataMessages[indexPath!.row][TypeDataMessage.image_id] as? String ?? "",
  3386. audio_id: dataMessages[indexPath!.row][TypeDataMessage.audio_id] as? String ?? "",
  3387. video_id: dataMessages[indexPath!.row][TypeDataMessage.video_id] as? String ?? "",
  3388. file_id: dataMessages[indexPath!.row][TypeDataMessage.file_id] as? String ?? "",
  3389. thumb_id: dataMessages[indexPath!.row][TypeDataMessage.thumb_id] as? String ?? "",
  3390. reff_id: dataMessages[indexPath!.row][TypeDataMessage.reff_id] as? String ?? "",
  3391. read_receipts: dataMessages[indexPath!.row][TypeDataMessage.read_receipts] as? String ?? "",
  3392. chat_id: dataMessages[indexPath!.row][TypeDataMessage.chat_id] as? String ?? "",
  3393. is_call_center: dataMessages[indexPath!.row][TypeDataMessage.is_call_center] as? String ?? "",
  3394. call_center_id: dataMessages[indexPath!.row][TypeDataMessage.call_center_id] as? String ?? "",
  3395. opposite_pin: dataMessages[indexPath!.row][TypeDataMessage.opposite_pin] as? String ?? "", specFile: "")
  3396. Nexilis.addQueueMessage(message: message)
  3397. })
  3398. var children: [UIMenuElement] = [star, reply, copy, delete]
  3399. var isMore = false
  3400. // let copyOption = self.copyOption(indexPath: indexPath!)
  3401. let idMe = User.getMyPin() as String?
  3402. if dataMessages[indexPath!.row]["status"] as? String ?? "" == "0" {
  3403. children = [resend, delete]
  3404. } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as? String ?? "" == "1") || dataMessages[indexPath!.row]["message_scope_id"] as? String ?? "" == "18" || dataMessages[indexPath!.row]["credential"] as? String ?? "" == "1" {
  3405. children = [delete]
  3406. } else if (groupImages[dataMessages[indexPath!.row]["message_id"] as? String ?? ""] != nil) {
  3407. forward.title = "Forward All".localized()
  3408. delete.title = "Delete All".localized()
  3409. children = [delete]
  3410. if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" {
  3411. children.insert(forward, at: 0)
  3412. }
  3413. } else {
  3414. if dataMessages[indexPath!.row]["f_pin"] as? String ?? "" == "-999" {
  3415. children = [star, reply ,delete]
  3416. }
  3417. 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 {
  3418. children = [star, reply ,delete]
  3419. } else if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  3420. children = [reply, delete]
  3421. }
  3422. if (Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"] as? String ?? "").isEmpty && dataMessages[indexPath!.row]["read_receipts"] as? String != "8") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
  3423. children.insert(forward, at: 2)
  3424. }
  3425. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  3426. children.insert(info, at: children.count - 1)
  3427. }
  3428. if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty {
  3429. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  3430. children.insert(edit, at: children.count - 1)
  3431. }
  3432. if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty {
  3433. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe && ((dataMessages[indexPath!.row][TypeDataMessage.is_forwarded] as? Int) ?? 0) == 0 {
  3434. let date = Date(milliseconds: Int64(dataMessages[indexPath!.row][TypeDataMessage.server_date] as? String ?? "") ?? 0)
  3435. let pastDate = date.addingTimeInterval(-10 * 60)
  3436. let differenceInSeconds = date.timeIntervalSince(pastDate)
  3437. if abs(differenceInSeconds) <= 15 * 60 {
  3438. children.insert(edit, at: children.count - 1)
  3439. }
  3440. }
  3441. isMore = true
  3442. }
  3443. }
  3444. }
  3445. let mainMenu = UIMenu(title: "", options: [.displayInline],
  3446. children: children)
  3447. var menuForShow = UIMenu(title: "", children: [mainMenu])
  3448. if isMore {
  3449. menuForShow = UIMenu(title: "", children: [mainMenu, more])
  3450. }
  3451. return UIContextMenuConfiguration(identifier: nil,
  3452. previewProvider: nil) { _ in
  3453. return menuForShow
  3454. }
  3455. }
  3456. func showEditMessageView(at indexPath: IndexPath) {
  3457. tempListMentionWithText = listMentionWithText
  3458. tempListMentionInTextField = listMentionInTextField
  3459. listMentionWithText.removeAll()
  3460. listMentionInTextField.removeAll()
  3461. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  3462. let oldText = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  3463. var oldTextForTextview = oldText
  3464. let pattern = "@[\\w]+"
  3465. do {
  3466. let regex = try NSRegularExpression(pattern: pattern)
  3467. let nsrange = NSRange(oldText.startIndex..., in: oldText)
  3468. let matches = regex.matches(in: oldText, range: nsrange)
  3469. let results = matches.map {
  3470. String(oldText[Range($0.range, in: oldText)!])
  3471. }
  3472. for result in results {
  3473. let pinRes = result.components(separatedBy: "@")[1]
  3474. Database.shared.database?.inTransaction({ fmdb, rollback in
  3475. do {
  3476. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where f_pin = '\(pinRes)'"), cursor.next() {
  3477. let user = User(pin: "")
  3478. user.pin = cursor.string(forColumnIndex: 0) ?? ""
  3479. user.firstName = cursor.string(forColumnIndex: 1) ?? ""
  3480. if !user.pin.isEmpty {
  3481. var fixUser = User.getDataCanNil(pin: user.pin, fmdb: fmdb)
  3482. if fixUser == nil {
  3483. fixUser = user
  3484. }
  3485. var indexAt = 0
  3486. if let range = oldTextForTextview.range(of: result) {
  3487. indexAt = oldTextForTextview.distance(from: oldTextForTextview.startIndex, to: range.lowerBound)
  3488. }
  3489. fixUser?.ex_block = "\(indexAt + fixUser!.fullName.count)"
  3490. listMentionWithText.append(fixUser!)
  3491. listMentionInTextField.append(fixUser!)
  3492. oldTextForTextview = oldTextForTextview.replacingOccurrences(of: result, with: "@\(fixUser!.fullName)")
  3493. lastTextLength = oldTextForTextview.count
  3494. }
  3495. cursor.close()
  3496. }
  3497. } catch {
  3498. rollback.pointee = true
  3499. print("Access database error: \(error.localizedDescription)")
  3500. }
  3501. })
  3502. }
  3503. } catch {
  3504. print("Invalid regex pattern")
  3505. }
  3506. editVC = UIViewController()
  3507. if let view = editVC.view {
  3508. // let tapGesture = ObjectGesture(target: self, action: #selector(dismissEditVC))
  3509. // tapGesture.message_id = oldTextForTextview
  3510. // view.addGestureRecognizer(tapGesture)
  3511. view.backgroundColor = .clear
  3512. let blurView = UIView()
  3513. let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
  3514. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  3515. blurEffectView.frame = blurView.bounds
  3516. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  3517. blurView.addSubview(blurEffectView)
  3518. blurView.sendSubviewToBack(blurEffectView)
  3519. view.addSubview(blurView)
  3520. blurView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  3521. let tapGesture = ObjectGesture(target: self, action: #selector(dismissEditVC))
  3522. tapGesture.message_id = oldTextForTextview
  3523. blurView.addGestureRecognizer(tapGesture)
  3524. editTextView = CustomTextView()
  3525. editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
  3526. editTextView.layer.borderWidth = 1.0
  3527. editTextView.textColor = UIColor.black
  3528. editTextView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  3529. editTextView.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  3530. editTextView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  3531. editTextView.font = UIFont.systemFont(ofSize: 12 + offset())
  3532. editTextView.delegate = self
  3533. editTextView.allowsEditingTextAttributes = true
  3534. editTextView.backgroundColor = .clear
  3535. view.addSubview(editTextView)
  3536. editTextView.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 15, paddingRight: 15)
  3537. constraintBottomeditTextView = editTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  3538. constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
  3539. constraintBottomeditTextView.isActive = true
  3540. constraintHeighteditTextView.isActive = true
  3541. editTextView.attributedText = oldTextForTextview.richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "", listMentionInTextField: listMentionInTextField)
  3542. editTextView.becomeFirstResponder()
  3543. 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)
  3544. buttonSendEdit.circle()
  3545. buttonSendEdit.isEnabled = true
  3546. buttonSendEdit.actionHandle(controlEvents: .touchUpInside,
  3547. ForAction:{() -> Void in
  3548. var newText = self.editTextView.text ?? ""
  3549. if newText.contains("@") && self.listMentionInTextField.count > 0 {
  3550. var diff: Int = 0
  3551. for i in 0..<self.listMentionInTextField.count {
  3552. let mention = self.listMentionInTextField[i]
  3553. guard let exBlockStr = mention.ex_block, let exBlock = Int(exBlockStr) else {
  3554. continue // skip if ex_block is nil or not an integer
  3555. }
  3556. let nameWithMention = ("@" + mention.firstName + " " + mention.lastName).trimmingCharacters(in: .whitespaces)
  3557. let pinString = "@\(mention.pin)"
  3558. let upperBound = exBlock + diff
  3559. let lowerBound = upperBound - nameWithMention.count + 1
  3560. guard lowerBound >= 0, upperBound < newText.count else {
  3561. continue // prevent index out-of-range
  3562. }
  3563. var afterMention = ""
  3564. let nextCharIndex = newText.index(newText.startIndex, offsetBy: upperBound + 1, limitedBy: newText.endIndex)
  3565. if let index = nextCharIndex, index < newText.endIndex {
  3566. let nextChar = newText[index]
  3567. if nextChar != "\n" && nextChar != " " {
  3568. afterMention = " "
  3569. }
  3570. }
  3571. let startIndex = newText.index(newText.startIndex, offsetBy: lowerBound)
  3572. let endIndex = newText.index(newText.startIndex, offsetBy: upperBound + 1)
  3573. let range = startIndex..<endIndex
  3574. if newText[range] == nameWithMention {
  3575. newText.replaceSubrange(range, with: pinString + afterMention)
  3576. diff += (pinString + afterMention).count - nameWithMention.count
  3577. }
  3578. }
  3579. }
  3580. if !newText.isEmpty && newText.trimmingCharacters(in: .whitespacesAndNewlines) != oldText {
  3581. let lastEdited = Int64(Date().currentTimeMillis())
  3582. 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)
  3583. Nexilis.addQueueMessage(message: message, isEditMessage: true)
  3584. DispatchQueue.global().async {
  3585. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3586. do {
  3587. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3588. "message_text" : newText,
  3589. "last_edited" : lastEdited
  3590. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  3591. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  3592. } catch {
  3593. rollback.pointee = true
  3594. print("Access database error: \(error.localizedDescription)")
  3595. }
  3596. })
  3597. }
  3598. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
  3599. if idx != nil{
  3600. self.dataMessages[idx!][TypeDataMessage.message_text] = newText
  3601. self.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
  3602. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  3603. }
  3604. }
  3605. self.isEditingMessage = false
  3606. self.listMentionWithText = self.tempListMentionWithText
  3607. self.listMentionInTextField = self.tempListMentionWithText
  3608. self.lastTextLength = self.textFieldSend.text?.count ?? 0
  3609. self.heightTableEditMention = nil
  3610. self.editVC.dismiss(animated: true)
  3611. })
  3612. buttonSendEdit.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  3613. view.addSubview(buttonSendEdit)
  3614. buttonSendEdit.anchor(right: view.rightAnchor, paddingRight: 15, width: 40, height: 40)
  3615. constraintBottomSendEditTV = buttonSendEdit.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  3616. constraintBottomSendEditTV.isActive = true
  3617. let viewMessage = UIView()
  3618. view.addSubview(viewMessage)
  3619. viewMessage.translatesAutoresizingMaskIntoConstraints = false
  3620. if (dataMessages[indexPath.row][TypeDataMessage.f_pin] as? String == User.getMyPin()) {
  3621. viewMessage.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 60).isActive = true
  3622. viewMessage.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15).isActive = true
  3623. viewMessage.backgroundColor = .blueBubbleColor
  3624. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  3625. } else {
  3626. viewMessage.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: 60).isActive = true
  3627. viewMessage.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
  3628. viewMessage.backgroundColor = .whiteBubbleColor
  3629. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  3630. }
  3631. viewMessage.bottomAnchor.constraint(equalTo: editTextView.topAnchor, constant: -15).isActive = true
  3632. viewMessage.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
  3633. viewMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  3634. viewMessage.layer.cornerRadius = 10.0
  3635. viewMessage.clipsToBounds = true
  3636. let messageText = UILabel()
  3637. messageText.numberOfLines = 0
  3638. messageText.lineBreakMode = .byWordWrapping
  3639. viewMessage.addSubview(messageText)
  3640. messageText.translatesAutoresizingMaskIntoConstraints = false
  3641. messageText.topAnchor.constraint(equalTo: viewMessage.topAnchor, constant: 15).isActive = true
  3642. messageText.leadingAnchor.constraint(equalTo: viewMessage.leadingAnchor, constant: 15).isActive = true
  3643. messageText.bottomAnchor.constraint(equalTo: viewMessage.bottomAnchor, constant: -15).isActive = true
  3644. messageText.trailingAnchor.constraint(equalTo: viewMessage.trailingAnchor, constant: -15).isActive = true
  3645. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  3646. messageText.font = .systemFont(ofSize: 12 + offset())
  3647. messageText.attributedText = oldText.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  3648. tableMentionEdit = UITableView()
  3649. tableMentionEdit.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditMention")
  3650. tableMentionEdit.dataSource = self
  3651. tableMentionEdit.delegate = self
  3652. tableMentionEdit.contentInset = UIEdgeInsets(top: -25, left: 0, bottom: 0, right: 0)
  3653. tableMentionEdit.backgroundColor = .white
  3654. view.addSubview(tableMentionEdit)
  3655. tableMentionEdit.anchor(left: view.leftAnchor, bottom: editTextView.topAnchor, right: view.rightAnchor)
  3656. heightTableEditMention = tableMentionEdit.heightAnchor.constraint(equalToConstant: 0)
  3657. self.heightTableEditMention.isActive = true
  3658. }
  3659. editVC.modalTransitionStyle = .crossDissolve
  3660. editVC.modalPresentationStyle = .overFullScreen
  3661. self.present(editVC, animated: true, completion: {
  3662. self.constraintHeighteditTextView.constant = self.editTextView.contentSize.height
  3663. if self.constraintHeighteditTextView.constant > 95 {
  3664. self.constraintHeighteditTextView.constant = 95.0
  3665. }
  3666. })
  3667. }
  3668. @objc func dismissEditVC(_ sender: ObjectGesture) {
  3669. if editTextView.text == sender.message_id {
  3670. isEditingMessage = false
  3671. listMentionWithText = tempListMentionWithText
  3672. listMentionInTextField = tempListMentionWithText
  3673. lastTextLength = textFieldSend.text?.count ?? 0
  3674. heightTableEditMention = nil
  3675. editVC.dismiss(animated: true)
  3676. } else if self.isEditingMessage {
  3677. let alert = LibAlertController(title: "".localized(), message: "Discard edit?".localized(), preferredStyle: .alert)
  3678. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.cancel, handler: nil))
  3679. alert.addAction(UIAlertAction(title: "Discard".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  3680. self.isEditingMessage = false
  3681. self.listMentionWithText = self.tempListMentionWithText
  3682. self.listMentionInTextField = self.tempListMentionWithText
  3683. self.lastTextLength = self.textFieldSend.text?.count ?? 0
  3684. self.heightTableEditMention = nil
  3685. self.editVC.dismiss(animated: true)
  3686. }))
  3687. editVC.present(alert, animated: true, completion: nil)
  3688. } else {
  3689. lastTextLength = self.textFieldSend.text?.count ?? 0
  3690. editVC.dismiss(animated: true)
  3691. }
  3692. }
  3693. @objc func cancelAction() {
  3694. DispatchQueue.main.async {
  3695. if self.copySession {
  3696. self.copySession = false
  3697. } else if self.forwardSession {
  3698. self.forwardSession = false
  3699. } else if self.deleteSession {
  3700. self.deleteSession = false
  3701. } else if self.isSearching {
  3702. self.countMatchesSearch = 0
  3703. self.isSearching = false
  3704. }
  3705. if self.viewTextfield.isHidden {
  3706. self.viewTextfield.isHidden = false
  3707. }
  3708. if self.viewAttachment.isHidden {
  3709. self.viewAttachment.isHidden = false
  3710. }
  3711. if self.containerAction.isHidden {
  3712. self.containerAction.isHidden = false
  3713. }
  3714. if self.viewButton.isHidden {
  3715. self.viewButton.isHidden = false
  3716. }
  3717. if self.constraintBottomTableViewWithTextfield.constant == -60.0 {
  3718. self.constraintBottomTableViewWithTextfield.constant = self.constraintBottomTableViewWithTextfield.constant + 70
  3719. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  3720. if (self.currentIndexpath != nil) {
  3721. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: true)
  3722. } else {
  3723. self.tableChatView.scrollToBottom()
  3724. }
  3725. })
  3726. }
  3727. let data = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  3728. for i in 0..<data.count {
  3729. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == data[i]["message_id"] as? String ?? ""})
  3730. if idx != nil{
  3731. self.dataMessages[idx!]["isSelected"] = false
  3732. }
  3733. }
  3734. self.tableChatView.reloadData()
  3735. self.setRightButtonItem()
  3736. self.changeAppBar()
  3737. self.containerMultpileSelectSession.removeFromSuperview()
  3738. self.checkNewMessage(tableView: self.tableChatView)
  3739. }
  3740. }
  3741. private func addMultipleSelectSession() {
  3742. viewTextfield.isHidden = true
  3743. viewAttachment.isHidden = true
  3744. containerAction.isHidden = true
  3745. viewButton.isHidden = true
  3746. constraintBottomTableViewWithTextfield.constant = constraintBottomTableViewWithTextfield.constant - 70
  3747. view.addSubview(containerMultpileSelectSession)
  3748. containerMultpileSelectSession.translatesAutoresizingMaskIntoConstraints = false
  3749. constraintBottomContainerMultpileSelectSession = containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
  3750. NSLayoutConstraint.activate([
  3751. containerMultpileSelectSession.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  3752. containerMultpileSelectSession.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  3753. constraintBottomContainerMultpileSelectSession,
  3754. containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 50)
  3755. ])
  3756. containerMultpileSelectSession.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  3757. addSubviewMultipleSession()
  3758. }
  3759. private func addSubviewMultipleSession() {
  3760. let container = UIView()
  3761. containerMultpileSelectSession.addSubview(container)
  3762. container.translatesAutoresizingMaskIntoConstraints = false
  3763. NSLayoutConstraint.activate([
  3764. container.leadingAnchor.constraint(equalTo: containerMultpileSelectSession.leadingAnchor),
  3765. container.trailingAnchor.constraint(equalTo:containerMultpileSelectSession.trailingAnchor),
  3766. container.bottomAnchor.constraint(equalTo: containerMultpileSelectSession.bottomAnchor),
  3767. container.heightAnchor.constraint(equalToConstant: 50)
  3768. ])
  3769. container.layer.shadowOpacity = 0.7
  3770. container.layer.shadowOffset = CGSize(width: 3, height: 3)
  3771. container.layer.shadowRadius = 3.0
  3772. container.layer.shadowColor = UIColor.black.cgColor
  3773. container.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  3774. if !isSearching {
  3775. let title = UILabel()
  3776. container.addSubview(title)
  3777. title.translatesAutoresizingMaskIntoConstraints = false
  3778. NSLayoutConstraint.activate([
  3779. title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  3780. title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  3781. ])
  3782. let countSelected = dataMessages.filter({ $0["isSelected"] as! Bool == true }).count
  3783. title.text = "\(countSelected) " + "Selected".localized()
  3784. title.textColor = .mainColor
  3785. title.font = UIFont.systemFont(ofSize: 15).bold
  3786. let button = UIImageView()
  3787. container.addSubview(button)
  3788. button.translatesAutoresizingMaskIntoConstraints = false
  3789. NSLayoutConstraint.activate([
  3790. button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
  3791. button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  3792. button.widthAnchor.constraint(equalToConstant: 30),
  3793. button.heightAnchor.constraint(equalToConstant: 30),
  3794. ])
  3795. if copySession {
  3796. button.image = UIImage(systemName: "doc.on.doc")
  3797. if countSelected == 0{
  3798. button.tintColor = .gray
  3799. } else {
  3800. button.tintColor = .mainColor
  3801. }
  3802. } else if forwardSession {
  3803. button.image = UIImage(systemName: "arrowshape.turn.up.right")
  3804. if countSelected == 0{
  3805. button.tintColor = .gray
  3806. } else {
  3807. button.tintColor = .mainColor
  3808. }
  3809. } else if deleteSession {
  3810. button.image = UIImage(systemName: "trash")
  3811. if countSelected == 0{
  3812. button.tintColor = .gray
  3813. } else {
  3814. button.tintColor = .red
  3815. }
  3816. }
  3817. let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
  3818. button.isUserInteractionEnabled = true
  3819. button.addGestureRecognizer(buttonGesture)
  3820. let selectedMessage = dataMessages.filter({ $0["isSelected"] as! Bool == true })
  3821. if selectedMessage.count > 0 {
  3822. for i in 0..<selectedMessage.count {
  3823. if let isGroupingImages = groupImages[selectedMessage[i]["message_id"] as? String ?? ""] {
  3824. title.text = "\(countSelected + (isGroupingImages.count - 1)) " + "Selected".localized()
  3825. }
  3826. }
  3827. }
  3828. } else {
  3829. buttonUp = UIButton()
  3830. container.addSubview(buttonUp)
  3831. buttonUp.translatesAutoresizingMaskIntoConstraints = false
  3832. NSLayoutConstraint.activate([
  3833. buttonUp.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10),
  3834. buttonUp.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  3835. buttonUp.widthAnchor.constraint(equalToConstant: 30),
  3836. buttonUp.heightAnchor.constraint(equalToConstant: 30),
  3837. ])
  3838. buttonUp.addTarget(self, action: #selector(upSearchText), for: .touchUpInside)
  3839. buttonDown = UIButton()
  3840. container.addSubview(buttonDown)
  3841. buttonDown.translatesAutoresizingMaskIntoConstraints = false
  3842. NSLayoutConstraint.activate([
  3843. buttonDown.leadingAnchor.constraint(equalTo: buttonUp.trailingAnchor, constant: 15),
  3844. buttonDown.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  3845. buttonDown.widthAnchor.constraint(equalToConstant: 30),
  3846. buttonDown.heightAnchor.constraint(equalToConstant: 30),
  3847. ])
  3848. buttonDown.addTarget(self, action: #selector(downSearchText), for: .touchUpInside)
  3849. buttonUp.setImage(UIImage(systemName: "chevron.up"), for: .normal)
  3850. buttonUp.tintColor = .gray
  3851. buttonDown.setImage(UIImage(systemName: "chevron.down"), for: .normal)
  3852. buttonDown.tintColor = .gray
  3853. titleSearchMatches = UILabel()
  3854. container.addSubview(titleSearchMatches)
  3855. titleSearchMatches.translatesAutoresizingMaskIntoConstraints = false
  3856. NSLayoutConstraint.activate([
  3857. titleSearchMatches.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  3858. titleSearchMatches.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  3859. ])
  3860. titleSearchMatches.textColor = .mainColor
  3861. titleSearchMatches.font = UIFont.systemFont(ofSize: 15.0).bold
  3862. titleSearchMatches.isHidden = true
  3863. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  3864. self.searchBar.becomeFirstResponder()
  3865. })
  3866. }
  3867. }
  3868. @objc func upSearchText() {
  3869. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch + 1)
  3870. }
  3871. @objc func downSearchText() {
  3872. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch - 1)
  3873. }
  3874. @objc func sessionAction() {
  3875. if copySession {
  3876. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  3877. let countSelected = dataMessages.count
  3878. if countSelected == 0 {
  3879. return
  3880. }
  3881. var nameTopic = "Lounge".localized()
  3882. if !dataTopic.isEmpty {
  3883. nameTopic = dataTopic["title"] as? String ?? ""
  3884. }
  3885. var text = "*^\(dataGroup["f_name"]!!) (\(nameTopic))^*"
  3886. for i in 0..<countSelected {
  3887. let stringDate = (dataMessages[i]["server_date"] as? String ?? "")
  3888. let date = Date(milliseconds: Int64(stringDate)!)
  3889. let formatterDate = DateFormatter()
  3890. let formatterTime = DateFormatter()
  3891. formatterDate.dateFormat = "dd/MM/yy"
  3892. formatterDate.locale = NSLocale(localeIdentifier: "id") as Locale?
  3893. formatterTime.dateFormat = "HH:mm"
  3894. formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
  3895. let dataProfile = getDataProfile(f_pin: dataMessages[i]["f_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "")
  3896. let textCopied = (dataMessages[i]["message_text"] as? String ?? "").richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "")
  3897. text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  3898. }
  3899. text = text + "\n\n\nchat " + "Powered by Nexilis".localized()
  3900. DispatchQueue.main.async {
  3901. UIPasteboard.general.string = text
  3902. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  3903. }
  3904. cancelAction()
  3905. } else if forwardSession {
  3906. var dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  3907. let countSelected = dataMessages.count
  3908. if countSelected == 0 {
  3909. return
  3910. }
  3911. for i in 0..<countSelected {
  3912. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  3913. var tempData = dataMessages
  3914. tempData.remove(at: 0)
  3915. var dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  3916. tempData.insert(contentsOf: dataMessageInGrouping, at: i)
  3917. dataMessages = tempData
  3918. }
  3919. }
  3920. contactChatNav.modalPresentationStyle = .custom
  3921. contactChatNav.navigationBar.tintColor = .white
  3922. contactChatNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  3923. contactChatNav.navigationBar.isTranslucent = false
  3924. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  3925. contactChatNav.navigationBar.titleTextAttributes = textAttributes
  3926. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  3927. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  3928. contactChatNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  3929. if let controller = contactChatNav.viewControllers.first as? ContactChatViewController {
  3930. controller.isChooser = { [weak self] scope, pin in
  3931. if scope == "3" {
  3932. let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
  3933. editorPersonalVC.unique_l_pin = pin
  3934. editorPersonalVC.dataMessageForward = dataMessages
  3935. self?.navigationController?.replaceAllViewController(with: editorPersonalVC, animated: true)
  3936. } else {
  3937. let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
  3938. editorGroupVC.unique_l_pin = pin
  3939. editorGroupVC.dataMessageForward = dataMessages
  3940. self?.navigationController?.replaceAllViewController(with: editorGroupVC, animated: true)
  3941. }
  3942. }
  3943. }
  3944. self.present(contactChatNav, animated: true, completion: nil)
  3945. } else if deleteSession {
  3946. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  3947. let countSelected = dataMessages.count
  3948. if countSelected == 0 {
  3949. return
  3950. }
  3951. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  3952. if let action = self.actionDelete(for: "me", title: "Delete".localized() + " \(countSelected) " + "For Me".localized(), dataMessages: dataMessages) {
  3953. alertController.addAction(action)
  3954. }
  3955. let idMe = User.getMyPin() as String?
  3956. let dataFilterFpin = dataMessages.filter({ $0["f_pin"] as? String != idMe})
  3957. let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1"})
  3958. // let statusDataRead = dataMessages.filter({ Int($0["status"] as? String ?? "")! >= 4})
  3959. let statusFailed = dataMessages.filter({ Int($0["status"] as? String ?? "")! == 0})
  3960. if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusFailed.count == 0 {
  3961. if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
  3962. alertController.addAction(action)
  3963. }
  3964. }
  3965. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  3966. self.present(alertController, animated: true)
  3967. }
  3968. }
  3969. private func deleteMessage(l_pin: String, message_id: String, scope: String, type: String, chat: String) {
  3970. let tmessage = CoreMessage_TMessageBank.deleteMessage(l_pin: l_pin, messageId: message_id, scope: scope, type: type, chat: chat)
  3971. Nexilis.deleteQueueMessage(message: tmessage)
  3972. }
  3973. private func queryMessageReply(message_id: String) -> [String: Any?] {
  3974. var dataQuery: [String: Any] = [:]
  3975. Database.shared.database?.inTransaction({ fmdb, rollback in
  3976. 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() {
  3977. dataQuery["message_id"] = c.string(forColumnIndex: 0)
  3978. dataQuery["f_pin"] = c.string(forColumnIndex: 1)
  3979. dataQuery["message_text"] = c.string(forColumnIndex: 2)
  3980. dataQuery["attachment_flag"] = c.string(forColumnIndex: 3)
  3981. dataQuery["thumb_id"] = c.string(forColumnIndex: 4)
  3982. dataQuery["image_id"] = c.string(forColumnIndex: 5)
  3983. dataQuery["video_id"] = c.string(forColumnIndex: 6)
  3984. dataQuery["file_id"] = c.string(forColumnIndex: 7)
  3985. c.close()
  3986. }
  3987. })
  3988. return dataQuery
  3989. }
  3990. @objc func segmentedControlValueChanged(_ sender: segmentedControllerObject) {
  3991. switch sender.selectedSegmentIndex {
  3992. case 0:
  3993. sender.navigation.viewControllers[0].children[1].view.isHidden = true
  3994. break;
  3995. case 1:
  3996. sender.navigation.viewControllers[0].children[1].view.isHidden = false
  3997. break;
  3998. default:
  3999. break;
  4000. }
  4001. }
  4002. private func copyOption(indexPath: IndexPath) -> UIMenu {
  4003. var ratingButtonTitles = ["Text".localized(), "Image".localized()]
  4004. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  4005. ratingButtonTitles = ["Image".localized()]
  4006. }
  4007. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  4008. let copyActions = ratingButtonTitles
  4009. .enumerated()
  4010. .map { index, title in
  4011. return UIAction(
  4012. title: title,
  4013. identifier: nil,
  4014. handler: {(_) in
  4015. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  4016. DispatchQueue.main.async {
  4017. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4018. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4019. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4020. if let dirPath = paths.first {
  4021. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  4022. if FileManager.default.fileExists(atPath: imageURL.path) {
  4023. let image = UIImage(contentsOfFile: imageURL.path)
  4024. UIPasteboard.general.image = image
  4025. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4026. }
  4027. else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  4028. do {
  4029. if var imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  4030. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  4031. if dataDecrypt != nil {
  4032. imageData = dataDecrypt!
  4033. }
  4034. let image = UIImage(data: imageData)
  4035. UIPasteboard.general.image = image
  4036. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4037. }
  4038. } catch {
  4039. }
  4040. }
  4041. }
  4042. }
  4043. return
  4044. }
  4045. if (index == 0) {
  4046. DispatchQueue.main.async {
  4047. UIPasteboard.general.string = dataMessages[indexPath.row]["message_text"] as? String
  4048. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  4049. }
  4050. } else {
  4051. DispatchQueue.main.async {
  4052. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4053. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4054. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4055. if let dirPath = paths.first {
  4056. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  4057. if FileManager.default.fileExists(atPath: imageURL.path) {
  4058. let image = UIImage(contentsOfFile: imageURL.path)
  4059. UIPasteboard.general.image = image
  4060. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4061. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  4062. do {
  4063. if var imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  4064. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  4065. if dataDecrypt != nil {
  4066. imageData = dataDecrypt!
  4067. }
  4068. let image = UIImage(data: imageData)
  4069. UIPasteboard.general.image = image
  4070. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4071. }
  4072. } catch {
  4073. }
  4074. }
  4075. }
  4076. }
  4077. }
  4078. self.dismissKeyboard()
  4079. })
  4080. }
  4081. return UIMenu(
  4082. title: "Copy".localized(),
  4083. image: UIImage(systemName: "doc.on.doc.fill"),
  4084. children: copyActions)
  4085. }
  4086. private func actionDelete(for type: String, title: String, dataMessages: [[String: Any?]]) -> UIAlertAction? {
  4087. return UIAlertAction(title: title, style: .destructive) { [unowned self] _ in
  4088. for i in 0..<dataMessages.count {
  4089. if (type == "me") {
  4090. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  4091. for i in 0..<groupingImages.count {
  4092. self.deleteMessage(l_pin: dataGroup["group_id"] as? String ?? "", message_id: groupingImages[i].messageId, scope: MessageScope.GROUP, type: "1", chat: dataTopic["chat_id"] as? String ?? "")
  4093. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId })
  4094. if idx != nil {
  4095. self.dataMessages.remove(at: idx!)
  4096. if (idx == self.dataMessages.count - 1) {
  4097. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4098. }
  4099. for i in 0..<dataDates.count {
  4100. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  4101. dataDates.remove(at: i)
  4102. }
  4103. }
  4104. }
  4105. }
  4106. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  4107. } else {
  4108. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  4109. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  4110. imageView.tintColor = .white
  4111. 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)
  4112. banner.show()
  4113. } else {
  4114. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  4115. for i in 0..<groupingImages.count {
  4116. self.deleteMessage(l_pin: dataGroup["group_id"] as? String ?? "", message_id: groupingImages[i].messageId, scope: MessageScope.GROUP, type: "2", chat: dataTopic["chat_id"] as? String ?? "")
  4117. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId})
  4118. if idx != nil {
  4119. self.dataMessages[idx!]["lock"] = "1"
  4120. self.dataMessages[idx!]["attachment_flag"] = "0"
  4121. self.dataMessages[idx!]["reff_id"] = ""
  4122. }
  4123. }
  4124. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[0].messageId}) {
  4125. var dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  4126. dataMessageInGrouping.remove(at: 0)
  4127. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx+1)
  4128. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  4129. }
  4130. } else {
  4131. self.deleteMessage(l_pin: dataGroup["group_id"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: MessageScope.GROUP, type: "1", chat: dataTopic["chat_id"] as? String ?? "")
  4132. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  4133. if idx != nil {
  4134. self.dataMessages.remove(at: idx!)
  4135. if (idx == self.dataMessages.count - 1) {
  4136. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4137. }
  4138. for i in 0..<dataDates.count {
  4139. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  4140. dataDates.remove(at: i)
  4141. }
  4142. }
  4143. }
  4144. }
  4145. }
  4146. }
  4147. } else {
  4148. self.deleteMessage(l_pin: dataGroup["group_id"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: MessageScope.GROUP, type: "2", chat: dataTopic["chat_id"] as? String ?? "")
  4149. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == dataMessages[i]["message_id"] as? String ?? ""})
  4150. if idx != nil {
  4151. self.dataMessages[idx!]["lock"] = "1"
  4152. self.dataMessages[idx!]["attachment_flag"] = "0"
  4153. self.dataMessages[idx!]["reff_id"] = ""
  4154. }
  4155. }
  4156. if self.listTimerCredential[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  4157. self.listTimerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  4158. self.timerCredential[dataMessages[i]["message_id"] as? String ?? ""]?.invalidate()
  4159. self.timerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  4160. }
  4161. }
  4162. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4163. cancelAction()
  4164. }
  4165. }
  4166. @objc func deleteReplyView() {
  4167. if self.containerPreviewReply.isDescendant(of: self.viewTextfield) {
  4168. self.containerPreviewReply.subviews.forEach { $0.removeFromSuperview() }
  4169. self.containerPreviewReply.removeConstraints(self.containerPreviewReply.constraints)
  4170. self.containerPreviewReply.removeFromSuperview()
  4171. self.reffId = nil
  4172. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  4173. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50 - (self.offset()*3)
  4174. if self.contraintBottomMention.constant > 0 {
  4175. self.contraintBottomMention.constant = self.contraintBottomMention.constant - 50
  4176. }
  4177. }, completion: nil)
  4178. }
  4179. }
  4180. @objc func removeLinkPreviewUntilEmptyTextView() {
  4181. isAlwaysHideLinkPreview = true
  4182. deleteLinkPreview()
  4183. }
  4184. @objc func deleteLinkPreview() {
  4185. if self.containerLink.isDescendant(of: self.viewTextfield) {
  4186. self.containerLink.subviews.forEach { $0.removeFromSuperview() }
  4187. self.containerLink.removeConstraints(self.containerLink.constraints)
  4188. self.containerLink.removeFromSuperview()
  4189. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  4190. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
  4191. if self.contraintBottomMention.constant > 0 {
  4192. self.contraintBottomMention.constant = self.contraintBottomMention.constant - 80
  4193. }
  4194. }, completion: nil)
  4195. self.showingLink = ""
  4196. }
  4197. if self.reffId != nil {
  4198. self.bottomAnchorPreviewReply.isActive = false
  4199. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  4200. self.bottomAnchorPreviewReply.isActive = true
  4201. }
  4202. }
  4203. }
  4204. extension EditorGroup: UICollectionViewDelegate, UICollectionViewDataSource {
  4205. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  4206. return 76
  4207. }
  4208. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  4209. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellSticker", for: indexPath)
  4210. if (cell.contentView.subviews.count > 0) {
  4211. cell.contentView.subviews[0].removeFromSuperview()
  4212. }
  4213. let imageSticker = UIImageView()
  4214. cell.contentView.addSubview(imageSticker)
  4215. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  4216. NSLayoutConstraint.activate([
  4217. imageSticker.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
  4218. imageSticker.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
  4219. imageSticker.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor),
  4220. imageSticker.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor)
  4221. ])
  4222. var imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  4223. if imageStickerBundle == nil {
  4224. imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  4225. }
  4226. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  4227. return cell
  4228. }
  4229. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  4230. sendChat(message_text: "sticker/\(stickers[indexPath.row])", attachment_flag: "11", viewController: self)
  4231. constraintBottomAttachment.constant = 0.0
  4232. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  4233. self.viewSticker.removeFromSuperview()
  4234. }
  4235. public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  4236. 1
  4237. }
  4238. public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  4239. return self.previewItem!
  4240. }
  4241. }
  4242. extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayerDelegate {
  4243. // public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  4244. // checkNewMessage(tableView: tableView)
  4245. // }
  4246. public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  4247. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  4248. if self.tableChatView.alpha != 1.0 {
  4249. UIView.animate(withDuration: 0.5, animations: {
  4250. self.tableChatView.alpha = 1.0
  4251. })
  4252. }
  4253. })
  4254. }
  4255. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  4256. lastY = scrollView.contentOffset.y
  4257. DispatchQueue.main.async { [self] in
  4258. if tableChatView.alpha != 1 {
  4259. return
  4260. }
  4261. checkNewMessage(tableView: self.tableChatView)
  4262. }
  4263. }
  4264. public func numberOfSections(in tableView: UITableView) -> Int {
  4265. if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
  4266. return 1
  4267. }
  4268. return dataDates.count
  4269. }
  4270. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  4271. if tableView == tableViewConfigFile {
  4272. return 2
  4273. }
  4274. if tableView == tableMention || tableView == tableMentionEdit {
  4275. return listMentionWithText.count
  4276. }
  4277. let count = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section] }).count
  4278. return count
  4279. }
  4280. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  4281. if tableView == tableViewConfigFile {
  4282. return nil
  4283. }
  4284. if tableView == tableMention || tableView == tableMentionEdit {
  4285. return .none
  4286. }
  4287. let containerView = UIView()
  4288. containerView.backgroundColor = .clear
  4289. let dateView = UIView()
  4290. containerView.addSubview(dateView)
  4291. dateView.translatesAutoresizingMaskIntoConstraints = false
  4292. var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
  4293. topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10.0)
  4294. NSLayoutConstraint.activate([
  4295. topAnchor,
  4296. dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10.0),
  4297. dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
  4298. // dateView.heightAnchor.constraint(equalToConstant: 30),
  4299. dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  4300. ])
  4301. dateView.backgroundColor = .orangeColor
  4302. dateView.layer.cornerRadius = 15.0
  4303. dateView.clipsToBounds = true
  4304. let labelDate = UILabel()
  4305. dateView.addSubview(labelDate)
  4306. labelDate.translatesAutoresizingMaskIntoConstraints = false
  4307. NSLayoutConstraint.activate([
  4308. labelDate.centerYAnchor.constraint(equalTo: dateView.centerYAnchor),
  4309. labelDate.centerXAnchor.constraint(equalTo: dateView.centerXAnchor),
  4310. labelDate.leadingAnchor.constraint(equalTo: dateView.leadingAnchor, constant: 10),
  4311. labelDate.trailingAnchor.constraint(equalTo: dateView.trailingAnchor, constant: -10),
  4312. ])
  4313. labelDate.textAlignment = .center
  4314. labelDate.textColor = .secondaryColor
  4315. labelDate.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  4316. labelDate.text = dataDates[section]
  4317. return containerView
  4318. }
  4319. public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  4320. if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
  4321. return 0
  4322. }
  4323. return 50
  4324. }
  4325. public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  4326. if tableView == tableViewConfigFile {
  4327. tableView.deselectRow(at: indexPath, animated: true)
  4328. var type = ""
  4329. if indexPath.row == 0 {
  4330. type = "share,download"
  4331. } else {
  4332. type = "forward"
  4333. }
  4334. if !specFileString.contains(type) {
  4335. if !specFileString.isEmpty {
  4336. specFileString += ","
  4337. }
  4338. specFileString += type
  4339. } else {
  4340. specFileString = specFileString.replacingOccurrences(of: type, with: "")
  4341. if specFileString == "," {
  4342. specFileString = ""
  4343. }
  4344. }
  4345. if specFileString.isEmpty {
  4346. buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
  4347. } else {
  4348. buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
  4349. }
  4350. tableView.reloadData()
  4351. return
  4352. }
  4353. if tableView == tableMention || tableView == tableMentionEdit {
  4354. tableView.deselectRow(at: indexPath, animated: true)
  4355. var nowTextField = textFieldSend!
  4356. if tableView == tableMentionEdit {
  4357. nowTextField = editTextView
  4358. }
  4359. let fulltextForMention = nowTextField.text.substring(from: 0, to: lastPositionCursorMention - 1)
  4360. let diff = nowTextField.text.count - fulltextForMention.count
  4361. let lines = fulltextForMention.split(separator: "\n")
  4362. if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
  4363. let words = lines[lastLineIndex].split(separator: " ")
  4364. if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
  4365. let lastWord = words[lastWordIndex]
  4366. if let rangeLastWord = fulltextForMention.range(of: lastWord, options: .backwards) {
  4367. listMentionInTextField.append(listMentionWithText[indexPath.row])
  4368. var addSpaceAfterReplacement = ""
  4369. if diff == 0 {
  4370. addSpaceAfterReplacement = " "
  4371. }
  4372. var text = nowTextField.text ?? ""
  4373. let nameMention = listMentionWithText[indexPath.row].fullName.trimmingCharacters(in: .whitespaces)
  4374. listMentionInTextField.last?.ex_block = "\(fulltextForMention.distance(from: fulltextForMention.startIndex, to: rangeLastWord.lowerBound) + nameMention.count)" //upperbound
  4375. let replacementText = "@\(nameMention)"
  4376. // Replace the old text with the new text using the replaceSubrange(_:with:) method
  4377. text.replaceSubrange(rangeLastWord, with: replacementText + addSpaceAfterReplacement)
  4378. nowTextField.attributedText = text.richText(isEditing: true, group_id: self.dataGroup["group_id"] as? String ?? "", listMentionInTextField: listMentionInTextField)
  4379. let newPosition = nowTextField.position(from: nowTextField.beginningOfDocument, offset: nowTextField.text.count - diff)
  4380. nowTextField.selectedTextRange = nowTextField.textRange(from: newPosition!, to: newPosition!)
  4381. hideMention()
  4382. lastTextLength = nowTextField.text.count
  4383. return
  4384. }
  4385. }
  4386. }
  4387. }
  4388. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section] })
  4389. if copySession || forwardSession || deleteSession {
  4390. guard indexPath.row < dataMessages.count else {
  4391. return
  4392. }
  4393. // if copySession && dataMessages[indexPath.row]["f_pin"] as? String ?? "" != "-999" {
  4394. // return
  4395. // }
  4396. if (dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" != "0" || dataMessages[indexPath.row]["lock"] as? String == "1") && !forwardSession && !deleteSession {
  4397. return
  4398. }
  4399. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == dataMessages[indexPath.row]["message_id"] as? String ?? ""})
  4400. if idx != nil {
  4401. self.dataMessages[idx!]["isSelected"] = !(self.dataMessages[idx!]["isSelected"] as! Bool)
  4402. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  4403. }
  4404. containerMultpileSelectSession.subviews.forEach({ $0.removeFromSuperview() })
  4405. addSubviewMultipleSession()
  4406. return
  4407. }
  4408. 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 || !(dataMessages[indexPath.row]["audio_id"] as? String ?? "").isEmpty {
  4409. if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
  4410. return
  4411. } else {
  4412. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  4413. if file.isEmpty {
  4414. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  4415. if file.isEmpty {
  4416. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  4417. if file.isEmpty {
  4418. file = dataMessages[indexPath.row]["audio_id"] as? String ?? ""
  4419. }
  4420. }
  4421. }
  4422. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4423. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4424. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4425. if let dirPath = paths.first {
  4426. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  4427. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  4428. return
  4429. }
  4430. }
  4431. }
  4432. }
  4433. let message = dataMessages[indexPath.row]
  4434. if let attachmentFlag = message["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  4435. if attachmentFlag == "27" || attachmentFlag == "26" {
  4436. let streamingController = (attachmentFlag == "27") ? QmeraCreateStreamingViewController() : CreateSeminarViewController()
  4437. switch(attachmentFlag){
  4438. case "27":
  4439. (streamingController as! QmeraCreateStreamingViewController).isJoin = true
  4440. default:
  4441. (streamingController as! CreateSeminarViewController).isJoin = true
  4442. }
  4443. if let messageText = message["message_text"],
  4444. let messageText = messageText as? String,
  4445. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  4446. if json["blog"] == nil {
  4447. json["blog"] = message["blog_id"] ?? nil
  4448. }
  4449. switch(attachmentFlag){
  4450. case "27":
  4451. (streamingController as! QmeraCreateStreamingViewController).data = json
  4452. default:
  4453. (streamingController as! CreateSeminarViewController).data = json
  4454. }
  4455. }
  4456. let streamingNav = CustomNavigationController(rootViewController: streamingController)
  4457. streamingNav.modalPresentationStyle = .custom
  4458. streamingNav.navigationBar.tintColor = .white
  4459. streamingNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  4460. streamingNav.navigationBar.isTranslucent = false
  4461. streamingNav.navigationBar.overrideUserInterfaceStyle = .dark
  4462. streamingNav.navigationBar.barStyle = .black
  4463. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  4464. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  4465. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  4466. streamingNav.navigationBar.titleTextAttributes = textAttributes
  4467. streamingNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  4468. streamingNav.navigationBar.isTranslucent = false
  4469. navigationController?.present(streamingNav, animated: true, completion: nil)
  4470. }
  4471. }
  4472. }
  4473. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  4474. if tableView == tableViewConfigFile {
  4475. let cell = tableView.dequeueReusableCell(withIdentifier: "cellConfigFile", for: indexPath as IndexPath)
  4476. var content = cell.defaultContentConfiguration()
  4477. content.textProperties.font = .systemFont(ofSize: 16, weight: .medium)
  4478. content.textProperties.color = .label
  4479. content.secondaryTextProperties.font = .systemFont(ofSize: 14)
  4480. content.secondaryTextProperties.color = .gray
  4481. if indexPath.row == 0 {
  4482. content.text = "Can Share and Download".localized()
  4483. content.secondaryText = "The user, as the receiver, can share and download the attachment.".localized()
  4484. cell.accessoryType = specFileString.contains("share,download") ? .checkmark : .none
  4485. } else {
  4486. content.text = "Can Forward".localized()
  4487. content.secondaryText = "The user, as the receiver, can forward the attachment.".localized()
  4488. cell.accessoryType = specFileString.contains("forward") ? .checkmark : .none
  4489. }
  4490. cell.contentConfiguration = content
  4491. cell.tintColor = .black
  4492. return cell
  4493. }
  4494. if tableView == tableMention || tableView == tableMentionEdit {
  4495. let cellMention = tableView.dequeueReusableCell(withIdentifier: tableView == tableMention ? "cellMention" : "cellEditMention", for: indexPath as IndexPath)
  4496. var content = cellMention.defaultContentConfiguration()
  4497. content.textProperties.font = UIFont.systemFont(ofSize: 11 + offset())
  4498. content.imageProperties.tintColor = .black
  4499. content.imageProperties.maximumSize = CGSize(width: 24, height: 24)
  4500. if indexPath.row < listMentionWithText.count {
  4501. getImage(name: listMentionWithText[indexPath.row].thumb, placeholderImage: UIImage(systemName: "person"), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
  4502. content.image = image
  4503. })
  4504. content.text = listMentionWithText[indexPath.row].firstName + " " + listMentionWithText[indexPath.row].lastName
  4505. }
  4506. cellMention.contentConfiguration = content
  4507. return cellMention
  4508. }
  4509. let idMe = User.getMyPin() as String?
  4510. let dataMessages = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  4511. let cellMessage = tableView.dequeueReusableCell(withIdentifier: "cellEditorGroup", for: indexPath as IndexPath)
  4512. cellMessage.backgroundColor = .clear
  4513. cellMessage.selectionStyle = .none
  4514. cellMessage.contentView.subviews.forEach({ $0.removeConstraints($0.constraints) })
  4515. cellMessage.contentView.subviews.forEach({ $0.removeFromSuperview() })
  4516. let profileMessage = UIImageView()
  4517. profileMessage.frame.size = CGSize(width: 35, height: 35)
  4518. cellMessage.contentView.addSubview(profileMessage)
  4519. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  4520. let tapGestureRecognizer = ObjectGesture(target: self, action: #selector(profilePersonTapped(_:)))
  4521. tapGestureRecognizer.message_id = dataMessages[indexPath.row]["f_pin"] as? String ?? ""
  4522. profileMessage.isUserInteractionEnabled = true
  4523. profileMessage.addGestureRecognizer(tapGestureRecognizer)
  4524. let containerMessage = UIView()
  4525. let messageIdChat = (dataMessages[indexPath.row]["message_id"] as? String) ?? ""
  4526. let thumbChat = dataMessages[indexPath.row]["thumb_id"] as? String ?? ""
  4527. let imageChat = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  4528. let videoChat = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  4529. let fileChat = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  4530. let reffChat = dataMessages[indexPath.row]["reff_id"] as? String ?? ""
  4531. let audioChat = (dataMessages[indexPath.row]["audio_id"] as? String) ?? ""
  4532. let gifChat = (dataMessages[indexPath.row]["gif_id"] as? String) ?? ""
  4533. let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as? String ?? "")]
  4534. cellMessage.contentView.addSubview(containerMessage)
  4535. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  4536. let timeMessage = UILabel()
  4537. timeMessage.numberOfLines = 0
  4538. cellMessage.contentView.addSubview(timeMessage)
  4539. timeMessage.translatesAutoresizingMaskIntoConstraints = false
  4540. if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
  4541. (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
  4542. !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
  4543. (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
  4544. (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  4545. timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
  4546. } else {
  4547. timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
  4548. }
  4549. let messageText = UITextView()
  4550. messageText.isEditable = false
  4551. messageText.isSelectable = true
  4552. messageText.dataDetectorTypes = [.link]
  4553. messageText.backgroundColor = .clear
  4554. messageText.isScrollEnabled = false
  4555. messageText.textContainerInset = UIEdgeInsets.zero
  4556. messageText.contentInset = UIEdgeInsets.zero
  4557. messageText.textDragInteraction?.isEnabled = false
  4558. containerMessage.addSubview(messageText)
  4559. messageText.translatesAutoresizingMaskIntoConstraints = false
  4560. let topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32)
  4561. let dataProfile = getDataProfile(f_pin: dataMessages[indexPath.row]["f_pin"] as? String ?? "", message_id: dataMessages[indexPath.row]["message_id"] as? String ?? "")
  4562. let statusMessage = UIImageView()
  4563. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "0" && dataMessages[indexPath.row]["lock"] as? String != "1") || forwardSession || deleteSession {
  4564. var showSelectedImage = true
  4565. if (!imageChat.isEmpty || !videoChat.isEmpty || !fileChat.isEmpty) && forwardSession {
  4566. if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
  4567. showSelectedImage = false
  4568. } else {
  4569. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  4570. if file.isEmpty {
  4571. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  4572. if file.isEmpty {
  4573. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  4574. if file.isEmpty {
  4575. file = dataMessages[indexPath.row]["audio_id"] as? String ?? ""
  4576. }
  4577. }
  4578. }
  4579. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4580. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4581. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4582. if let dirPath = paths.first {
  4583. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  4584. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  4585. showSelectedImage = false
  4586. }
  4587. }
  4588. }
  4589. }
  4590. if dataMessages[indexPath.row]["f_pin"] as? String ?? "" == "-999" && !deleteSession {
  4591. showSelectedImage = false
  4592. }
  4593. if showSelectedImage {
  4594. let selectedImage = UIImageView()
  4595. cellMessage.contentView.addSubview(selectedImage)
  4596. selectedImage.translatesAutoresizingMaskIntoConstraints = false
  4597. selectedImage.frame.size = CGSize(width: 20, height: 20)
  4598. var leading = selectedImage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: -20)
  4599. selectedImage.isHidden = true
  4600. if copySession || forwardSession || deleteSession {
  4601. leading = selectedImage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15)
  4602. selectedImage.isHidden = false
  4603. }
  4604. NSLayoutConstraint.activate([
  4605. leading,
  4606. selectedImage.centerYAnchor.constraint(equalTo: cellMessage.contentView.centerYAnchor),
  4607. selectedImage.widthAnchor.constraint(equalToConstant: 20),
  4608. selectedImage.heightAnchor.constraint(equalToConstant: 20)
  4609. ])
  4610. selectedImage.circle()
  4611. selectedImage.layer.borderWidth = 2
  4612. selectedImage.layer.borderColor = UIColor.mainColor.cgColor
  4613. if dataMessages[indexPath.row]["isSelected"] as! Bool {
  4614. selectedImage.image = UIImage(systemName: "checkmark.circle.fill")
  4615. }
  4616. selectedImage.tintColor = .mainColor
  4617. }
  4618. }
  4619. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  4620. profileMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  4621. profileMessage.trailingAnchor.constraint(equalTo: cellMessage.contentView.trailingAnchor, constant: -15).isActive = true
  4622. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  4623. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  4624. profileMessage.circle()
  4625. profileMessage.clipsToBounds = true
  4626. profileMessage.backgroundColor = .lightGray
  4627. profileMessage.image = UIImage(systemName: "person")
  4628. profileMessage.tintColor = .white
  4629. profileMessage.contentMode = .scaleAspectFit
  4630. let pictureImage = dataProfile["image_id"]
  4631. if (pictureImage != "" && pictureImage != nil) {
  4632. profileMessage.setImage(name: pictureImage!)
  4633. profileMessage.contentMode = .scaleAspectFill
  4634. }
  4635. containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  4636. containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 60).isActive = true
  4637. containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
  4638. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  4639. containerMessage.layer.cornerRadius = 10.0
  4640. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  4641. containerMessage.clipsToBounds = true
  4642. timeMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  4643. if (dataMessages[indexPath.row]["lock"] as? String == "0" || (dataMessages[indexPath.row]["lock"] as? String ?? "").isEmpty) {
  4644. cellMessage.contentView.addSubview(statusMessage)
  4645. statusMessage.translatesAutoresizingMaskIntoConstraints = false
  4646. statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  4647. statusMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  4648. statusMessage.widthAnchor.constraint(equalToConstant: 15).isActive = true
  4649. statusMessage.heightAnchor.constraint(equalToConstant: 15).isActive = true
  4650. let status = getRealStatus(messageId: dataMessages[indexPath.row]["message_id"] as? String ?? "")
  4651. if status == "0" {
  4652. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  4653. } else if status == "1" {
  4654. statusMessage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
  4655. } else if status == "2" {
  4656. statusMessage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  4657. } else if (status == "3") {
  4658. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  4659. } else if (status == "8") {
  4660. statusMessage.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  4661. } else {
  4662. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  4663. }
  4664. }
  4665. let nameSender = UILabel()
  4666. containerMessage.addSubview(nameSender)
  4667. nameSender.translatesAutoresizingMaskIntoConstraints = false
  4668. nameSender.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  4669. nameSender.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  4670. nameSender.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  4671. nameSender.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  4672. nameSender.text = dataProfile["name"]
  4673. nameSender.textAlignment = .right
  4674. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  4675. containerMessage.backgroundColor = .clear
  4676. nameSender.textColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .lightGray : .mainColor
  4677. } else {
  4678. containerMessage.backgroundColor = .blueBubbleColor
  4679. nameSender.textColor = UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? .lightGray : .mainColor
  4680. }
  4681. } else {
  4682. if copySession || forwardSession || deleteSession {
  4683. profileMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 50).isActive = true
  4684. } else {
  4685. profileMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15).isActive = true
  4686. }
  4687. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  4688. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  4689. profileMessage.circle()
  4690. profileMessage.clipsToBounds = true
  4691. profileMessage.backgroundColor = .lightGray
  4692. profileMessage.image = UIImage(systemName: "person")
  4693. profileMessage.tintColor = .white
  4694. profileMessage.contentMode = .scaleAspectFit
  4695. let pictureImage = dataProfile["image_id"]
  4696. if dataMessages[indexPath.row]["f_pin"] as? String == "-999" {
  4697. if !Utils.getIconDock().isEmpty {
  4698. let dataImage = try? Data(contentsOf: URL(string: Utils.getUrlDock()!)!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
  4699. if dataImage != nil {
  4700. profileMessage.image = UIImage(data: dataImage!)
  4701. }
  4702. } else {
  4703. profileMessage.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  4704. }
  4705. profileMessage.contentMode = .scaleAspectFill
  4706. }
  4707. else if (pictureImage != "" && pictureImage != nil) {
  4708. profileMessage.setImage(name: pictureImage!)
  4709. profileMessage.contentMode = .scaleAspectFill
  4710. }
  4711. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  4712. profileMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 35).isActive = true
  4713. containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 35).isActive = true
  4714. let newMessagesView = UIView()
  4715. cellMessage.contentView.addSubview(newMessagesView)
  4716. newMessagesView.translatesAutoresizingMaskIntoConstraints = false
  4717. NSLayoutConstraint.activate([
  4718. newMessagesView.topAnchor.constraint(equalTo: newMessagesView.topAnchor),
  4719. newMessagesView.bottomAnchor.constraint(equalTo: containerMessage.topAnchor),
  4720. newMessagesView.centerXAnchor.constraint(equalTo: cellMessage.contentView.centerXAnchor),
  4721. newMessagesView.heightAnchor.constraint(equalToConstant: 30),
  4722. newMessagesView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  4723. ])
  4724. newMessagesView.backgroundColor = .greenColor
  4725. newMessagesView.layer.cornerRadius = 15.0
  4726. newMessagesView.clipsToBounds = true
  4727. let labelNewMessages = UILabel()
  4728. newMessagesView.addSubview(labelNewMessages)
  4729. labelNewMessages.translatesAutoresizingMaskIntoConstraints = false
  4730. NSLayoutConstraint.activate([
  4731. labelNewMessages.centerYAnchor.constraint(equalTo: newMessagesView.centerYAnchor),
  4732. labelNewMessages.centerXAnchor.constraint(equalTo: newMessagesView.centerXAnchor),
  4733. labelNewMessages.leadingAnchor.constraint(equalTo: newMessagesView.leadingAnchor, constant: 10),
  4734. labelNewMessages.trailingAnchor.constraint(equalTo: newMessagesView.trailingAnchor, constant: -10),
  4735. ])
  4736. labelNewMessages.textAlignment = .center
  4737. labelNewMessages.textColor = .secondaryColor
  4738. labelNewMessages.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  4739. labelNewMessages.text = "Unread Messages".localized()
  4740. } else {
  4741. profileMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  4742. containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
  4743. }
  4744. containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
  4745. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
  4746. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  4747. 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" {
  4748. containerMessage.backgroundColor = .clear
  4749. } else {
  4750. containerMessage.backgroundColor = .whiteBubbleColor
  4751. }
  4752. containerMessage.layer.cornerRadius = 10.0
  4753. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  4754. containerMessage.clipsToBounds = true
  4755. timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  4756. let nameSender = UILabel()
  4757. containerMessage.addSubview(nameSender)
  4758. nameSender.translatesAutoresizingMaskIntoConstraints = false
  4759. nameSender.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  4760. nameSender.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  4761. nameSender.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  4762. nameSender.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  4763. if dataMessages[indexPath.row]["f_pin"] as? String == "-999" {
  4764. nameSender.text = "Bot"
  4765. }
  4766. else {
  4767. nameSender.text = dataProfile["name"]
  4768. }
  4769. nameSender.textAlignment = .left
  4770. nameSender.textColor = .mainColor
  4771. }
  4772. if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
  4773. (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
  4774. !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
  4775. (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
  4776. (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  4777. containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
  4778. } else {
  4779. containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
  4780. }
  4781. let imageStared = UIImageView()
  4782. let imageAckView = UIImageView()
  4783. let imageCredentialView = UIImageView()
  4784. if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" == "0") {
  4785. cellMessage.contentView.addSubview(imageStared)
  4786. imageStared.translatesAutoresizingMaskIntoConstraints = false
  4787. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  4788. imageStared.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
  4789. imageStared.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  4790. } else {
  4791. imageStared.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  4792. imageStared.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  4793. }
  4794. imageStared.widthAnchor.constraint(equalToConstant: 15).isActive = true
  4795. imageStared.heightAnchor.constraint(equalToConstant: 15).isActive = true
  4796. imageStared.image = UIImage(systemName: "star.fill")
  4797. imageStared.backgroundColor = .clear
  4798. imageStared.tintColor = .systemYellow
  4799. }
  4800. if dataMessages[indexPath.row]["read_receipts"] as? String == "8" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  4801. var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  4802. cellMessage.contentView.addSubview(imageAckView)
  4803. imageAckView.translatesAutoresizingMaskIntoConstraints = false
  4804. imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  4805. imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  4806. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  4807. let status = getRealStatus(messageId: dataMessages[indexPath.row]["message_id"] as? String ?? "")
  4808. if status == "8" {
  4809. imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  4810. }
  4811. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  4812. imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  4813. } else {
  4814. let status = dataMessages[indexPath.row]["status"] as? String
  4815. if status == "8" {
  4816. imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  4817. }
  4818. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  4819. imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  4820. let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
  4821. tap.indexPath = indexPath
  4822. imageAckView.addGestureRecognizer(tap)
  4823. imageAckView.isUserInteractionEnabled = true
  4824. }
  4825. imageAckView.image = imageAck
  4826. }
  4827. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  4828. let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  4829. imageCredentialView.image = imageCredential
  4830. cellMessage.contentView.addSubview(imageCredentialView)
  4831. imageCredentialView.translatesAutoresizingMaskIntoConstraints = false
  4832. imageCredentialView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  4833. imageCredentialView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  4834. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  4835. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  4836. imageCredentialView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  4837. } else {
  4838. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  4839. imageCredentialView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  4840. }
  4841. }
  4842. if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  4843. let imageSpecFileView = UIImageView()
  4844. let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  4845. imageSpecFileView.image = imageSpecFile
  4846. cellMessage.contentView.addSubview(imageSpecFileView)
  4847. imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
  4848. imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  4849. imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  4850. imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  4851. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  4852. if imageAckView.isDescendant(of: cellMessage.contentView) {
  4853. imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
  4854. } else if imageCredentialView.isDescendant(of: cellMessage.contentView) {
  4855. imageSpecFileView.leadingAnchor.constraint(equalTo: imageCredentialView.trailingAnchor, constant: 5).isActive = true
  4856. } else {
  4857. imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  4858. }
  4859. } else {
  4860. if imageAckView.isDescendant(of: cellMessage.contentView) {
  4861. imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
  4862. } else if imageCredentialView.isDescendant(of: cellMessage.contentView) {
  4863. imageSpecFileView.trailingAnchor.constraint(equalTo: imageCredentialView.leadingAnchor, constant: -5).isActive = true
  4864. } else {
  4865. imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  4866. }
  4867. }
  4868. }
  4869. if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "26" {
  4870. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
  4871. let imageLS = UIImageView()
  4872. containerMessage.addSubview(imageLS)
  4873. imageLS.translatesAutoresizingMaskIntoConstraints = false
  4874. NSLayoutConstraint.activate([
  4875. imageLS.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15.0),
  4876. imageLS.trailingAnchor.constraint(equalTo: messageText.leadingAnchor, constant: -10.0),
  4877. imageLS.centerYAnchor.constraint(equalTo: containerMessage.centerYAnchor),
  4878. imageLS.heightAnchor.constraint(equalToConstant: 60.0)
  4879. ])
  4880. if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "26" {
  4881. imageLS.image = UIImage(named: "pb_seminar_wpr", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  4882. } else if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "27" {
  4883. imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  4884. }
  4885. } else if !audioChat.isEmpty {
  4886. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 60).isActive = true
  4887. } else {
  4888. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  4889. }
  4890. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -15).isActive = true
  4891. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  4892. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  4893. messageText.font = .systemFont(ofSize: 12 + offset())
  4894. var textChat = dataMessages[indexPath.row]["message_text"] as? String ?? ""
  4895. let originalMessageText = textChat
  4896. if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
  4897. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  4898. textChat = "🚫 _"+"You were deleted this message".localized()+"_"
  4899. } else {
  4900. textChat = "🚫 _"+"This message was deleted".localized()+"_"
  4901. }
  4902. }
  4903. if dataMessages[indexPath.row]["lock"] as? String == "2" {
  4904. textChat = "🚫 _"+"Message has expired".localized()+"_"
  4905. }
  4906. if !audioChat.isEmpty {
  4907. textChat = textChat.components(separatedBy: "|")[0]
  4908. }
  4909. let imageSticker = UIImageView()
  4910. if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  4911. if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
  4912. if let json = try! JSONSerialization.jsonObject(with: textChat.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  4913. Database.shared.database?.inTransaction({ fmdb, rollback in
  4914. let title = json["title"] as? String ?? ""
  4915. let description = json["description"] as? String ?? ""
  4916. let start = json["time"] as! Int64
  4917. let by = json["by"] as? String ?? ""
  4918. let textLS = "Live Streaming".localized()
  4919. var type = "*\(textLS)*"
  4920. if attachmentFlag == "26" {
  4921. let textSeminar = "Seminar".localized()
  4922. type = "*\(textSeminar)*"
  4923. }
  4924. if let c = Database().getRecords(fmdb: fmdb, query: "select first_name || ' ' || last_name from BUDDY where f_pin = '\(by)'"), c.next() {
  4925. let name = c.string(forColumnIndex: 0)!
  4926. messageText.attributedText = "\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: \(name)".richText()
  4927. c.close()
  4928. } else {
  4929. messageText.attributedText = ("\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: " + "Unknown".localized()).richText()
  4930. }
  4931. })
  4932. }
  4933. }
  4934. else if attachmentFlag == "11" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  4935. messageText.text = ""
  4936. topMarginText.constant = topMarginText.constant + 100
  4937. containerMessage.addSubview(imageSticker)
  4938. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  4939. let data = queryMessageReply(message_id: reffChat)
  4940. if reffChat.isEmpty || data.count == 0 {
  4941. imageSticker.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32).isActive = true
  4942. imageSticker.widthAnchor.constraint(equalToConstant: 80).isActive = true
  4943. } else {
  4944. imageSticker.widthAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true
  4945. }
  4946. imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  4947. imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  4948. imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  4949. var imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  4950. if imageStickerBundle == nil {
  4951. imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  4952. }
  4953. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  4954. imageSticker.contentMode = .scaleAspectFit
  4955. }
  4956. else {
  4957. messageText.attributedText = textChat.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  4958. modifyText()
  4959. }
  4960. } else {
  4961. messageText.attributedText = textChat.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  4962. modifyText()
  4963. }
  4964. func modifyText() {
  4965. if !textChat.isEmpty {
  4966. if textChat.contains("■"){
  4967. textChat = textChat.components(separatedBy: "■")[0]
  4968. textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
  4969. }
  4970. if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  4971. textChat = textChat.components(separatedBy: "|")[1]
  4972. }
  4973. let finalAtribute = textChat.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  4974. textChat = finalAtribute.string
  4975. let urlPattern = "(https?://|www\\.)\\S+"
  4976. if let regex = try? NSRegularExpression(pattern: urlPattern, options: []) {
  4977. let matches = regex.matches(in: textChat, options: [], range: NSRange(textChat.startIndex..., in: textChat))
  4978. for match in matches {
  4979. if let range = Range(match.range, in: textChat) {
  4980. let linkText = String(textChat[range])
  4981. let nsRange = NSRange(range, in: textChat)
  4982. finalAtribute.addAttribute(.link, value: linkText, range: nsRange)
  4983. finalAtribute.addAttribute(.foregroundColor, value: UIColor.blue, range: nsRange)
  4984. finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
  4985. }
  4986. }
  4987. }
  4988. messageText.attributedText = finalAtribute
  4989. messageText.delegate = self
  4990. }
  4991. }
  4992. if !copySession && !forwardSession && !deleteSession && !isHistoryCC && !removed {
  4993. let interaction = UIContextMenuInteraction(delegate: self)
  4994. containerMessage.addInteraction(interaction)
  4995. containerMessage.isUserInteractionEnabled = true
  4996. }
  4997. if isSearching && textSearch.count > 1 {
  4998. messageText.attributedText = textChat.richText(isSearching: true, textSearch: textSearch, group_id: self.dataGroup["group_id"] as? String ?? "")
  4999. }
  5000. let stringDate = (dataMessages[indexPath.row]["server_date"] as? String ?? "")
  5001. if !stringDate.isEmpty {
  5002. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" && dataMessages[indexPath.row]["lock"] as? String != "1" {
  5003. if dataTimer! >= 10 {
  5004. timeMessage.text = "00:\(dataTimer!)"
  5005. } else {
  5006. timeMessage.text = "00:0\(dataTimer!)"
  5007. }
  5008. timeMessage.textColor = .systemRed
  5009. } else {
  5010. let date = Date(milliseconds: Int64(stringDate) ?? 100)
  5011. let formatter = DateFormatter()
  5012. formatter.dateFormat = "HH:mm"
  5013. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  5014. timeMessage.text = formatter.string(from: date as Date)
  5015. timeMessage.textColor = .lightGray
  5016. }
  5017. timeMessage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  5018. if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
  5019. timeMessage.text = (timeMessage.text ?? "") + "\n" + "Edited".localized()
  5020. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5021. timeMessage.textAlignment = .right
  5022. }
  5023. }
  5024. }
  5025. let imageThumb = UIImageView()
  5026. let containerViewFile = UIView()
  5027. let imageGif = SDAnimatedImageView()
  5028. if !audioChat.isEmpty {
  5029. messageText.isHidden = true
  5030. let imageAudio = UIImageView()
  5031. imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
  5032. containerMessage.addSubview(imageAudio)
  5033. imageAudio.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 15, centerY: containerMessage.centerYAnchor)
  5034. imageAudio.tintColor = .mainColor
  5035. let playButtonAudio = UIButton(type: .system)
  5036. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  5037. playButtonAudio.tintColor = .gray
  5038. containerMessage.addSubview(playButtonAudio)
  5039. playButtonAudio.anchor(left: containerMessage.leftAnchor, paddingLeft: 60, centerY: containerMessage.centerYAnchor, width: 20, height: 20)
  5040. let progressSliderAudio = UISlider()
  5041. progressSliderAudio.minimumValue = 0
  5042. progressSliderAudio.maximumValue = 1
  5043. let thumbImage = UIImage(systemName: "circle.fill")?.withTintColor(UIColor.mainColor)
  5044. .resize(target: CGSize(width: 15, height: 15))
  5045. progressSliderAudio.setThumbImage(thumbImage, for: .normal)
  5046. containerMessage.addSubview(progressSliderAudio)
  5047. progressSliderAudio.anchor(left: playButtonAudio.rightAnchor, right: containerMessage.rightAnchor, paddingLeft: 10, paddingRight: 15, centerY: containerMessage.centerYAnchor, height: 15)
  5048. let timeLabelAudio = UILabel()
  5049. timeLabelAudio.text = "0:00"
  5050. timeLabelAudio.font = .systemFont(ofSize: 10 + offset())
  5051. timeLabelAudio.textColor = .gray
  5052. containerMessage.addSubview(timeLabelAudio)
  5053. timeLabelAudio.anchor(top: playButtonAudio.bottomAnchor, left: playButtonAudio.rightAnchor, paddingLeft: 10, width: 100, height: 12)
  5054. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5055. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5056. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5057. if let dirPath = paths.first {
  5058. let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(audioChat)
  5059. var url = audioURL
  5060. if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: audioChat) {
  5061. let activityIndicator = UIActivityIndicatorView(style: .medium)
  5062. activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  5063. activityIndicator.startAnimating()
  5064. playButtonAudio.setImage(nil, for: .normal)
  5065. playButtonAudio.addSubview(activityIndicator)
  5066. NSLayoutConstraint.activate([
  5067. activityIndicator.centerXAnchor.constraint(equalTo: playButtonAudio.centerXAnchor),
  5068. activityIndicator.centerYAnchor.constraint(equalTo: playButtonAudio.centerYAnchor)
  5069. ])
  5070. Download().startHTTP(forKey: audioChat) { (name, progress) in
  5071. guard progress == 100 else {
  5072. return
  5073. }
  5074. tableView.reloadRows(at: [indexPath], with: .none)
  5075. }
  5076. } else {
  5077. if !FileManager.default.fileExists(atPath: audioURL.path) {
  5078. do {
  5079. if var audioData = try FileEncryption.shared.readSecure(filename: audioChat) {
  5080. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: audioData)
  5081. if dataDecrypt != nil {
  5082. audioData = dataDecrypt!
  5083. }
  5084. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  5085. let tempPath = cachesDirectory.appendingPathComponent(audioChat)
  5086. try audioData.write(to: tempPath)
  5087. url = tempPath
  5088. }
  5089. } catch {
  5090. }
  5091. }
  5092. if audioPlayers[indexPath] == nil {
  5093. do {
  5094. let audioPlayer = try AVAudioPlayer(contentsOf: url)
  5095. audioPlayers[indexPath] = audioPlayer
  5096. audioPlayer.delegate = self
  5097. progressSliderAudio.maximumValue = Float(audioPlayer.duration)
  5098. timeLabelAudio.text = formatTime(audioPlayer.duration)
  5099. } catch {
  5100. print("Error loading audio: \(error)")
  5101. }
  5102. }
  5103. let audioPlayer = audioPlayers[indexPath]
  5104. if playingIndexPath == indexPath, let player = audioPlayer, player.isPlaying {
  5105. playButtonAudio.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  5106. } else {
  5107. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  5108. }
  5109. // Play/Pause Button Action
  5110. playButtonAudio.addAction(UIAction { _ in
  5111. self.playPauseAudio(indexPath: indexPath, playButton: playButtonAudio, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  5112. }, for: .touchUpInside)
  5113. progressSliderAudio.addAction(UIAction { _ in
  5114. self.sliderChanged(indexPath: indexPath, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  5115. }, for: .valueChanged)
  5116. }
  5117. }
  5118. }
  5119. if (!thumbChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5120. if let listImages = groupImages[messageIdChat] {
  5121. timeMessage.isHidden = true
  5122. statusMessage.isHidden = true
  5123. imageStared.isHidden = true
  5124. topMarginText.constant = topMarginText.constant + 220
  5125. var constTop = 35.0
  5126. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
  5127. topMarginText.constant = topMarginText.constant + 20
  5128. constTop = 55.0
  5129. }
  5130. let listImageThumb: [UIImageView] = [UIImageView(), UIImageView(), UIImageView(), UIImageView()]
  5131. for i in 0..<4 {
  5132. containerMessage.addSubview(listImageThumb[i])
  5133. listImageThumb[i].layer.cornerRadius = 5.0
  5134. listImageThumb[i].clipsToBounds = true
  5135. listImageThumb[i].contentMode = .scaleAspectFill
  5136. let widthHeightImage: CGFloat = 120
  5137. switch i {
  5138. case 0:
  5139. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: constTop, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
  5140. case 1:
  5141. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: constTop, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  5142. case 2:
  5143. listImageThumb[i].anchor(left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingLeft: 5, paddingBottom: 5, width: widthHeightImage, height: widthHeightImage)
  5144. default:
  5145. listImageThumb[i].anchor(left: listImageThumb[2].rightAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  5146. }
  5147. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5148. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5149. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5150. if let dirPath = paths.first {
  5151. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].thumbId)
  5152. if FileManager.default.fileExists(atPath: thumbURL.path) {
  5153. DispatchQueue.main.async {
  5154. let image : UIImage? = {
  5155. if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
  5156. return img
  5157. }
  5158. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  5159. Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
  5160. return img
  5161. }
  5162. return nil
  5163. }()
  5164. listImageThumb[i].image = image
  5165. }
  5166. } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
  5167. do {
  5168. if var data = try FileEncryption.shared.readSecure(filename: listImages[i].thumbId) {
  5169. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  5170. if dataDecrypt != nil {
  5171. data = dataDecrypt!
  5172. }
  5173. DispatchQueue.main.async {
  5174. let image : UIImage? = {
  5175. if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
  5176. return img
  5177. }
  5178. else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
  5179. Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
  5180. return img
  5181. }
  5182. return nil
  5183. }()
  5184. listImageThumb[i].image = image
  5185. }
  5186. }
  5187. } catch {
  5188. }
  5189. } else {
  5190. Download().startHTTP(forKey: listImages[i].thumbId) { (name, progress) in
  5191. guard progress == 100 else {
  5192. return
  5193. }
  5194. tableView.reloadRows(at: [indexPath], with: .none)
  5195. }
  5196. }
  5197. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
  5198. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  5199. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  5200. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  5201. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[i].frame.size.width, height: listImageThumb[i].frame.size.height)
  5202. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  5203. listImageThumb[i].addSubview(blurEffectView)
  5204. }
  5205. }
  5206. let containerTimeStatus = UIView()
  5207. listImageThumb[i].addSubview(containerTimeStatus)
  5208. containerTimeStatus.anchor(bottom: listImageThumb[i].bottomAnchor, right: listImageThumb[i].rightAnchor, height: 15)
  5209. let widthcontainerTimeStatus = containerTimeStatus.widthAnchor.constraint(equalToConstant: 50)
  5210. widthcontainerTimeStatus.isActive = true
  5211. containerTimeStatus.layer.cornerRadius = 5.0
  5212. containerTimeStatus.layer.masksToBounds = true
  5213. containerTimeStatus.backgroundColor = .black.withAlphaComponent(0.15)
  5214. let timeInImage = UILabel()
  5215. containerTimeStatus.addSubview(timeInImage)
  5216. let date = Date(milliseconds: Int64(listImages[i].time) ?? 100)
  5217. let formatter = DateFormatter()
  5218. formatter.dateFormat = "HH:mm"
  5219. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  5220. timeInImage.text = formatter.string(from: date as Date)
  5221. timeInImage.textColor = .white
  5222. timeInImage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  5223. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5224. let statusInImage = UIImageView()
  5225. containerTimeStatus.addSubview(statusInImage)
  5226. statusInImage.anchor(right: containerTimeStatus.rightAnchor, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  5227. if listImages[i].status == "0" {
  5228. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  5229. } else if listImages[i].status == "1" {
  5230. statusMessage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
  5231. } else if listImages[i].status == "2" {
  5232. statusInImage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  5233. } else if listImages[i].status == "3" {
  5234. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  5235. } else {
  5236. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  5237. }
  5238. timeInImage.anchor(right: statusInImage.leftAnchor, centerY: containerTimeStatus.centerYAnchor, height: 15)
  5239. } else {
  5240. timeInImage.anchor(right: containerTimeStatus.rightAnchor, paddingRight: 5, centerY: containerTimeStatus.centerYAnchor, height: 15)
  5241. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant - 10
  5242. }
  5243. if listImages[i].dataMessage["is_stared"] as? String == "1" {
  5244. let iconStar = UIImageView()
  5245. containerTimeStatus.addSubview(iconStar)
  5246. iconStar.anchor(right: timeInImage.leftAnchor, paddingRight: 2, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  5247. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant + 15
  5248. iconStar.image = UIImage(systemName: "star.fill")
  5249. iconStar.tintColor = .white
  5250. }
  5251. if !copySession && !forwardSession && !deleteSession {
  5252. let objectTap = ObjectGesture(target: self, action: #selector(imageGroupingTapped(_:)))
  5253. listImageThumb[i].isUserInteractionEnabled = true
  5254. listImageThumb[i].addGestureRecognizer(objectTap)
  5255. objectTap.indexImageTapped = i
  5256. objectTap.listImageFromGrouping = listImages
  5257. objectTap.isInitiator = dataMessages[indexPath.row]["f_pin"] as? String == idMe
  5258. }
  5259. }
  5260. if listImages.count > 4 {
  5261. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
  5262. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  5263. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[3].frame.size.width, height: listImageThumb[3].frame.size.height)
  5264. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  5265. listImageThumb[3].addSubview(blurEffectView)
  5266. let countRestImages = UILabel()
  5267. listImageThumb[3].addSubview(countRestImages)
  5268. countRestImages.anchor(centerX: listImageThumb[3].centerXAnchor, centerY: listImageThumb[3].centerYAnchor)
  5269. countRestImages.font = UIFont.systemFont(ofSize: 30, weight: .medium)
  5270. countRestImages.text = "+\(listImages.count - 3)"
  5271. countRestImages.textColor = .white
  5272. }
  5273. } else {
  5274. let getHeightImage = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.height
  5275. let getWidthImage = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.width
  5276. topMarginText.constant = topMarginText.constant + (getHeightImage < 40 ? 40 : getHeightImage)
  5277. containerMessage.addSubview(imageThumb)
  5278. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  5279. imageThumb.frame = CGRect(x: 0, y: 0, width: getWidthImage, height: getHeightImage)
  5280. let data = queryMessageReply(message_id: reffChat)
  5281. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  5282. imageThumb.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 37).isActive = true
  5283. }
  5284. imageThumb.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5285. imageThumb.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  5286. imageThumb.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5287. imageThumb.widthAnchor.constraint(equalToConstant: getWidthImage).isActive = true
  5288. imageThumb.layer.cornerRadius = 5.0
  5289. imageThumb.clipsToBounds = true
  5290. imageThumb.contentMode = .scaleAspectFill
  5291. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5292. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5293. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5294. if let dirPath = paths.first {
  5295. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
  5296. if FileManager.default.fileExists(atPath: thumbURL.path) {
  5297. DispatchQueue.main.async {
  5298. let image : UIImage? = {
  5299. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  5300. return img
  5301. }
  5302. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  5303. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  5304. return img
  5305. }
  5306. return nil
  5307. }()
  5308. imageThumb.image = image
  5309. }
  5310. } else if FileEncryption.shared.isSecureExists(filename: thumbChat) {
  5311. do {
  5312. if var data = try FileEncryption.shared.readSecure(filename: thumbChat) {
  5313. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  5314. if dataDecrypt != nil {
  5315. data = dataDecrypt!
  5316. }
  5317. DispatchQueue.main.async {
  5318. let image : UIImage? = {
  5319. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  5320. return img
  5321. }
  5322. else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
  5323. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  5324. return img
  5325. }
  5326. return nil
  5327. }()
  5328. imageThumb.image = image
  5329. }
  5330. }
  5331. } catch {
  5332. }
  5333. } else {
  5334. Download().startHTTP(forKey: thumbChat) { (name, progress) in
  5335. guard progress == 100 else {
  5336. return
  5337. }
  5338. tableView.reloadRows(at: [indexPath], with: .none)
  5339. }
  5340. }
  5341. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
  5342. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  5343. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  5344. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  5345. blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
  5346. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  5347. imageThumb.addSubview(blurEffectView)
  5348. if !imageChat.isEmpty {
  5349. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
  5350. imageThumb.addSubview(blurEffectView)
  5351. imageThumb.addSubview(imageDownload)
  5352. imageDownload.tintColor = .black.withAlphaComponent(0.3)
  5353. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  5354. imageDownload.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  5355. imageDownload.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  5356. }
  5357. }
  5358. }
  5359. if (videoChat != "" && gifChat.isEmpty) {
  5360. 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))
  5361. imagePlay.circle()
  5362. imageThumb.addSubview(imagePlay)
  5363. imagePlay.backgroundColor = .black.withAlphaComponent(0.3)
  5364. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  5365. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  5366. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  5367. } else if !gifChat.isEmpty {
  5368. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5369. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5370. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5371. if let dirPath = paths.first {
  5372. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifChat)
  5373. if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifChat) {
  5374. Download().startHTTP(forKey: gifChat) { (name, progress) in
  5375. guard progress == 100 else {
  5376. return
  5377. }
  5378. tableView.reloadRows(at: [indexPath], with: .none)
  5379. }
  5380. } else {
  5381. imageThumb.addSubview(imageGif)
  5382. imageGif.translatesAutoresizingMaskIntoConstraints = false
  5383. imageGif.anchor(top: imageThumb.topAnchor, left: imageThumb.leftAnchor, bottom: imageThumb.bottomAnchor, right: imageThumb.rightAnchor)
  5384. if FileManager.default.fileExists(atPath: gifURL.path) {
  5385. imageGif.image = SDAnimatedImage(contentsOfFile: gifURL.path)
  5386. // imageGif.shouldCustomLoopCount = true
  5387. // imageGif.animationRepeatCount = 4
  5388. } else if FileEncryption.shared.isSecureExists(filename: gifChat){
  5389. do {
  5390. if var data = try FileEncryption.shared.readSecure(filename: gifChat) {
  5391. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  5392. if dataDecrypt != nil {
  5393. data = dataDecrypt!
  5394. }
  5395. if let imageData = SDAnimatedImage(data: data) {
  5396. imageGif.image = imageData
  5397. // imageGif.shouldCustomLoopCount = true
  5398. // imageGif.animationRepeatCount = 4
  5399. }
  5400. }
  5401. }
  5402. catch {
  5403. print("Error reading secure file")
  5404. }
  5405. }
  5406. }
  5407. }
  5408. }
  5409. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0 && dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5410. let container = UIView()
  5411. imageThumb.addSubview(container)
  5412. container.translatesAutoresizingMaskIntoConstraints = false
  5413. container.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  5414. container.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  5415. container.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5416. container.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5417. container.backgroundColor = .white.withAlphaComponent(0.1)
  5418. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 10, y: 20), radius: 15, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  5419. let trackShape = CAShapeLayer()
  5420. trackShape.path = circlePath.cgPath
  5421. trackShape.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
  5422. trackShape.lineWidth = 3
  5423. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  5424. container.backgroundColor = .clear
  5425. container.layer.addSublayer(trackShape)
  5426. let shapeLoading = CAShapeLayer()
  5427. shapeLoading.path = circlePath.cgPath
  5428. shapeLoading.fillColor = UIColor.clear.cgColor
  5429. shapeLoading.lineWidth = 3
  5430. shapeLoading.strokeEnd = 0
  5431. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  5432. container.layer.addSublayer(shapeLoading)
  5433. let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  5434. imageupload.tintColor = .white
  5435. container.addSubview(imageupload)
  5436. imageupload.translatesAutoresizingMaskIntoConstraints = false
  5437. imageupload.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  5438. imageupload.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  5439. imageupload.widthAnchor.constraint(equalToConstant: 20).isActive = true
  5440. imageupload.heightAnchor.constraint(equalToConstant: 20).isActive = true
  5441. }
  5442. if !copySession && !forwardSession && !deleteSession {
  5443. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  5444. let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
  5445. imageThumb.isUserInteractionEnabled = true
  5446. imageThumb.addGestureRecognizer(objectTap)
  5447. objectTap.image_id = imageChat
  5448. objectTap.video_id = videoChat
  5449. objectTap.gif_id = gifChat
  5450. objectTap.specFile = sfs
  5451. objectTap.imageView = imageThumb
  5452. objectTap.indexPath = indexPath
  5453. }
  5454. }
  5455. }
  5456. if (!fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5457. topMarginText.constant = topMarginText.constant + 55
  5458. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5459. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5460. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5461. let arrExtFile = (originalMessageText.components(separatedBy: "|")[0]).split(separator: ".")
  5462. let finalExtFile = arrExtFile[arrExtFile.count - 1]
  5463. if let dirPath = paths.first {
  5464. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
  5465. if FileManager.default.fileExists(atPath: fileURL.path) {
  5466. if let dataFile = try? Data(contentsOf: fileURL), textChat.isEmpty {
  5467. var sizeOfFile = Int(dataFile.count / 1000000)
  5468. if (sizeOfFile < 1) {
  5469. sizeOfFile = Int(dataFile.count / 1000)
  5470. if (finalExtFile.count > 4) {
  5471. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  5472. }else {
  5473. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  5474. }
  5475. } else {
  5476. if (finalExtFile.count > 4) {
  5477. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  5478. }else {
  5479. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  5480. }
  5481. }
  5482. }
  5483. }
  5484. else if FileEncryption.shared.isSecureExists(filename: fileChat) {
  5485. do {
  5486. if var dataFile = try FileEncryption.shared.readSecure(filename: fileChat), textChat.isEmpty {
  5487. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: dataFile)
  5488. if dataDecrypt != nil {
  5489. dataFile = dataDecrypt!
  5490. }
  5491. var sizeOfFile = Int(dataFile.count / 1000000)
  5492. if (sizeOfFile < 1) {
  5493. sizeOfFile = Int(dataFile.count / 1000)
  5494. if (finalExtFile.count > 4) {
  5495. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  5496. }else {
  5497. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  5498. }
  5499. } else {
  5500. if (finalExtFile.count > 4) {
  5501. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  5502. }else {
  5503. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  5504. }
  5505. }
  5506. }
  5507. } catch {
  5508. }
  5509. }
  5510. }
  5511. containerMessage.addSubview(containerViewFile)
  5512. containerViewFile.translatesAutoresizingMaskIntoConstraints = false
  5513. let data = queryMessageReply(message_id: reffChat)
  5514. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  5515. containerViewFile.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 37).isActive = true
  5516. }
  5517. containerViewFile.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5518. containerViewFile.bottomAnchor.constraint(equalTo:messageText.topAnchor, constant: -5).isActive = true
  5519. containerViewFile.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5520. // containerViewFile.heightAnchor.constraint(equalToConstant: 50).isActive = true
  5521. containerViewFile.backgroundColor = .black.withAlphaComponent(0.2)
  5522. containerViewFile.layer.cornerRadius = 5.0
  5523. containerViewFile.clipsToBounds = true
  5524. let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .bold, scale: .default)))
  5525. containerViewFile.addSubview(imageFile)
  5526. let nameFile = UILabel()
  5527. containerViewFile.addSubview(nameFile)
  5528. imageFile.translatesAutoresizingMaskIntoConstraints = false
  5529. imageFile.leadingAnchor.constraint(equalTo: containerViewFile.leadingAnchor, constant: 5).isActive = true
  5530. imageFile.trailingAnchor.constraint(equalTo: nameFile.leadingAnchor, constant: -5).isActive = true
  5531. imageFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  5532. imageFile.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5533. imageFile.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5534. imageFile.tintColor = .docColor
  5535. nameFile.translatesAutoresizingMaskIntoConstraints = false
  5536. nameFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  5537. nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
  5538. nameFile.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  5539. nameFile.textColor = .white
  5540. nameFile.text = originalMessageText.components(separatedBy: "|")[0]
  5541. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
  5542. let containerLoading = UIView()
  5543. containerViewFile.addSubview(containerLoading)
  5544. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  5545. containerLoading.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  5546. containerLoading.leadingAnchor.constraint(equalTo: nameFile.trailingAnchor, constant: 5).isActive = true
  5547. containerLoading.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  5548. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5549. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5550. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  5551. let trackShape = CAShapeLayer()
  5552. trackShape.path = circlePath.cgPath
  5553. trackShape.fillColor = UIColor.clear.cgColor
  5554. trackShape.lineWidth = 5
  5555. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  5556. containerLoading.layer.addSublayer(trackShape)
  5557. let shapeLoading = CAShapeLayer()
  5558. shapeLoading.path = circlePath.cgPath
  5559. shapeLoading.fillColor = UIColor.clear.cgColor
  5560. shapeLoading.lineWidth = 3
  5561. shapeLoading.strokeEnd = 0
  5562. shapeLoading.strokeColor = UIColor.secondaryColor.cgColor
  5563. containerLoading.layer.addSublayer(shapeLoading)
  5564. var imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  5565. if dataMessages[indexPath.row]["f_pin"] as? String != idMe {
  5566. imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  5567. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  5568. }
  5569. imageupload.tintColor = .white
  5570. containerLoading.addSubview(imageupload)
  5571. imageupload.translatesAutoresizingMaskIntoConstraints = false
  5572. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  5573. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  5574. } else {
  5575. nameFile.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  5576. }
  5577. if !copySession && !forwardSession && !deleteSession {
  5578. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  5579. let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
  5580. containerViewFile.addGestureRecognizer(objectTap)
  5581. objectTap.containerFile = containerViewFile
  5582. objectTap.labelFile = nameFile
  5583. objectTap.file_id = fileChat
  5584. objectTap.specFile = sfs
  5585. objectTap.indexPath = indexPath
  5586. }
  5587. }
  5588. let containerLinkMessage = UIView()
  5589. var isLoadingShowLink = false
  5590. if thumbChat.isEmpty && fileChat.isEmpty && !textChat.isEmpty {
  5591. var text = ""
  5592. let listTextSplitBreak = textChat.components(separatedBy: "\n")
  5593. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  5594. if indexFirstLinkSplitBreak != nil {
  5595. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  5596. 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) })
  5597. if indexFirstLinkSplitSpace != nil {
  5598. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  5599. }
  5600. }
  5601. if !text.isEmpty {
  5602. isLoadingShowLink = true
  5603. func showLink() {
  5604. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5605. let title = data["title"] as? String
  5606. let description = data["description"] as? String
  5607. let imageUrl = data["imageUrl"] as? String
  5608. let link = data["link"] as? String
  5609. topMarginText.constant = topMarginText.constant + 85
  5610. containerMessage.addSubview(containerLinkMessage)
  5611. containerLinkMessage.translatesAutoresizingMaskIntoConstraints = false
  5612. containerLinkMessage.leadingAnchor.constraint(equalTo:containerMessage.leadingAnchor, constant: 15).isActive = true
  5613. if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" {
  5614. containerLinkMessage.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  5615. } else {
  5616. containerLinkMessage.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  5617. }
  5618. containerLinkMessage.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5619. containerLinkMessage.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  5620. containerLinkMessage.backgroundColor = .gray.withAlphaComponent(0.2)
  5621. let imagePreview = UIImageView()
  5622. if imageUrl != nil {
  5623. containerLinkMessage.addSubview(imagePreview)
  5624. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  5625. imagePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor).isActive = true
  5626. imagePreview.bottomAnchor.constraint(equalTo: containerLinkMessage.bottomAnchor).isActive = true
  5627. imagePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor).isActive = true
  5628. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  5629. imagePreview.loadImageAsync(with: imageUrl)
  5630. imagePreview.contentMode = .scaleToFill
  5631. }
  5632. let titlePreview = UILabel()
  5633. containerLinkMessage.addSubview(titlePreview)
  5634. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  5635. if imageUrl != nil {
  5636. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  5637. } else {
  5638. titlePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  5639. }
  5640. titlePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: 10.0).isActive = true
  5641. titlePreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  5642. titlePreview.text = title
  5643. titlePreview.font = UIFont.systemFont(ofSize: 14.0 + offset(), weight: .bold)
  5644. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5645. let descPreview = UILabel()
  5646. containerLinkMessage.addSubview(descPreview)
  5647. descPreview.translatesAutoresizingMaskIntoConstraints = false
  5648. if imageUrl != nil {
  5649. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  5650. } else {
  5651. descPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  5652. }
  5653. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  5654. descPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  5655. descPreview.text = description
  5656. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  5657. descPreview.textColor = .gray
  5658. descPreview.numberOfLines = 1
  5659. let linkPreview = UILabel()
  5660. containerLinkMessage.addSubview(linkPreview)
  5661. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  5662. if imageUrl != nil {
  5663. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  5664. } else {
  5665. linkPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  5666. }
  5667. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  5668. linkPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  5669. linkPreview.text = link
  5670. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  5671. linkPreview.textColor = .gray
  5672. linkPreview.numberOfLines = 1
  5673. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
  5674. showForwardedSign()
  5675. }
  5676. if !copySession && !forwardSession && !deleteSession {
  5677. let objectTap = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
  5678. objectTap.message_id = text
  5679. containerLinkMessage.addGestureRecognizer(objectTap)
  5680. }
  5681. }
  5682. }
  5683. var dataURL = ""
  5684. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  5685. do {
  5686. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
  5687. if let data = cursor.string(forColumnIndex: 0) {
  5688. dataURL = data
  5689. }
  5690. cursor.close()
  5691. }
  5692. } catch {
  5693. rollback.pointee = true
  5694. print("Access database error: \(error.localizedDescription)")
  5695. }
  5696. })
  5697. if dataURL.isEmpty {
  5698. let urlConfig = URLSessionConfiguration.default
  5699. let sessionDelegate = SelfSignedURLSessionDelegate()
  5700. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  5701. let slp = SwiftLinkPreview(session: session,
  5702. workQueue: SwiftLinkPreview.defaultWorkQueue,
  5703. responseQueue: DispatchQueue.main,
  5704. cache: DisabledCache.instance)
  5705. let preview = slp.preview(text,
  5706. onSuccess: { result in
  5707. let title = result.title ?? "No Title"
  5708. let description = text.contains("google.com") ? "" : result.description
  5709. let imageUrl = result.icon
  5710. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  5711. do {
  5712. var dataJson: [String: Any] = [:]
  5713. dataJson["title"] = title
  5714. dataJson["description"] = description
  5715. dataJson["imageUrl"] = imageUrl
  5716. dataJson["link"] = text
  5717. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  5718. return
  5719. }
  5720. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  5721. "id" : "\(Date().currentTimeMillis().toHex())",
  5722. "link" : text,
  5723. "data_link" : json,
  5724. "retry": 0
  5725. ], replace: true)
  5726. dataURL = json
  5727. showLink()
  5728. DispatchQueue.main.async {
  5729. tableView.reloadRows(at: [indexPath], with: .none)
  5730. }
  5731. } catch {
  5732. rollback.pointee = true
  5733. print("Access database error: \(error.localizedDescription)")
  5734. }
  5735. })
  5736. }, onError: { error in
  5737. })
  5738. } else {
  5739. showLink()
  5740. }
  5741. }
  5742. }
  5743. if (!reffChat.isEmpty) {
  5744. let data = queryMessageReply(message_id: reffChat)
  5745. if data.count != 0 {
  5746. let containerReply = UIView()
  5747. containerMessage.addSubview(containerReply)
  5748. containerReply.translatesAutoresizingMaskIntoConstraints = false
  5749. containerReply.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5750. containerReply.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32).isActive = true
  5751. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  5752. containerReply.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  5753. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  5754. containerReply.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  5755. } else if containerMessage.subviews.contains(containerLinkMessage) {
  5756. containerReply.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  5757. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  5758. containerReply.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  5759. } else {
  5760. containerReply.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  5761. }
  5762. containerReply.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5763. let minHeightConstraint = containerReply.heightAnchor.constraint(greaterThanOrEqualToConstant: 50 + (self.offset()*3))
  5764. minHeightConstraint.priority = .defaultHigh
  5765. minHeightConstraint.isActive = true
  5766. containerReply.backgroundColor = .black.withAlphaComponent(0.2)
  5767. containerReply.layer.cornerRadius = 5
  5768. containerReply.clipsToBounds = true
  5769. let leftReply = UIView()
  5770. containerReply.addSubview(leftReply)
  5771. leftReply.translatesAutoresizingMaskIntoConstraints = false
  5772. leftReply.leadingAnchor.constraint(equalTo: containerReply.leadingAnchor).isActive = true
  5773. leftReply.topAnchor.constraint(equalTo: containerReply.topAnchor).isActive = true
  5774. leftReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor).isActive = true
  5775. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  5776. leftReply.layer.cornerRadius = 5
  5777. leftReply.clipsToBounds = true
  5778. leftReply.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
  5779. let titleReply = UILabel()
  5780. containerReply.addSubview(titleReply)
  5781. titleReply.translatesAutoresizingMaskIntoConstraints = false
  5782. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  5783. titleReply.topAnchor.constraint(equalTo: containerReply.topAnchor, constant: 10).isActive = true
  5784. titleReply.trailingAnchor.constraint(lessThanOrEqualTo: containerReply.trailingAnchor, constant: -20).isActive = true
  5785. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  5786. if (data["f_pin"] as? String == idMe) {
  5787. titleReply.text = "You".localized()
  5788. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  5789. titleReply.textColor = .white
  5790. leftReply.backgroundColor = .white
  5791. } else {
  5792. titleReply.textColor = .mainColor
  5793. leftReply.backgroundColor = .mainColor
  5794. }
  5795. } else {
  5796. if data["f_pin"] as? String != "-999" {
  5797. let dataProfile = getDataProfile(f_pin: data["f_pin"] as? String ?? "", message_id: data["message_id"] as? String ?? "")
  5798. titleReply.text = dataProfile["name"]
  5799. } else {
  5800. titleReply.text = "Bot"
  5801. }
  5802. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  5803. titleReply.textColor = .white
  5804. leftReply.backgroundColor = .white
  5805. } else {
  5806. titleReply.textColor = .mainColor
  5807. leftReply.backgroundColor = .mainColor
  5808. }
  5809. }
  5810. let contentReply = UILabel()
  5811. contentReply.numberOfLines = 2
  5812. containerReply.addSubview(contentReply)
  5813. contentReply.translatesAutoresizingMaskIntoConstraints = false
  5814. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  5815. contentReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor, constant: -10).isActive = true
  5816. let topConstraintContent = contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor)
  5817. topConstraintContent.priority = .defaultHigh
  5818. topConstraintContent.isActive = true
  5819. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  5820. let message_text = data["message_text"] as? String ?? ""
  5821. let attachment_flag = data["attachment_flag"] as? String ?? ""
  5822. let thumb_chat = data["thumb_id"] as? String ?? ""
  5823. let image_chat = data["image_id"] as? String ?? ""
  5824. let video_chat = data["video_id"] as? String ?? ""
  5825. let file_chat = data["file_id"] as? String ?? ""
  5826. if (attachment_flag == "0" && thumb_chat == "") {
  5827. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  5828. contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  5829. } else if (attachment_flag == "1" || image_chat != "") {
  5830. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  5831. contentReply.text = "📷 Photo".localized()
  5832. } else {
  5833. contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  5834. }
  5835. } else if (attachment_flag == "2" || video_chat != "") {
  5836. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  5837. contentReply.text = "📹 Video".localized()
  5838. } else {
  5839. contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  5840. }
  5841. } else if (attachment_flag == "6" || file_chat != ""){
  5842. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  5843. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  5844. } else if (attachment_flag == "11") {
  5845. contentReply.text = "❤️ Sticker"
  5846. } else if attachment_flag == "27" {
  5847. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  5848. contentReply.text = "📄 " + "Live Streaming".localized()
  5849. } else if attachment_flag == "26" {
  5850. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  5851. contentReply.text = "📄 " + "Seminar".localized()
  5852. }
  5853. contentReply.textColor = .white.withAlphaComponent(0.8)
  5854. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  5855. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5856. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5857. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5858. if let dirPath = paths.first {
  5859. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  5860. let image : UIImage? = {
  5861. if let img = Nexilis.imageCache.object(forKey: thumb_chat as NSString) {
  5862. return img
  5863. }
  5864. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  5865. Nexilis.imageCache.setObject(img, forKey: thumb_chat as NSString)
  5866. return img
  5867. }
  5868. return nil
  5869. }()
  5870. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  5871. let imageThumb = UIImageView(image: image)
  5872. containerReply.addSubview(imageThumb)
  5873. imageThumb.layer.cornerRadius = 2.0
  5874. imageThumb.clipsToBounds = true
  5875. imageThumb.contentMode = .scaleAspectFill
  5876. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  5877. imageThumb.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  5878. imageThumb.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  5879. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5880. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5881. if (attachment_flag == "2") {
  5882. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  5883. imageThumb.addSubview(imagePlay)
  5884. imagePlay.clipsToBounds = true
  5885. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  5886. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  5887. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  5888. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  5889. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  5890. imagePlay.tintColor = .white
  5891. }
  5892. titleReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  5893. contentReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  5894. }
  5895. }
  5896. if (attachment_flag == "11") {
  5897. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  5898. containerReply.addSubview(imageSticker)
  5899. imageSticker.layer.cornerRadius = 2.0
  5900. imageSticker.clipsToBounds = true
  5901. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  5902. imageSticker.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  5903. imageSticker.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  5904. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5905. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5906. titleReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  5907. contentReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  5908. }
  5909. if !copySession && !forwardSession && !deleteSession {
  5910. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  5911. containerReply.addGestureRecognizer(objectTap)
  5912. objectTap.indexPath = indexPath
  5913. objectTap.message_id = data["message_id"] as? String ?? ""
  5914. }
  5915. }
  5916. }
  5917. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 && !isLoadingShowLink {
  5918. showForwardedSign()
  5919. }
  5920. func showForwardedSign() {
  5921. topMarginText.constant = topMarginText.constant + 20
  5922. let containerForwarded = UIView()
  5923. containerMessage.addSubview(containerForwarded)
  5924. containerForwarded.translatesAutoresizingMaskIntoConstraints = false
  5925. containerForwarded.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5926. containerForwarded.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 32).isActive = true
  5927. containerForwarded.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5928. containerForwarded.heightAnchor.constraint(equalToConstant: 20).isActive = true
  5929. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  5930. if groupImages[messageIdChat] == nil {
  5931. containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  5932. }
  5933. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  5934. containerForwarded.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  5935. } else if containerMessage.subviews.contains(containerLinkMessage) {
  5936. containerForwarded.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  5937. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  5938. containerForwarded.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  5939. }
  5940. let imageForwarded = UIImageView()
  5941. containerForwarded.addSubview(imageForwarded)
  5942. imageForwarded.anchor(top: containerForwarded.topAnchor, left: containerForwarded.leftAnchor, width: 15, height: 15)
  5943. imageForwarded.image = UIImage(systemName: "arrowshape.turn.up.right.fill")
  5944. imageForwarded.tintColor = .gray
  5945. let titleForwarded = UILabel()
  5946. containerForwarded.addSubview(titleForwarded)
  5947. titleForwarded.anchor(top: containerForwarded.topAnchor, left: imageForwarded.rightAnchor, right: containerForwarded.rightAnchor, height: 15)
  5948. titleForwarded.font = .systemFont(ofSize: 15)
  5949. let textForwarded = "Forwarded".localized()
  5950. titleForwarded.attributedText = " $\(textForwarded)$".richText()
  5951. }
  5952. if messageText.isDescendant(of: containerMessage) {
  5953. var addTopMargin = true
  5954. if !reffChat.isEmpty && dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" != MessageScope.FORM {
  5955. let data = queryMessageReply(message_id: reffChat)
  5956. if data.count != 0 {
  5957. addTopMargin = false
  5958. }
  5959. }
  5960. if addTopMargin{
  5961. topMarginText.isActive = true
  5962. }
  5963. }
  5964. return cellMessage
  5965. }
  5966. func playPauseAudio(indexPath: IndexPath, playButton: UIButton, progressSlider: UISlider, timeLabel: UILabel) {
  5967. guard let audioPlayer = audioPlayers[indexPath] else { return }
  5968. if audioPlayer.isPlaying {
  5969. // Pause Audio
  5970. audioPlayer.pause()
  5971. playButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
  5972. timers[indexPath]?.invalidate()
  5973. } else {
  5974. // Stop other players if one is already playing
  5975. if let currentPlayingIndexPath = playingIndexPath, let currentAudioPlayer = audioPlayers[currentPlayingIndexPath] {
  5976. if currentPlayingIndexPath != indexPath {
  5977. currentAudioPlayer.pause()
  5978. timers[currentPlayingIndexPath]?.invalidate()
  5979. timers[currentPlayingIndexPath] = nil
  5980. audioPlayers[currentPlayingIndexPath] = nil
  5981. tableChatView.reloadRows(at: [currentPlayingIndexPath], with: .none)
  5982. }
  5983. }
  5984. do {
  5985. try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
  5986. try AVAudioSession.sharedInstance().setActive(true)
  5987. } catch {
  5988. }
  5989. // Play new audio
  5990. audioPlayer.play()
  5991. playButton.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  5992. playingIndexPath = indexPath
  5993. // Start timer to update progress
  5994. timers[indexPath] = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
  5995. progressSlider.value = Float(audioPlayer.currentTime)
  5996. timeLabel.text = self.formatTime(audioPlayer.currentTime)
  5997. }
  5998. }
  5999. }
  6000. func sliderChanged(indexPath: IndexPath, progressSlider: UISlider, timeLabel: UILabel) {
  6001. guard let audioPlayer = audioPlayers[indexPath] else { return }
  6002. audioPlayer.currentTime = TimeInterval(progressSlider.value)
  6003. timeLabel.text = formatTime(audioPlayer.currentTime)
  6004. }
  6005. func formatTime(_ time: TimeInterval) -> String {
  6006. let roundedTime = time.rounded(.up)
  6007. let minutes = Int(roundedTime) / 60
  6008. let seconds = Int(roundedTime) % 60
  6009. return String(format: "%d:%02d", minutes, seconds)
  6010. }
  6011. public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  6012. if let finishedIndexPath = audioPlayers.first(where: { $0.value == player })?.key {
  6013. DispatchQueue.main.async {
  6014. self.timers[finishedIndexPath]?.invalidate()
  6015. self.timers[finishedIndexPath] = nil
  6016. self.playingIndexPath = nil
  6017. self.audioPlayers[finishedIndexPath] = nil
  6018. self.tableChatView.reloadRows(at: [finishedIndexPath], with: .none)
  6019. }
  6020. }
  6021. }
  6022. @objc func imageGroupingTapped(_ sender: ObjectGesture) {
  6023. let listGroupingImages = ListGroupImages()
  6024. listGroupingImages.imageTapped = sender.indexImageTapped
  6025. listGroupingImages.listGroupingImages = sender.listImageFromGrouping
  6026. listGroupingImages.titleName = titleText
  6027. listGroupingImages.isInitiator = sender.isInitiator
  6028. listGroupingImages.isPersonal = false
  6029. listGroupingImages.updateEditor = { [self] updatedData, replyData, isUpdateDelete in
  6030. if replyData.count == 0 {
  6031. if updatedData.count != 0 && !isUpdateDelete {
  6032. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  6033. } else if updatedData.count > 0 {
  6034. let deletedForEveryoneData = updatedData.filter({ $0.dataMessage["lock"] as? String == "1" })
  6035. if deletedForEveryoneData.count != 0 {
  6036. if groupImages[sender.listImageFromGrouping[0].messageId] != nil {
  6037. var dataWillEmpty = updatedData
  6038. while dataWillEmpty.count > 0 {
  6039. if let lastIdx = dataWillEmpty.lastIndex(where: { $0.dataMessage["lock"] as? String == "1" }) {
  6040. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6041. if dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId {
  6042. self.dataMessages.remove(at: idx)
  6043. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx)
  6044. } else {
  6045. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx + 1)
  6046. }
  6047. let subData = Array(updatedData[lastIdx+1..<dataWillEmpty.count])
  6048. if subData.count >= 4 {
  6049. groupImages[subData[0].messageId] = subData
  6050. self.dataMessages.insert(subData[0].dataMessage, at: lastIdx + 1)
  6051. } else {
  6052. if subData.count > 0 {
  6053. self.dataMessages.insert(contentsOf: subData.map({ $0.dataMessage }), at: idx + (dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId ? 1 : 2))
  6054. }
  6055. }
  6056. }
  6057. dataWillEmpty.removeSubrange(lastIdx..<dataWillEmpty.count)
  6058. } else if dataWillEmpty.count >= 4 {
  6059. groupImages[dataWillEmpty[0].messageId] = dataWillEmpty
  6060. dataWillEmpty.removeAll()
  6061. } else {
  6062. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6063. self.dataMessages.remove(at: idx)
  6064. self.dataMessages.insert(contentsOf: dataWillEmpty.map({ $0.dataMessage }), at: idx)
  6065. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6066. }
  6067. dataWillEmpty.removeAll()
  6068. }
  6069. }
  6070. } else {
  6071. }
  6072. } else {
  6073. if updatedData.count >= 4 {
  6074. if updatedData[0].messageId == sender.listImageFromGrouping[0].messageId {
  6075. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  6076. } else {
  6077. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6078. self.dataMessages.remove(at: idx)
  6079. self.dataMessages.insert(updatedData[0].dataMessage, at: idx)
  6080. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6081. groupImages[updatedData[0].messageId] = updatedData
  6082. }
  6083. }
  6084. } else {
  6085. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6086. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6087. self.dataMessages.remove(at: idx)
  6088. let dataMessageInGrouping = updatedData.map({ $0.dataMessage })
  6089. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx)
  6090. }
  6091. }
  6092. }
  6093. } else {
  6094. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  6095. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  6096. self.dataMessages.remove(at: idx)
  6097. }
  6098. }
  6099. DispatchQueue.main.async { [self] in
  6100. tableChatView.reloadData()
  6101. }
  6102. } else if replyData.count != 0 {
  6103. handleReply(indexPath: IndexPath(row: 0, section: 0), dataMessagesImage: replyData)
  6104. }
  6105. }
  6106. self.navigationController?.pushViewController(listGroupingImages, animated: true)
  6107. }
  6108. @objc func tapAck(_ sender: ObjectGesture) {
  6109. let indexPath = sender.indexPath
  6110. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  6111. if dataMessages[indexPath.row]["status"] as? String ?? "" == "8" {
  6112. return
  6113. }
  6114. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  6115. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  6116. imageView.tintColor = .white
  6117. 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)
  6118. banner.show()
  6119. return
  6120. }
  6121. DispatchQueue.global().async {
  6122. var opposite_pin = self.dataGroup["group_id"] as? String ?? ""
  6123. if (self.dataTopic["chat_id"] as? String ?? "" != "") {
  6124. opposite_pin = self.dataTopic["chat_id"] as? String ?? ""
  6125. }
  6126. 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: opposite_pin, server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as? String ?? "", longitude: self.longitude, latitude: self.latitude, description: ""))
  6127. if result != nil {
  6128. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  6129. do {
  6130. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  6131. "status" : "8"
  6132. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  6133. } catch {
  6134. rollback.pointee = true
  6135. print("Access database error: \(error.localizedDescription)")
  6136. }
  6137. })
  6138. DispatchQueue.main.async {
  6139. if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
  6140. self.dataMessages[index]["status"] = "8"
  6141. let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"] as? String ?? "")
  6142. 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 ?? ""})
  6143. if row != nil && section != nil {
  6144. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  6145. }
  6146. self.view.makeToast("Confirmation Success.".localized(), duration: 3)
  6147. }
  6148. }
  6149. }
  6150. }
  6151. }
  6152. @objc func contentMessageTapped(_ sender: ObjectGesture) {
  6153. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6154. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6155. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6156. func showMedia(data: Data? = nil, url: URL? = nil, type: Int = 0) {
  6157. let image = UIImage(data: data ?? Data())
  6158. let imageViewer = MediaViewerViewController()
  6159. if type == 0 {
  6160. imageViewer.media = .image(image ?? UIImage())
  6161. } else if type == 1 {
  6162. imageViewer.media = .video(url ?? URL(string: "")!)
  6163. } else if type == 2 {
  6164. imageViewer.media = .gif(UIImage.gifImageWithData(data ?? Data()) ?? UIImage())
  6165. }
  6166. let navigationController = UINavigationController(rootViewController: imageViewer)
  6167. navigationController.defaultStyle()
  6168. navigationController.view.backgroundColor = .clear
  6169. navigationController.modalPresentationCapturesStatusBarAppearance = true
  6170. navigationController.modalPresentationStyle = .overFullScreen
  6171. let backAction = UIAction { _ in
  6172. navigationController.dismiss(animated: true)
  6173. }
  6174. let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
  6175. imageViewer.navigationItem.leftBarButtonItem = backButton
  6176. if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
  6177. let shareAction = UIAction { _ in
  6178. var activityViewController = UIActivityViewController(activityItems: [image ?? UIImage()], applicationActivities: nil)
  6179. if type == 1 {
  6180. activityViewController = UIActivityViewController(activityItems: [url ?? URL(string: "")!], applicationActivities: nil)
  6181. }
  6182. activityViewController.popoverPresentationController?.sourceView = imageViewer.view
  6183. imageViewer.present(activityViewController, animated: true, completion: nil)
  6184. }
  6185. let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
  6186. imageViewer.navigationItem.rightBarButtonItem = shareButton
  6187. }
  6188. let name = (dataGroup["f_name"] as? String ?? "") + " (\(dataTopic["title"] as? String ?? ""))"
  6189. imageViewer.title = name
  6190. let transitionDelegate = ZoomTransitioningDelegate()
  6191. transitionDelegate.originImageView = sender.imageView
  6192. navigationController.transitioningDelegate = transitionDelegate
  6193. self.transitioningDelegateRef = transitionDelegate
  6194. present(navigationController, animated: true) {
  6195. imageViewer.animateBackgroundIn()
  6196. }
  6197. }
  6198. if (sender.image_id != "") {
  6199. if let dirPath = paths.first {
  6200. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  6201. if FileManager.default.fileExists(atPath: imageURL.path) {
  6202. do {
  6203. showMedia(data: try Data(contentsOf: imageURL))
  6204. } catch {
  6205. }
  6206. } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
  6207. do {
  6208. if var data = try FileEncryption.shared.readSecure(filename: sender.image_id) {
  6209. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  6210. if dataDecrypt != nil {
  6211. data = dataDecrypt!
  6212. }
  6213. showMedia(data: data)
  6214. }
  6215. }
  6216. catch {
  6217. print("Error reading secure file")
  6218. }
  6219. } else {
  6220. for view in sender.imageView.subviews {
  6221. if view is UIImageView {
  6222. view.removeFromSuperview()
  6223. }
  6224. }
  6225. let activityIndicator = UIActivityIndicatorView(style: .large)
  6226. activityIndicator.color = .mainColor
  6227. activityIndicator.hidesWhenStopped = true
  6228. activityIndicator.center = CGPoint(x:sender.imageView.frame.width/2,
  6229. y: sender.imageView.frame.height/2)
  6230. activityIndicator.startAnimating()
  6231. sender.imageView.addSubview(activityIndicator)
  6232. Download().startHTTP(forKey: sender.image_id) { (name, progress) in
  6233. guard progress == 100 else {
  6234. return
  6235. }
  6236. DispatchQueue.main.async {
  6237. activityIndicator.stopAnimating()
  6238. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  6239. }
  6240. }
  6241. }
  6242. }
  6243. } else if (sender.gif_id != "") {
  6244. if let dirPath = paths.first {
  6245. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.gif_id)
  6246. if FileManager.default.fileExists(atPath: gifURL.path) {
  6247. do {
  6248. let data = try Data(contentsOf: gifURL)
  6249. showMedia(data: data, type: 2)
  6250. } catch {
  6251. }
  6252. } else if FileEncryption.shared.isSecureExists(filename: sender.gif_id) {
  6253. do {
  6254. if var secureData = try FileEncryption.shared.readSecure(filename: sender.gif_id) {
  6255. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  6256. if dataDecrypt != nil {
  6257. secureData = dataDecrypt!
  6258. }
  6259. showMedia(data: secureData, type: 2)
  6260. }
  6261. } catch {
  6262. }
  6263. }
  6264. }
  6265. } else if (sender.video_id != "") {
  6266. if let dirPath = paths.first {
  6267. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  6268. if FileManager.default.fileExists(atPath: videoURL.path) {
  6269. showMedia(url: videoURL, type: 1)
  6270. } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
  6271. do {
  6272. if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
  6273. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  6274. if dataDecrypt != nil {
  6275. secureData = dataDecrypt!
  6276. }
  6277. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  6278. let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
  6279. try secureData.write(to: tempPath)
  6280. showMedia(url: tempPath, type: 1)
  6281. }
  6282. } catch {
  6283. }
  6284. } else {
  6285. if downloadList[sender.video_id] != nil && downloadList[sender.video_id] == sender.indexPath {
  6286. return
  6287. }
  6288. downloadList[sender.video_id] = sender.indexPath
  6289. for view in sender.imageView.subviews {
  6290. if view is UIImageView {
  6291. view.removeFromSuperview()
  6292. }
  6293. }
  6294. let container = UIView()
  6295. sender.imageView.addSubview(container)
  6296. container.translatesAutoresizingMaskIntoConstraints = false
  6297. container.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  6298. container.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  6299. container.widthAnchor.constraint(equalToConstant: 50).isActive = true
  6300. container.heightAnchor.constraint(equalToConstant: 50).isActive = true
  6301. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 20, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  6302. let trackShape = CAShapeLayer()
  6303. trackShape.path = circlePath.cgPath
  6304. trackShape.fillColor = UIColor.clear.cgColor
  6305. trackShape.lineWidth = 10
  6306. trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
  6307. container.backgroundColor = .clear
  6308. container.layer.addSublayer(trackShape)
  6309. let shapeLoading = CAShapeLayer()
  6310. shapeLoading.path = circlePath.cgPath
  6311. shapeLoading.fillColor = UIColor.clear.cgColor
  6312. shapeLoading.lineWidth = 10
  6313. shapeLoading.strokeEnd = 0
  6314. shapeLoading.strokeColor = UIColor.mentionColor.cgColor
  6315. container.layer.addSublayer(shapeLoading)
  6316. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6317. imageDownload.tintColor = .white
  6318. container.addSubview(imageDownload)
  6319. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  6320. imageDownload.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  6321. imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  6322. imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6323. imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6324. Download().startHTTP(forKey: sender.video_id) { (name, progress) in
  6325. DispatchQueue.main.async {
  6326. guard progress == 100 else {
  6327. shapeLoading.strokeEnd = CGFloat(progress / 100)
  6328. return
  6329. }
  6330. let idx = self.dataMessages.firstIndex(where: { $0["video_id"] as? String ?? "" == sender.video_id})
  6331. if idx != nil {
  6332. self.dataMessages[idx!]["progress"] = progress
  6333. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  6334. }
  6335. }
  6336. }
  6337. }
  6338. }
  6339. } else if (sender.file_id != "") {
  6340. func showFile(urlFile: URL) {
  6341. let previewController = QLPreviewController()
  6342. previewController.dataSource = self
  6343. let vcHandleFile = UIViewController()
  6344. let nc = UINavigationController(rootViewController: vcHandleFile)
  6345. let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
  6346. let navBarAppearance = UINavigationBarAppearance()
  6347. nc.defaultStyle()
  6348. navBarAppearance.configureWithOpaqueBackground()
  6349. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
  6350. navBarAppearance.titleTextAttributes = attributes
  6351. nc.navigationBar.standardAppearance = navBarAppearance
  6352. nc.navigationBar.scrollEdgeAppearance = navBarAppearance
  6353. let backAction = UIAction { _ in
  6354. nc.dismiss(animated: true)
  6355. }
  6356. let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
  6357. vcHandleFile.navigationItem.leftBarButtonItem = backButton
  6358. if Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share") {
  6359. let shareAction = UIAction { _ in
  6360. let fileManager = FileManager.default
  6361. let tempURL = fileManager.temporaryDirectory.appendingPathComponent(sender.labelFile.text ?? "")
  6362. do {
  6363. try fileManager.copyItem(at: urlFile, to: tempURL)
  6364. let activityViewController = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
  6365. activityViewController.popoverPresentationController?.sourceView = vcHandleFile.view
  6366. vcHandleFile.present(activityViewController, animated: true, completion: nil)
  6367. } catch {
  6368. }
  6369. }
  6370. let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
  6371. vcHandleFile.navigationItem.rightBarButtonItem = shareButton
  6372. }
  6373. if let viewVc = vcHandleFile.view {
  6374. vcHandleFile.title = sender.labelFile.text
  6375. vcHandleFile.addChild(previewController)
  6376. previewController.dataSource = self
  6377. previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
  6378. previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6379. viewVc.addSubview(previewController.view)
  6380. previewController.didMove(toParent: vcHandleFile)
  6381. self.present(nc, animated: true)
  6382. }
  6383. }
  6384. if let dirPath = paths.first {
  6385. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id)
  6386. if FileManager.default.fileExists(atPath: fileURL.path) {
  6387. self.previewItem = fileURL as NSURL
  6388. showFile(urlFile: fileURL)
  6389. } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
  6390. do {
  6391. if var docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
  6392. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: docData)
  6393. if dataDecrypt != nil {
  6394. docData = dataDecrypt!
  6395. }
  6396. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  6397. let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
  6398. try docData.write(to: tempPath)
  6399. self.previewItem = tempPath as NSURL
  6400. showFile(urlFile: tempPath)
  6401. }
  6402. }
  6403. catch {
  6404. }
  6405. } else {
  6406. if downloadList[sender.file_id] != nil && downloadList[sender.file_id] == sender.indexPath {
  6407. return
  6408. }
  6409. downloadList[sender.file_id] = sender.indexPath
  6410. for view in sender.containerFile.subviews {
  6411. if !(view is UIImageView) && !(view is UILabel) {
  6412. view.removeFromSuperview()
  6413. }
  6414. }
  6415. let containerLoading = UIView()
  6416. sender.containerFile.addSubview(containerLoading)
  6417. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  6418. containerLoading.centerYAnchor.constraint(equalTo: sender.containerFile.centerYAnchor).isActive = true
  6419. containerLoading.leadingAnchor.constraint(equalTo: sender.labelFile.trailingAnchor, constant: 5).isActive = true
  6420. containerLoading.trailingAnchor.constraint(equalTo: sender.containerFile.trailingAnchor, constant: -5).isActive = true
  6421. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6422. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6423. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  6424. let trackShape = CAShapeLayer()
  6425. trackShape.path = circlePath.cgPath
  6426. trackShape.fillColor = UIColor.clear.cgColor
  6427. trackShape.lineWidth = 5
  6428. trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
  6429. containerLoading.layer.addSublayer(trackShape)
  6430. let shapeLoading = CAShapeLayer()
  6431. shapeLoading.path = circlePath.cgPath
  6432. shapeLoading.fillColor = UIColor.clear.cgColor
  6433. shapeLoading.lineWidth = 3
  6434. shapeLoading.strokeEnd = 0
  6435. shapeLoading.strokeColor = UIColor.mentionColor.cgColor
  6436. containerLoading.layer.addSublayer(shapeLoading)
  6437. let imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6438. imageupload.tintColor = .white
  6439. containerLoading.addSubview(imageupload)
  6440. imageupload.translatesAutoresizingMaskIntoConstraints = false
  6441. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  6442. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  6443. Download().startHTTP(forKey: sender.file_id) { (name, progress) in
  6444. DispatchQueue.main.async {
  6445. guard progress == 100 else {
  6446. shapeLoading.strokeEnd = CGFloat(progress / 100)
  6447. return
  6448. }
  6449. let idx = self.dataMessages.firstIndex(where: { $0["file_id"] as? String ?? "" == sender.file_id})
  6450. if idx != nil {
  6451. self.dataMessages[idx!]["progress"] = progress
  6452. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  6453. }
  6454. }
  6455. }
  6456. }
  6457. }
  6458. } else {
  6459. DispatchQueue.main.async {
  6460. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.message_id})
  6461. if idx == nil {
  6462. return
  6463. }
  6464. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  6465. if section == nil {
  6466. return
  6467. }
  6468. 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 ?? ""})
  6469. if row == nil {
  6470. return
  6471. }
  6472. let indexPath = IndexPath(row: row!, section: section!)
  6473. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  6474. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  6475. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  6476. let containerMessage = cell.contentView.subviews[1]
  6477. let idMe = User.getMyPin() as String?
  6478. if (self.dataMessages[idx!]["f_pin"] as? String == idMe) {
  6479. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  6480. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  6481. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  6482. containerMessage.backgroundColor = .clear
  6483. } else {
  6484. containerMessage.backgroundColor = .blueBubbleColor
  6485. }
  6486. }
  6487. } else {
  6488. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  6489. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  6490. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  6491. containerMessage.backgroundColor = .clear
  6492. } else {
  6493. containerMessage.backgroundColor = .whiteBubbleColor
  6494. }
  6495. }
  6496. }
  6497. }
  6498. }
  6499. }
  6500. }
  6501. }
  6502. func highlightedText(for text: String, in range: Range<String.Index>, textView: UITextView) -> NSAttributedString {
  6503. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  6504. mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.lightGray.withAlphaComponent(0.5), range: NSRange(range, in: text))
  6505. return mutableAttributedString
  6506. }
  6507. func removeHighlightedText(for text: String, in range: Range<String.Index>, textView: UITextView) -> NSAttributedString {
  6508. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  6509. mutableAttributedString.removeAttribute(.backgroundColor, range: NSRange(range, in: text))
  6510. return mutableAttributedString
  6511. }
  6512. @objc func tapMessageText(_ sender: ObjectGesture) {
  6513. var stringURl = sender.message_id
  6514. if stringURl.lowercased().starts(with: "www.") {
  6515. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  6516. }
  6517. if Nexilis.checkingAccess(key: "secure_browser") {
  6518. APIS.openUrl(url: stringURl)
  6519. } else {
  6520. guard let url = URL(string: stringURl) else { return }
  6521. UIApplication.shared.open(url)
  6522. }
  6523. }
  6524. // public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  6525. // if copySession || forwardSession || deleteSession {
  6526. // return nil
  6527. // }
  6528. // let idMe = User.getMyPin() as String?
  6529. // if (dataMessages[indexPath.row]["f_pin"] as? String != idMe) {
  6530. // return nil
  6531. // }
  6532. // let messageInfoVC = MessageInfo()
  6533. // self.navigationController?.show(messageInfoVC, sender: nil)
  6534. // return UISwipeActionsConfiguration()
  6535. // }
  6536. //
  6537. // public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  6538. // if copySession || forwardSession || deleteSession {
  6539. // return nil
  6540. // }
  6541. // let action = UIContextualAction(style: .normal,
  6542. // title: "") { [weak self] (action, view, completionHandler) in
  6543. // self?.handleReply(indexPath: indexPath)
  6544. // completionHandler(true)
  6545. // }
  6546. // action.backgroundColor = .white
  6547. // action.image = UIImage(systemName: "arrowshape.turn.up.left.fill")?.withTintColor(.black, renderingMode: .alwaysOriginal)
  6548. // return UISwipeActionsConfiguration(actions: [action])
  6549. // }
  6550. private func handleReply(indexPath: IndexPath, dataMessagesImage: [String: Any?] = [:], reffId: String = "") {
  6551. var dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  6552. if reffId.isEmpty {
  6553. self.deleteReplyView()
  6554. if dataMessagesImage.count != 0 {
  6555. dataMessages = [dataMessagesImage]
  6556. } else {
  6557. self.textFieldSend.becomeFirstResponder()
  6558. }
  6559. self.reffId = dataMessages[indexPath.row]["message_id"] as? String
  6560. } else {
  6561. dataMessages = self.dataMessages.filter({ $0["message_id"] as? String ?? "" == reffId })
  6562. self.reffId = reffId
  6563. }
  6564. if dataMessages.count == 0 {
  6565. self.deleteReplyView()
  6566. return
  6567. }
  6568. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  6569. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50 + (self.offset()*3)
  6570. if self.contraintBottomMention.constant > 0 {
  6571. self.contraintBottomMention.constant = self.contraintBottomMention.constant + self.heightTextFieldSend.constant
  6572. }
  6573. }, completion: nil)
  6574. if (self.currentIndexpath != nil) {
  6575. DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
  6576. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  6577. }
  6578. } else {
  6579. self.tableChatView.scrollToBottom()
  6580. }
  6581. self.viewTextfield.addSubview(self.containerPreviewReply)
  6582. self.containerPreviewReply.translatesAutoresizingMaskIntoConstraints = false
  6583. self.containerPreviewReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  6584. self.containerPreviewReply.topAnchor.constraint(equalTo: self.viewTextfield.topAnchor).isActive = true
  6585. if !self.containerLink.isDescendant(of: self.viewTextfield) {
  6586. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  6587. } else {
  6588. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  6589. }
  6590. self.bottomAnchorPreviewReply.isActive = true
  6591. self.containerPreviewReply.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  6592. self.containerPreviewReply.backgroundColor = .secondaryColor
  6593. let leftReply = UIView()
  6594. self.containerPreviewReply.addSubview(leftReply)
  6595. leftReply.translatesAutoresizingMaskIntoConstraints = false
  6596. leftReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  6597. leftReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor).isActive = true
  6598. leftReply.bottomAnchor.constraint(equalTo: self.containerPreviewReply.bottomAnchor).isActive = true
  6599. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  6600. leftReply.backgroundColor = .orangeColor
  6601. let titleReply = UILabel()
  6602. self.containerPreviewReply.addSubview(titleReply)
  6603. titleReply.translatesAutoresizingMaskIntoConstraints = false
  6604. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  6605. titleReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor, constant: 10).isActive = true
  6606. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  6607. let idMe = User.getMyPin() as String?
  6608. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6609. titleReply.text = "You".localized()
  6610. } else {
  6611. if dataMessages[indexPath.row]["f_pin"] as? String != "-999" {
  6612. let dataPerson = self.getDataProfile(f_pin: dataMessages[indexPath.row]["f_pin"] as? String ?? "", message_id: dataMessages[indexPath.row]["message_id"] as? String ?? "")
  6613. titleReply.text = dataPerson["name"]
  6614. } else {
  6615. titleReply.text = "Bot"
  6616. }
  6617. }
  6618. titleReply.textColor = .orangeColor
  6619. let contentReply = UILabel()
  6620. self.containerPreviewReply.addSubview(contentReply)
  6621. contentReply.translatesAutoresizingMaskIntoConstraints = false
  6622. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  6623. contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor).isActive = true
  6624. contentReply.trailingAnchor.constraint(equalTo: containerPreviewReply.trailingAnchor, constant: -20).isActive = true
  6625. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  6626. let message_text = dataMessages[indexPath.row]["message_text"] as? String ?? ""
  6627. let attachment_flag = dataMessages[indexPath.row]["attachment_flag"] as? String ?? ""
  6628. let thumb_chat = dataMessages[indexPath.row]["thumb_id"] as? String ?? ""
  6629. let image_chat = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  6630. let video_chat = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  6631. let file_chat = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  6632. if (attachment_flag == "0" && thumb_chat == "") {
  6633. contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  6634. } else if (attachment_flag == "1" || image_chat != "") {
  6635. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  6636. contentReply.text = "📷 Photo".localized()
  6637. } else {
  6638. contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  6639. }
  6640. } else if (attachment_flag == "2" || video_chat != "") {
  6641. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  6642. contentReply.text = "📹 Video".localized()
  6643. } else {
  6644. contentReply.attributedText = message_text.richText(group_id: self.dataGroup["group_id"] as? String ?? "")
  6645. }
  6646. } else if (attachment_flag == "6" || file_chat != ""){
  6647. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  6648. } else if (attachment_flag == "11") {
  6649. contentReply.text = "❤️ Sticker"
  6650. } else if attachment_flag == "27" {
  6651. contentReply.text = "📄 " + "Live Streaming".localized()
  6652. } else if attachment_flag == "26" {
  6653. contentReply.text = "📄 " + "Seminar".localized()
  6654. }
  6655. contentReply.textColor = .gray
  6656. let buttonCancelReply = UIButton(type: .custom)
  6657. self.containerPreviewReply.addSubview(buttonCancelReply)
  6658. buttonCancelReply.translatesAutoresizingMaskIntoConstraints = false
  6659. buttonCancelReply.trailingAnchor.constraint(equalTo: self.containerPreviewReply.trailingAnchor, constant: -10).isActive = true
  6660. buttonCancelReply.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  6661. buttonCancelReply.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  6662. buttonCancelReply.addTarget(nil, action: #selector(self.deleteReplyView), for: .touchUpInside)
  6663. buttonCancelReply.backgroundColor = .clear
  6664. buttonCancelReply.tintColor = .mainColor
  6665. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  6666. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6667. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6668. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6669. if let dirPath = paths.first {
  6670. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  6671. let image : UIImage? = {
  6672. if let img = Nexilis.imageCache.object(forKey: thumb_chat as NSString) {
  6673. return img
  6674. }
  6675. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6676. Nexilis.imageCache.setObject(img, forKey: thumb_chat as NSString)
  6677. return img
  6678. }
  6679. return nil
  6680. }()
  6681. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  6682. let imageThumb = UIImageView(image: image)
  6683. self.containerPreviewReply.addSubview(imageThumb)
  6684. imageThumb.layer.cornerRadius = 2.0
  6685. imageThumb.clipsToBounds = true
  6686. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  6687. imageThumb.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  6688. imageThumb.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  6689. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6690. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6691. if (attachment_flag == "2") {
  6692. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  6693. imageThumb.addSubview(imagePlay)
  6694. imagePlay.clipsToBounds = true
  6695. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  6696. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  6697. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  6698. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  6699. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  6700. imagePlay.tintColor = .white
  6701. }
  6702. }
  6703. }
  6704. if (attachment_flag == "11") {
  6705. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  6706. self.containerPreviewReply.addSubview(imageSticker)
  6707. imageSticker.layer.cornerRadius = 2.0
  6708. imageSticker.clipsToBounds = true
  6709. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  6710. imageSticker.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  6711. imageSticker.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  6712. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6713. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6714. }
  6715. }
  6716. func scrollToFirstSearchMessage(indexScroll: Int = 1) {
  6717. if textSearch.count < 2 {
  6718. return
  6719. }
  6720. var lastIndex = 0
  6721. let messageTextForSearch: [[String: Any?]] = self.dataMessages.reversed()
  6722. for idx in 0..<messageTextForSearch.count {
  6723. if (messageTextForSearch[idx]["message_text"] as? String ?? "").lowercased().contains(textSearch) {
  6724. lastIndex += 1
  6725. if lastIndex < indexScroll {
  6726. continue
  6727. }
  6728. lastScrollIdxSearch = lastIndex
  6729. let section = self.dataDates.firstIndex(of: messageTextForSearch[idx]["chat_date"] as? String ?? "")
  6730. if section == nil {
  6731. return
  6732. }
  6733. 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 ?? ""})
  6734. if row == nil {
  6735. return
  6736. }
  6737. let indexPath = IndexPath(row: row!, section: section!)
  6738. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  6739. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  6740. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  6741. let containerMessage = cell.contentView.subviews[1]
  6742. let idMe = User.getMyPin() as String?
  6743. if (messageTextForSearch[idx]["f_pin"] as? String == idMe) {
  6744. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  6745. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  6746. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  6747. containerMessage.backgroundColor = .clear
  6748. } else {
  6749. containerMessage.backgroundColor = .blueBubbleColor
  6750. }
  6751. }
  6752. } else {
  6753. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  6754. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  6755. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  6756. containerMessage.backgroundColor = .clear
  6757. } else {
  6758. containerMessage.backgroundColor = .whiteBubbleColor
  6759. }
  6760. }
  6761. }
  6762. }
  6763. }
  6764. titleSearchMatches.isHidden = false
  6765. if countMatchesSearch != 0 {
  6766. if countMatchesSearch > 1 {
  6767. titleSearchMatches.text = "\(lastScrollIdxSearch) " + "of".localized() + " \(countMatchesSearch) " + "matches".localized()
  6768. } else {
  6769. titleSearchMatches.text = "\(countMatchesSearch) " + "matches".localized()
  6770. }
  6771. } else {
  6772. titleSearchMatches.text = "Not found".localized()
  6773. }
  6774. if lastScrollIdxSearch == countMatchesSearch || countMatchesSearch == 0 {
  6775. buttonUp.isEnabled = false
  6776. buttonUp.tintColor = .gray
  6777. } else {
  6778. buttonUp.isEnabled = true
  6779. buttonUp.tintColor = .mainColor
  6780. }
  6781. if countMatchesSearch == 0 || lastScrollIdxSearch == 1 || countMatchesSearch == 1 {
  6782. buttonDown.isEnabled = false
  6783. buttonDown.tintColor = .gray
  6784. } else {
  6785. buttonDown.isEnabled = true
  6786. buttonDown.tintColor = .mainColor
  6787. }
  6788. break
  6789. }
  6790. }
  6791. }
  6792. public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  6793. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  6794. if indexPath != nil {
  6795. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  6796. let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  6797. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && indexPath!.row > 0 {
  6798. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  6799. let headerView = listViewOnSection[sect]
  6800. headerView.isHidden = true
  6801. }
  6802. }
  6803. }
  6804. public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  6805. if !decelerate {
  6806. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  6807. if indexPath != nil {
  6808. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  6809. let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  6810. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  6811. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  6812. let headerView = listViewOnSection[sect]
  6813. headerView.isHidden = true
  6814. }
  6815. }
  6816. }
  6817. }
  6818. }
  6819. extension EditorGroup: UISearchBarDelegate {
  6820. public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
  6821. timerSearch?.invalidate()
  6822. if searchText.count > 1 {
  6823. timerSearch = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {[self] _ in
  6824. textSearch = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
  6825. titleSearchMatches.isHidden = true
  6826. countMatchesSearch = Chat.getCountSearchMessage(key: textSearch, pin: (self.dataGroup["group_id"] as? String) ?? "", chatId: (self.dataTopic["chat_id"] as? String) ?? "", isPersonal: false)
  6827. tableChatView.reloadData()
  6828. scrollToFirstSearchMessage()
  6829. })
  6830. }
  6831. }
  6832. }