EditorGroup.swift 392 KB

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