EditorGroup.swift 421 KB

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