EditorGroup.swift 424 KB

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