EditorPersonal.swift 618 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238
  1. //
  2. // EditorPersonal.swift
  3. // Qmera
  4. //
  5. // Created by Akhmad Al Qindi Irsyam on 31/08/21.
  6. //
  7. import UIKit
  8. import AVKit
  9. import AVFoundation
  10. import QuickLook
  11. import NotificationBannerSwift
  12. import Photos
  13. import nuSDKService
  14. import SwiftLinkPreview
  15. import SDWebImage
  16. import PhotosUI
  17. import ObjectiveC
  18. import UniformTypeIdentifiers
  19. public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate {
  20. @IBOutlet var wallpaperView: UIImageView!
  21. @IBOutlet var viewButton: UIView!
  22. @IBOutlet var constraintViewTextField: NSLayoutConstraint!
  23. @IBOutlet var buttonVoice: UIButton!
  24. @IBOutlet var buttonSendImage: UIButton!
  25. @IBOutlet var buttonSendPhoto: UIButton!
  26. @IBOutlet var buttonSendSticker: UIButton!
  27. @IBOutlet var buttonSendFile: UIButton!
  28. @IBOutlet var textFieldSend: CustomTextView!
  29. @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
  30. @IBOutlet var buttonSendChat: UIButton!
  31. @IBOutlet var tableChatView: UITableView!
  32. @IBOutlet var constraintTopTextField: NSLayoutConstraint!
  33. @IBOutlet var constraintBottomAttachment: NSLayoutConstraint!
  34. @IBOutlet var viewTextfield: UIView!
  35. @IBOutlet weak var buttonAckConfidential: UIButton!
  36. @IBOutlet weak var constraintLeftTextField: NSLayoutConstraint!
  37. @IBOutlet weak var constraintBottomTableViewWithTextfield: NSLayoutConstraint!
  38. @IBOutlet weak var viewAttachment: UIStackView!
  39. @IBOutlet weak var tableMention: UITableView!
  40. @IBOutlet weak var heightTableMention: NSLayoutConstraint!
  41. @IBOutlet weak var contraintBottomMention: NSLayoutConstraint!
  42. public var dataPerson: [String: String?] = [:]
  43. var dataMessages: [[String: Any?]] = [] {
  44. didSet {
  45. groupMessagesByDate()
  46. }
  47. }
  48. var messagesByDate: [String: [[String: Any?]]] = [:]
  49. var dataDates: [String] = []
  50. var users: [User] = []
  51. public var dataMessageForward: [[String: Any?]]?
  52. var imageVideoPicker: ImageVideoPicker!
  53. var documentPicker: DocumentPicker!
  54. var currentIndexpath: IndexPath?
  55. var previewItem: NSURL?
  56. var reffId: String?
  57. var stickers = [String]()
  58. public var unique_l_pin = ""
  59. public var isContactCenter = false
  60. public var isRequestContactCenter = true
  61. public var fromNotification = false
  62. public var onGoingCC = false
  63. public var fPinContacCenter = ""
  64. public var complaintId = ""
  65. public var referenceMessageId = ""
  66. public var referenceChatDate = ""
  67. var channelContactCenter = ""
  68. var counter = 0
  69. var dateStartCC = ""
  70. var markerCounter: String?
  71. var buttonScrollToBottom = UIButton()
  72. let indicatorCounterBSTB = UIView()
  73. let labelCounter = UILabel()
  74. var copySession = false
  75. var forwardSession = false
  76. var deleteSession = false
  77. var isSearching = false
  78. let containerMultpileSelectSession = UIView()
  79. let containerAction = UIView()
  80. var removed = false
  81. var isConfidential = false
  82. var isAck = false
  83. var isSecret = false
  84. let viewSticker = UIView()
  85. let containerLink = UIView()
  86. let containerPreviewReply = UIView()
  87. let containerPin = UIView()
  88. let textPin = UILabel()
  89. let signSelectedPin = UIStackView()
  90. var bottomAnchorPreviewReply = NSLayoutConstraint()
  91. var blocking = ""
  92. var timeoutCC = Timer()
  93. var nowSelectedCategoryCC = ""
  94. var showToastTwiceClick = false
  95. var showToast30s = false
  96. var allowTyping = true
  97. var hapticSwipeLeft = false
  98. var listTimerCredential: [String: Int] = [:]
  99. var timerCredential: [String: Timer] = [:]
  100. let contactChatNav = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
  101. var searchBar: UISearchBar!
  102. var constraintBottomContainerMultpileSelectSession = NSLayoutConstraint()
  103. var titleSearchMatches: UILabel!
  104. var textSearch = ""
  105. var nextPinShowed = 0
  106. var countMatchesSearch = 0
  107. var lastScrollIdxSearch = 0
  108. var buttonUp: UIButton!
  109. var buttonDown: UIButton!
  110. var multipleOffsetUp = 1
  111. var lastOffsetDown = 1
  112. var gettingDataMessage = true
  113. var keyboardHeightForMention: CGFloat?
  114. var listMentionWithText:[User] = []
  115. var listMentionInTextField:[User] = []
  116. var tempListMentionWithText:[User] = []
  117. var tempListMentionInTextField:[User] = []
  118. var showingLink = ""
  119. var isAlwaysHideLinkPreview = false
  120. var timerCheckLink: Timer?
  121. var lastPositionCursorMention = 0
  122. var lastTextLength = 0
  123. var timerFakeProgress: Timer?
  124. var showMenuContext = false
  125. var touchedSubview = UIView()
  126. var listViewOnSection: [UIView] = []
  127. var fromVCAC = false
  128. var serviceIdCC = ""
  129. var isDirectCC = false
  130. var fakeProgMultip = 0
  131. let maxFakeProgMultip = 2
  132. var groupImages: [String:[ImageGrouping]] = [:]
  133. var titleText: String!
  134. var lastY: CGFloat = 0
  135. var editVC = UIViewController()
  136. var editTextView = CustomTextView()
  137. var isEditingMessage = false
  138. var constraintBottomeditTextView: NSLayoutConstraint!
  139. var constraintHeighteditTextView: NSLayoutConstraint!
  140. var constraintBottomSendEditTV: NSLayoutConstraint!
  141. let locationManager = CLLocationManager()
  142. var longitude = ""
  143. var latitude = ""
  144. var isBlackCancelButton = false
  145. let buttonSendEdit = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
  146. var audioPlayers: [IndexPath: AVAudioPlayer] = [:]
  147. var timers: [IndexPath: Timer] = [:]
  148. var playingIndexPath: IndexPath?
  149. var timerSearch: Timer?
  150. var downloadList: [String: IndexPath] = [:]
  151. var transitioningDelegateRef: ZoomTransitioningDelegate?
  152. var buttonSpec = UIButton(type: .custom)
  153. var tableViewConfigFile: UITableView!
  154. var specFileString = ""
  155. var contextCC = ""
  156. var tableMentionEdit = UITableView()
  157. var heightTableEditMention: NSLayoutConstraint!
  158. func offset() -> CGFloat{
  159. guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
  160. return CGFloat(fontSize)
  161. }
  162. private func groupMessagesByDate() {
  163. messagesByDate = Dictionary(
  164. grouping: dataMessages.compactMap { (msg: [String: Any?]) -> [String: Any?]? in
  165. guard let _ = msg["chat_date"] as? String else { return nil }
  166. return msg
  167. },
  168. by: { (msg: [String: Any?]) -> String in
  169. return msg["chat_date"] as! String
  170. }
  171. )
  172. }
  173. public override func viewDidDisappear(_ animated: Bool) {
  174. if self.isMovingFromParent {
  175. removeAllObjectBeforeDismissVC()
  176. }
  177. }
  178. public override func viewDidAppear(_ animated: Bool) {
  179. let navBarAppearance = UINavigationBarAppearance()
  180. navBarAppearance.configureWithOpaqueBackground()
  181. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  182. navigationController?.navigationBar.standardAppearance = navBarAppearance
  183. navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
  184. navigationController?.navigationBar.isTranslucent = false
  185. navigationController?.navigationBar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  186. navigationController?.navigationBar.tintColor = .white
  187. navigationController?.navigationBar.overrideUserInterfaceStyle = .dark
  188. self.setNeedsStatusBarAppearanceUpdate()
  189. navigationController?.navigationBar.barStyle = .black
  190. if self.navigationController?.isNavigationBarHidden ?? false {
  191. self.navigationController?.setNavigationBarHidden(false, animated: false)
  192. }
  193. navigationController?.navigationBar.prefersLargeTitles = false
  194. navigationController?.navigationItem.largeTitleDisplayMode = .never
  195. updateProfile()
  196. gettingDataMessage = false
  197. // let indexPath = tableChatView.indexPathsForVisibleRows?.first
  198. // if indexPath != nil && currentIndexpath != nil {
  199. // let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  200. // let isPinned = headerRect.origin.y <= tableChatView.contentOffset.y
  201. // if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  202. // let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  203. // let headerView = listViewOnSection[sect]
  204. // headerView.isHidden = true
  205. // }
  206. // }
  207. }
  208. public override func viewDidLoad() {
  209. super.viewDidLoad()
  210. // navigationController?.navigationBar.topItem?.title = ""
  211. Utils.addBackground(view: self.view)
  212. if let dataWall = UserDefaults.standard.data(forKey: "chatWallpaper") {
  213. wallpaperView.image = UIImage(data: UserDefaults.standard.data(forKey: "chatWallpaper")!)
  214. }
  215. else {
  216. wallpaperView.isHidden = true
  217. }
  218. if Nexilis.fromMAB {
  219. Nexilis.floatingButton.isHidden = true
  220. }
  221. viewButton.layer.shadowColor = self.traitCollection.userInterfaceStyle == .dark ? UIColor.white.cgColor : UIColor.gray.cgColor
  222. viewButton.layer.shadowOpacity = 1
  223. viewButton.layer.shadowOffset = .zero
  224. viewButton.layer.shadowRadius = 3
  225. viewButton.addTopBorder(with: UIColor.lightGray, andWidth: 1.0)
  226. // buttonVoice.setImage(resizeImage(image: UIImage(named: "Voice-Record", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)), for: .normal)
  227. viewAttachment.backgroundColor = .white
  228. 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)
  229. 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)
  230. 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)
  231. 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)
  232. 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)
  233. buttonSendChat.circle()
  234. buttonSendChat.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
  235. buttonSendChat.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  236. if isContactCenter {
  237. buttonAckConfidential.isHidden = true
  238. constraintLeftTextField.constant = 20
  239. } else {
  240. buttonAckConfidential.circle()
  241. buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
  242. buttonAckConfidential.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  243. buttonAckConfidential.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  244. }
  245. textFieldSend.backgroundColor = .white
  246. textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
  247. textFieldSend.layer.borderWidth = 1.0
  248. textFieldSend.text = "Send message".localized()
  249. textFieldSend.textColor = UIColor.lightGray
  250. textFieldSend.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  251. textFieldSend.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  252. textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  253. textFieldSend.font = UIFont.systemFont(ofSize: 12 + offset())
  254. textFieldSend.delegate = self
  255. textFieldSend.customDelegate = self
  256. textFieldSend.allowsEditingTextAttributes = true
  257. navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
  258. imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self)
  259. documentPicker = DocumentPicker(presentationController: self, delegate: self)
  260. let fm = FileManager.default
  261. if Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") != nil {
  262. let path = Bundle.resourceBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  263. let items = try! fm.contentsOfDirectory(atPath: path)
  264. for item in items {
  265. if item.hasPrefix("sticker") {
  266. stickers.append(item)
  267. }
  268. }
  269. } else {
  270. let path = Bundle.resourcesMediaBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  271. let items = try! fm.contentsOfDirectory(atPath: path)
  272. for item in items {
  273. if item.hasPrefix("sticker") {
  274. stickers.append(item)
  275. }
  276. }
  277. }
  278. tableChatView.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditorPersonal")
  279. loadData()
  280. setRightButtonItem()
  281. let center: NotificationCenter = NotificationCenter.default
  282. center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  283. center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  284. center.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil)
  285. center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil)
  286. center.addObserver(self, selector: #selector(onUploadChat(notification:)), name: NSNotification.Name(rawValue: "onUploadChat"), object: nil)
  287. center.addObserver(self, selector: #selector(onUnfriend(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
  288. center.addObserver(self, selector: #selector(onTyping(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerTypingChat), object: nil)
  289. center.addObserver(self, selector: #selector(onFailedSendMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.failedSendMessage), object: nil)
  290. center.addObserver(self, selector: #selector(onRefreshCallLog(notification:)), name: NSNotification.Name(rawValue: "refreshCallLog"), object: nil)
  291. center.addObserver(self, selector: #selector(onUpdatedMessage(notification:)), name: NSNotification.Name(rawValue: "onUpdatedMessage"), object: nil)
  292. center.addObserver(self, selector: #selector(onCheckNewMessages(notification:)), name: NSNotification.Name(rawValue: "checkNewMessagesNexilis"), object: nil)
  293. locationManager.delegate = self
  294. locationManager.requestWhenInUseAuthorization()
  295. DispatchQueue.global().async { [self] in
  296. // Check if location services are enabled
  297. if CLLocationManager.locationServicesEnabled() {
  298. locationManager.desiredAccuracy = kCLLocationAccuracyBest
  299. locationManager.startUpdatingLocation()
  300. } else {
  301. print("Location services are not enabled.")
  302. }
  303. }
  304. if dataMessageForward != nil {
  305. for i in 0..<dataMessageForward!.count {
  306. let isForwarded = (dataMessageForward![i][TypeDataMessage.is_forwarded] as? Int) ?? 0
  307. sendChat(message_scope_id: MessageScope.WHISPER, status: "2", message_text: dataMessageForward![i]["message_text"] as? String ?? "", credential: "0", attachment_flag: dataMessageForward![i]["attachment_flag"] as? String ?? "", ex_blog_id: "", message_large_text: "", ex_format: "", image_id: dataMessageForward![i]["image_id"] as? String ?? "", audio_id: dataMessageForward![i]["audio_id"] as? String ?? "", video_id: dataMessageForward![i]["video_id"] as? String ?? "", file_id: dataMessageForward![i]["file_id"] as? String ?? "", thumb_id: dataMessageForward![i]["thumb_id"] as? String ?? "", reff_id: "", read_receipts: dataMessageForward![i]["read_receipts"] as? String ?? "", chat_id: "", is_call_center: "0", call_center_id: "", viewController: self, gif_id: dataMessageForward![i][TypeDataMessage.gif_id] as? String ?? "", is_forwarded: isForwarded + 1, is_secret: (dataMessageForward![i][TypeDataMessage.is_secret] as? Int) ?? 0)
  308. }
  309. dataMessageForward = nil
  310. }
  311. tableMention.register(UITableViewCell.self, forCellReuseIdentifier: "cellMention")
  312. tableMention.dataSource = self
  313. tableMention.delegate = self
  314. tableMention.contentInset = UIEdgeInsets(top: -25, left: 0, bottom: 0, right: 0)
  315. tableChatView.rowHeight = UITableView.automaticDimension
  316. tableChatView.estimatedRowHeight = 100
  317. if isContactCenter && !isRequestContactCenter && !onGoingCC {
  318. var companyName = ""
  319. Database.shared.database?.inTransaction({ fmdb, rollback in
  320. do {
  321. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT first_name, last_name FROM BUDDY where official_account = '1'"), cursor.next() {
  322. companyName = cursor.string(forColumnIndex: 0)! + " " + cursor.string(forColumnIndex: 1)!
  323. companyName = companyName.trimmingCharacters(in: .whitespaces)
  324. cursor.close()
  325. }
  326. } catch {
  327. rollback.pointee = true
  328. print("Access database error: \(error.localizedDescription)")
  329. }
  330. })
  331. self.dateStartCC = "\(Date().currentTimeMillis())"
  332. let myName = User.getData(pin: User.getMyPin() as String?)
  333. sendChat(message_text: "Hi \(dataPerson["name"]!!), thank you for contacting \(companyName). My name is \(myName!.fullName.trimmingCharacters(in: .whitespaces)), how can I help you?".localized(), ex_format: "1", is_call_center: "1", call_center_id: complaintId, viewController: self, isAutoSendCC: true)
  334. if channelContactCenter == "1" {
  335. if let pin = dataPerson["f_pin"] {
  336. let controller = QmeraAudioViewController()
  337. controller.user = User.getData(pin: pin)
  338. controller.isOutgoing = true
  339. controller.modalPresentationStyle = .overCurrentContext
  340. present(controller, animated: true, completion: nil)
  341. }
  342. } else if channelContactCenter == "2" {
  343. let videoVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
  344. videoVC.dataPerson.append(dataPerson)
  345. self.show(videoVC, sender: nil)
  346. }
  347. }
  348. if isDirectCC {
  349. directCC()
  350. }
  351. // else if isContactCenter {
  352. // let buttonId = UIButton()
  353. // if channelContactCenter == "0" {
  354. // buttonId.tag = 0
  355. // ccAction(sender: buttonId)
  356. // } else if channelContactCenter == "1" {
  357. // buttonId.tag = 1
  358. // ccAction(sender: buttonId)
  359. // } else if channelContactCenter == "2" {
  360. // buttonId.tag = 2
  361. // ccAction(sender: buttonId)
  362. // }
  363. // }
  364. }
  365. public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  366. guard let location = locations.last else { return }
  367. latitude = "\(location.coordinate.latitude)"
  368. longitude = "\(location.coordinate.longitude)"
  369. locationManager.stopUpdatingLocation()
  370. }
  371. public func afterUnfriend() {
  372. DispatchQueue.main.async {
  373. for timer in self.timerCredential.values {
  374. timer.invalidate()
  375. }
  376. self.timeoutCC.invalidate()
  377. SecureUserDefaults.shared.removeValue(forKey: "inEditorPersonal")
  378. NotificationCenter.default.removeObserver(self)
  379. }
  380. }
  381. private func setRightButtonItem() {
  382. navigationItem.rightBarButtonItems = nil
  383. let actionDelete = UIAction(title: "Delete Conversation".localized(), handler: {(_) in
  384. if !self.isContactCenter {
  385. let alert = LibAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
  386. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.default, handler: nil))
  387. alert.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: {(_) in
  388. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  389. do {
  390. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "(f_pin='\(self.dataPerson["f_pin"]!!)' or l_pin='\(self.dataPerson["f_pin"]!!)') and (message_scope_id='\(MessageScope.WHISPER)' or message_scope_id='\(MessageScope.FORM)' or message_scope_id='\(MessageScope.CALL)' or message_scope_id='\(MessageScope.MISSED_CALL)') and is_call_center = 0")
  391. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(self.dataPerson["f_pin"]!!)'")
  392. let l_pin = self.dataPerson["f_pin"]!!
  393. SecureUserDefaults.shared.removeValue(forKey: "new_saved_\(l_pin)")
  394. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  395. if self.fromNotification {
  396. self.didTapExit()
  397. } else {
  398. self.navigationController?.popViewController(animated: true)
  399. }
  400. } catch {
  401. rollback.pointee = true
  402. print("Access database error: \(error.localizedDescription)")
  403. }
  404. })
  405. }))
  406. self.present(alert, animated: true, completion: nil)
  407. }
  408. })
  409. let actionSearch = UIAction(title: "Search".localized(), handler: {(_) in
  410. self.isSearching = true
  411. if self.reffId != nil {
  412. self.deleteReplyView()
  413. }
  414. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  415. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  416. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  417. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  418. self.navigationItem.rightBarButtonItems = nil
  419. }
  420. self.navigationItem.rightBarButtonItem = cancelButton
  421. if self.isContactCenter || self.fromNotification {
  422. self.navigationItem.leftBarButtonItem = nil
  423. }
  424. self.changeAppBar()
  425. self.addMultipleSelectSession()
  426. }
  427. })
  428. let actionUnblock = UIAction(title: "Unblock".localized(), handler: {(_) in
  429. if !self.isContactCenter {
  430. DispatchQueue.global().async {
  431. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: self.dataPerson["f_pin"]!!)) {
  432. if !response.isOk() {
  433. DispatchQueue.main.async {
  434. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  435. imageView.tintColor = .white
  436. let banner = FloatingNotificationBanner(title: "Unable to complete action".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  437. banner.show()
  438. }
  439. } else {
  440. DispatchQueue.main.async { [self] in
  441. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  442. do {
  443. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  444. "ex_block" : "0"
  445. ], _where: "f_pin = '\(self.dataPerson["f_pin"]!!)'")
  446. } catch {
  447. rollback.pointee = true
  448. print("Access database error: \(error.localizedDescription)")
  449. }
  450. })
  451. containerAction.subviews.forEach({ $0.removeFromSuperview() })
  452. containerAction.removeFromSuperview()
  453. setRightButtonItem()
  454. changeAppBar()
  455. }
  456. }
  457. } else {
  458. DispatchQueue.main.async {
  459. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  460. imageView.tintColor = .white
  461. let banner = FloatingNotificationBanner(title: "Unable to access servers".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  462. banner.show()
  463. }
  464. }
  465. }
  466. }
  467. })
  468. let actionBlock = UIAction(title: "Block".localized(), handler: {(_) in
  469. if !self.isContactCenter {
  470. DispatchQueue.global().async {
  471. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: self.dataPerson["f_pin"]!!)) {
  472. if !response.isOk() {
  473. DispatchQueue.main.async {
  474. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  475. imageView.tintColor = .white
  476. let banner = FloatingNotificationBanner(title: "Unable to complete action".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  477. banner.show()
  478. }
  479. } else {
  480. DispatchQueue.main.async { [self] in
  481. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  482. do {
  483. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  484. "ex_block" : "1"
  485. ], _where: "f_pin = '\(self.dataPerson["f_pin"]!!)'")
  486. } catch {
  487. rollback.pointee = true
  488. print("Access database error: \(error.localizedDescription)")
  489. }
  490. })
  491. setRightButtonItem()
  492. changeAppBar()
  493. }
  494. }
  495. } else {
  496. DispatchQueue.main.async {
  497. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  498. imageView.tintColor = .white
  499. let banner = FloatingNotificationBanner(title: "Unable to access servers".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  500. banner.show()
  501. }
  502. }
  503. }
  504. }
  505. })
  506. var menu = UIMenu(title: "", children: [
  507. actionSearch,
  508. actionDelete
  509. ])
  510. let exblock = User.getDataCanNil(pin: self.dataPerson["f_pin"]!!)?.ex_block
  511. blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
  512. if blocking == "1" && self.dataPerson["f_pin"]!! != "-999" && self.dataPerson["isOfficial"]!! != "1" {
  513. menu = UIMenu(title: "", children: [
  514. actionSearch,
  515. actionUnblock,
  516. actionDelete
  517. ])
  518. blockedView(blocked: "1")
  519. } else if blocking == "0" {
  520. if self.dataPerson["f_pin"]!! != "-999" && complaintId.isEmpty && self.dataPerson["isOfficial"]!! != "1" {
  521. menu = UIMenu(title: "", children: [
  522. actionSearch,
  523. actionBlock,
  524. actionDelete
  525. ])
  526. } else if !complaintId.isEmpty{
  527. menu = UIMenu(title: "", children: [
  528. actionSearch
  529. ])
  530. }
  531. else {
  532. menu = UIMenu(title: "", children: [
  533. actionSearch,
  534. actionDelete
  535. ])
  536. }
  537. if containerAction.isDescendant(of: self.view) {
  538. containerAction.subviews.forEach({ $0.removeFromSuperview() })
  539. containerAction.removeFromSuperview()
  540. }
  541. } else {
  542. blockedView(blocked: "-1")
  543. changeAppBar()
  544. }
  545. let moreIcon = UIBarButtonItem(image: UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), menu: menu)
  546. let buttonAudioCall = UIBarButtonItem(image: UIImage(systemName: "phone", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  547. // let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  548. buttonAudioCall.tag = 0
  549. let buttonVideoCall = UIBarButtonItem(image: UIImage(systemName: "video", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  550. buttonVideoCall.tag = 1
  551. let buttonAddRoom = UIBarButtonItem(image: UIImage(systemName: "plus.message", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(addRoom(sender:)))
  552. if dataPerson["f_pin"] != "-999" && !isContactCenter && blocking == "0" && self.dataPerson["isOfficial"]!! != "1" {
  553. navigationItem.rightBarButtonItems = [moreIcon,buttonAudioCall,buttonVideoCall]
  554. } else if !isContactCenter {
  555. navigationItem.rightBarButtonItem = moreIcon
  556. } else if !complaintId.isEmpty { //!complaintId.isEmpty
  557. navigationItem.rightBarButtonItems = [moreIcon,buttonAddRoom]
  558. }
  559. }
  560. func loadData() {
  561. if (unique_l_pin != "" || isContactCenter) {
  562. getDataProfile(fPin: unique_l_pin)
  563. if isContactCenter && !isRequestContactCenter && users.count == 0 {
  564. if !unique_l_pin.isEmpty {
  565. users.append(User.getData(pin: unique_l_pin) ?? User(pin: ""))
  566. }
  567. }
  568. }
  569. let pinPerson: String = (dataPerson["f_pin"] ?? "") ?? ""
  570. if onGoingCC {
  571. SecureUserDefaults.shared.set(self.fPinContacCenter, forKey: "inEditorPersonal")
  572. } else {
  573. SecureUserDefaults.shared.set(pinPerson, forKey: "inEditorPersonal")
  574. }
  575. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [pinPerson])
  576. if isContactCenter || fromNotification {
  577. let imageButton = UIImageView(frame: CGRect(x: -16, y: 0, width: 20, height: 44))
  578. imageButton.image = UIImage(systemName: "chevron.backward", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default))?.withTintColor(.white)
  579. imageButton.contentMode = .left
  580. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapExit))
  581. imageButton.isUserInteractionEnabled = true
  582. imageButton.addGestureRecognizer(tapGestureRecognizer)
  583. let leftItem = UIBarButtonItem(customView: imageButton)
  584. self.navigationItem.leftBarButtonItem = leftItem
  585. }
  586. if dataPerson["f_pin"] == "-999" {
  587. chatbot()
  588. }
  589. let dataUser = User.getData(pin: pinPerson)
  590. if dataUser == nil {
  591. blocking = "0"
  592. } else {
  593. let exblock = dataUser!.ex_block
  594. blocking = exblock!.isEmpty ? "0" : exblock!
  595. }
  596. changeAppBar()
  597. getData()
  598. tableChatView.alpha = 0
  599. tableChatView.delegate = self
  600. tableChatView.dataSource = self
  601. tableChatView.keyboardDismissMode = .interactive
  602. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  603. tapGesture.cancelsTouchesInView = false
  604. tableChatView.addGestureRecognizer(tapGesture)
  605. if !isContactCenter {
  606. getCounter()
  607. if counter > 0 && dataMessages.count >= counter {
  608. markerCounter = dataMessages[dataMessages.count - counter]["message_id"] as? String
  609. }
  610. if !referenceMessageId.isEmpty {
  611. if dataMessages.firstIndex(where: {$0["message_id"] as? String == referenceMessageId} ) != nil {
  612. DispatchQueue.main.async {
  613. if self.referenceChatDate.isEmpty {
  614. self.referenceChatDate = self.chatDate(stringDate: self.dataMessages[self.dataMessages.firstIndex(where: {$0["message_id"] as? String == self.referenceMessageId} )!][TypeDataMessage.server_date] as! String)
  615. }
  616. let section = self.dataDates.firstIndex(of: self.referenceChatDate)
  617. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.referenceChatDate}).firstIndex(where: { $0["message_id"] as? String == self.referenceMessageId})
  618. if row != nil && section != nil {
  619. let indexPath = IndexPath(row: row!, section: section!)
  620. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: false)
  621. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .yellow
  622. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  623. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .clear
  624. })
  625. }
  626. }
  627. }
  628. } else if counter != 0 && dataMessages.count >= counter {
  629. if dataMessages.firstIndex(where: {$0["message_id"] as? String == markerCounter} ) != nil {
  630. DispatchQueue.main.async {
  631. let data = self.dataMessages.filter({ $0["message_id"] as? String == self.markerCounter })
  632. if data.count > 0 {
  633. let section = self.dataDates.firstIndex(of: data[0]["chat_date"] as? String ?? "")
  634. 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})
  635. self.tableChatView.scrollToRow(at: IndexPath(row: row!, section: section!), at: .bottom, animated: false)
  636. }
  637. }
  638. } else {
  639. tableChatView.scrollToTop()
  640. }
  641. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in
  642. if currentIndexpath == nil && counter != 0 {
  643. let idMe = User.getMyPin() as String?
  644. if let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == markerCounter}) {
  645. for i in idx..<dataMessages.count {
  646. if dataMessages[i]["f_pin"] as? String != idMe {
  647. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: dataMessages[i]["message_id"] as? String ?? "")
  648. }
  649. }
  650. counter = 0
  651. updateCounter(counter: counter)
  652. }
  653. }
  654. }
  655. } else {
  656. let l_pin = self.dataPerson["f_pin"] as? String ?? ""
  657. if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
  658. let data = dataSaved
  659. if let jsonData = data.data(using: .utf8),
  660. let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
  661. let last_m = dataJson["text"] ?? ""
  662. let last_r = dataJson["reffId"] ?? ""
  663. let list_m = dataJson["list_mention"] as? [[String: String]] ?? []
  664. if list_m.count > 0 {
  665. for list in list_m {
  666. let f_pin = list["f_pin_mention"] ?? ""
  667. let upper = list["upper"] ?? ""
  668. let userFromBuddy = User.getData(pin: f_pin, lPin: l_pin)
  669. if userFromBuddy != nil {
  670. userFromBuddy!.ex_block = upper
  671. listMentionInTextField.append(userFromBuddy!)
  672. }
  673. }
  674. }
  675. if !last_m.isEmpty {
  676. textFieldSend.attributedText = last_m.richText(isEditing: true, listMentionInTextField: listMentionInTextField)
  677. textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  678. }
  679. if !last_r.isEmpty {
  680. handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
  681. }
  682. }
  683. }
  684. tableChatView.scrollToBottom(isAnimated: false)
  685. }
  686. } else if isContactCenter && onGoingCC {
  687. let idMe = User.getMyPin() as String?
  688. for i in 0..<dataMessages.count {
  689. if dataMessages[i]["f_pin"] as? String != idMe {
  690. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: dataMessages[i]["message_id"] as? String ?? "")
  691. }
  692. }
  693. tableChatView.scrollToBottom(isAnimated: false)
  694. } else {
  695. tableChatView.scrollToBottom(isAnimated: false)
  696. }
  697. if tableChatView.alpha != 1 {
  698. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  699. UIView.animate(withDuration: 0.5, animations: {
  700. self.tableChatView.alpha = 1.0
  701. })
  702. })
  703. }
  704. for data in listTimerCredential {
  705. if data.value > 0 {
  706. var second = data.value
  707. var timer = Timer()
  708. timerCredential[data.key] = timer
  709. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  710. second -= 1
  711. self.listTimerCredential[data.key] = second
  712. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key })
  713. if (idx != nil) {
  714. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  715. 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 })
  716. if second == 0 {
  717. timer.invalidate()
  718. self.listTimerCredential.removeValue(forKey: data.key)
  719. self.timerCredential.removeValue(forKey: data.key)
  720. SecureUserDefaults.shared.removeValue(forKey: data.key)
  721. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key})
  722. if idx != nil {
  723. self.dataMessages[idx!]["lock"] = "2"
  724. self.dataMessages[idx!]["reff_id"] = ""
  725. }
  726. DispatchQueue.global().async {
  727. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  728. do {
  729. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  730. "lock" : "2"
  731. ], _where: "message_id = '\(data.key)'")
  732. } catch {
  733. rollback.pointee = true
  734. print("Access database error: \(error.localizedDescription)")
  735. }
  736. })
  737. }
  738. }
  739. if row != nil && section != nil {
  740. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  741. }
  742. }
  743. })
  744. }
  745. }
  746. let dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  747. pinAllMessages(dataMessages: dataMessagesPin)
  748. }
  749. private func chatbot() {
  750. let containerChatbot = UIView()
  751. self.view.addSubview(containerChatbot)
  752. containerChatbot.translatesAutoresizingMaskIntoConstraints = false
  753. NSLayoutConstraint.activate([
  754. containerChatbot.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  755. containerChatbot.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  756. containerChatbot.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  757. containerChatbot.heightAnchor.constraint(equalToConstant: 120)
  758. ])
  759. containerChatbot.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  760. let labelChatbot = UILabel()
  761. containerChatbot.addSubview(labelChatbot)
  762. labelChatbot.translatesAutoresizingMaskIntoConstraints = false
  763. NSLayoutConstraint.activate([
  764. labelChatbot.centerYAnchor.constraint(equalTo: containerChatbot.centerYAnchor),
  765. labelChatbot.centerXAnchor.constraint(equalTo: containerChatbot.centerXAnchor),
  766. ])
  767. labelChatbot.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  768. labelChatbot.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  769. labelChatbot.text = "Interactive chatbot. Coming soon".localized()
  770. }
  771. private func changeAppBar() {
  772. let viewAppBar = UIView()
  773. viewAppBar.frame.size = CGSize(width: self.view.frame.size.width, height: 44)
  774. if !isSearching {
  775. let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  776. imageProfile.circle()
  777. imageProfile.clipsToBounds = true
  778. let pictureImage = dataPerson["picture"]!
  779. var count = 0
  780. if isContactCenter {
  781. if fPinContacCenter.isEmpty && isRequestContactCenter {
  782. getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  783. imageProfile.image = image
  784. }
  785. viewAppBar.addSubview(imageProfile)
  786. } else {
  787. if users.count == 1 {
  788. viewAppBar.addSubview(imageProfile)
  789. getImage(name: users[0].thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  790. imageProfile.image = image
  791. imageProfile.contentMode = .scaleAspectFit
  792. }
  793. } else {
  794. for user in users {
  795. if count == 3 {
  796. count += 1
  797. continue
  798. }
  799. if count == 0 {
  800. let pictures = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  801. pictures.circle()
  802. pictures.clipsToBounds = true
  803. viewAppBar.addSubview(pictures)
  804. getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  805. pictures.image = image
  806. pictures.contentMode = .scaleAspectFit
  807. }
  808. } else {
  809. let pictures = UIImageView(frame: CGRect(x: count * 20 , y: 7, width: 30, height: 30))
  810. pictures.circle()
  811. pictures.clipsToBounds = true
  812. viewAppBar.addSubview(pictures)
  813. getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  814. pictures.image = image
  815. pictures.contentMode = .scaleAspectFit
  816. }
  817. }
  818. count += 1
  819. }
  820. }
  821. }
  822. } else if dataPerson["f_pin"]!! == "-999" {
  823. viewAppBar.addSubview(imageProfile)
  824. if !Utils.getIconDock().isEmpty {
  825. let urlString = Utils.getUrlDock()!
  826. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  827. let imageData = cachedImage
  828. imageProfile.image = imageData
  829. } else {
  830. DispatchQueue.global().async{
  831. Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
  832. guard let data = data, error == nil else { return }
  833. DispatchQueue.main.async() {
  834. if UIImage(data: data) != nil {
  835. let imageData = UIImage(data: data)!
  836. imageProfile.image = imageData
  837. ImageCache.shared.save(image: imageData, forKey: urlString)
  838. }
  839. }
  840. }
  841. }
  842. }
  843. } else {
  844. imageProfile.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  845. }
  846. imageProfile.contentMode = .scaleAspectFit
  847. }
  848. else if (pictureImage != "" && pictureImage != nil) {
  849. viewAppBar.addSubview(imageProfile)
  850. imageProfile.setImage(name: pictureImage!)
  851. imageProfile.contentMode = .scaleAspectFill
  852. } else {
  853. viewAppBar.addSubview(imageProfile)
  854. imageProfile.image = UIImage(systemName: "person")
  855. imageProfile.contentMode = .scaleAspectFit
  856. imageProfile.backgroundColor = .lightGray
  857. }
  858. var titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 250, height: 44))
  859. if blocking == "-1" || blocking == "1" {
  860. titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
  861. } else if isContactCenter {
  862. titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
  863. if users.count > 0 {
  864. titleNavigation = UILabel(frame: CGRect(x: 35 * (CGFloat(users.count)) - (CGFloat((users.count - 1) * 15)), y: 0, width: viewAppBar.frame.size.width - 150 - (35 * (CGFloat(users.count - 1)) - (CGFloat((users.count - 1) * 15))), height: 44))
  865. }
  866. }
  867. viewAppBar.addSubview(titleNavigation)
  868. if ((User.isOfficial(official_account: (dataPerson["isOfficial"] ?? "")!) || User.isOfficialRegular(official_account: (dataPerson["isOfficial"] ?? "")!)) && !isContactCenter) || ((User.isOfficial(official_account: (dataPerson["isOfficial"] ?? "")!) || User.isOfficialRegular(official_account: (dataPerson["isOfficial"] ?? "")!)) && fPinContacCenter.isEmpty) {
  869. var name = dataPerson["name"]!!
  870. if (isContactCenter) {
  871. name = name + " " + "Contact Center".localized()
  872. titleNavigation.text = name
  873. } else {
  874. titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  875. }
  876. } else if User.isVerified(official_account: (dataPerson["isOfficial"] ?? "")!) && !isContactCenter {
  877. let name = dataPerson["name"]!!
  878. titleNavigation.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  879. } else if User.isInternal(userType: (dataPerson["user_type"] ?? "")!) && !isContactCenter {
  880. let name = dataPerson["name"]!!
  881. titleNavigation.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  882. } else {
  883. if !isContactCenter {
  884. titleNavigation.text = dataPerson["name"] as? String
  885. } else {
  886. if users.count == 1 {
  887. titleNavigation.text = users[0].fullName
  888. } else {
  889. var stringName = ""
  890. for user in users {
  891. if stringName.isEmpty {
  892. stringName = user.fullName
  893. } else {
  894. stringName += ", \(user.fullName)"
  895. }
  896. }
  897. titleNavigation.text = stringName
  898. }
  899. }
  900. }
  901. titleNavigation.textColor = .white
  902. titleNavigation.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  903. navigationItem.titleView = viewAppBar
  904. titleText = titleNavigation.text
  905. } else {
  906. searchBar = UISearchBar()
  907. searchBar.autocapitalizationType = .none
  908. searchBar.delegate = self
  909. searchBar.searchTextField.tintColor = .mainColor
  910. searchBar.searchTextField.textColor = .mainColor
  911. searchBar.showsCancelButton = false
  912. // searchBar.setMagnifyingGlassColorTo(color: .white)
  913. // searchBar.updateHeight(height: 36, radius: 18)
  914. searchBar.setImage(UIImage(), for: .search, state: .normal)
  915. searchBar.setPositionAdjustment(UIOffset(horizontal: 10, vertical: 0), for: .search)
  916. searchBar.setCustomBackgroundImage(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
  917. navigationItem.titleView = searchBar
  918. self.definesPresentationContext = true
  919. }
  920. if copySession || forwardSession || deleteSession || isSearching {
  921. navigationItem.hidesBackButton = true
  922. navigationController?.interactivePopGestureRecognizer?.isEnabled = false
  923. } else {
  924. navigationItem.hidesBackButton = false
  925. navigationController?.interactivePopGestureRecognizer?.isEnabled = true
  926. }
  927. viewAppBar.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(seeProfileTapped)))
  928. }
  929. private func getDataProfile(fPin: String) {
  930. var query = "SELECT f_pin, first_name || ' ' || last_name, official_account, image_id, device_id, offline_mode, user_type FROM BUDDY where f_pin = '\(fPin)'"
  931. if (isContactCenter && isRequestContactCenter) {
  932. query = "SELECT group_id, f_name, official, image_id FROM GROUPZ where group_type = 1 AND official = 1"
  933. }
  934. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  935. do {
  936. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  937. if cursorData.next() {
  938. dataPerson["f_pin"] = cursorData.string(forColumnIndex: 0) ?? ""
  939. dataPerson["name"] = (cursorData.string(forColumnIndex: 1) ?? "").trimmingCharacters(in: .whitespaces)
  940. dataPerson["picture"] = cursorData.string(forColumnIndex: 3) ?? ""
  941. dataPerson["isOfficial"] = cursorData.string(forColumnIndex: 2) ?? ""
  942. if isContactCenter && isRequestContactCenter {
  943. dataPerson["user_type"] = "0"
  944. } else {
  945. dataPerson["user_type"] = cursorData.string(forColumnIndex: 6) ?? ""
  946. }
  947. } else {
  948. dataPerson["f_pin"] = "-999"
  949. dataPerson["name"] = "Bot"
  950. dataPerson["picture"] = ""
  951. dataPerson["isOfficial"] = ""
  952. dataPerson["deviceId"] = ""
  953. dataPerson["isOffline"] = "0"
  954. dataPerson["user_type"] = "0"
  955. }
  956. cursorData.close()
  957. }
  958. } catch {
  959. rollback.pointee = true
  960. print("Access database error: \(error.localizedDescription)")
  961. }
  962. })
  963. }
  964. private func addDataMessage() {
  965. multipleOffsetUp += 1
  966. let queryCount = "SELECT COUNT(*) FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0"
  967. let query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin, last_edited, gif_id, is_forwarded_message, attachment_speciality, is_pinned, is_bot FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20*\(multipleOffsetUp-1) ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
  968. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  969. do {
  970. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  971. var tempData: [[String: Any?]] = []
  972. while cursorData.next() {
  973. var row: [String: Any?] = [:]
  974. row["message_id"] = cursorData.string(forColumnIndex: 0)
  975. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  976. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  977. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  978. row["server_date"] = cursorData.string(forColumnIndex: 4)
  979. row["status"] = cursorData.string(forColumnIndex: 5)
  980. row["message_text"] = cursorData.string(forColumnIndex: 6)
  981. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  982. row["video_id"] = cursorData.string(forColumnIndex: 8)
  983. row["image_id"] = cursorData.string(forColumnIndex: 9)
  984. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  985. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  986. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  987. row["file_id"] = cursorData.string(forColumnIndex: 13)
  988. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  989. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  990. row["lock"] = cursorData.string(forColumnIndex: 16)
  991. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  992. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  993. row["credential"] = cursorData.string(forColumnIndex: 19)
  994. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  995. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  996. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  997. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  998. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  999. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  1000. row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 26)
  1001. row[TypeDataMessage.is_pinned] = cursorData.string(forColumnIndex: 27)
  1002. row[TypeDataMessage.is_bot] = cursorData.string(forColumnIndex: 28)
  1003. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
  1004. while cursorStatus.next() {
  1005. row["status"] = cursorStatus.string(forColumnIndex: 0)
  1006. }
  1007. cursorStatus.close()
  1008. }
  1009. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  1010. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  1011. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  1012. if let dirPath = paths.first {
  1013. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  1014. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  1015. if ((row["video_id"] as? String ?? "") != "") {
  1016. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
  1017. row["progress"] = 100.0
  1018. } else {
  1019. row["progress"] = 0.0
  1020. }
  1021. } else {
  1022. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent){
  1023. row["progress"] = 100.0
  1024. } else {
  1025. row["progress"] = 0.0
  1026. }
  1027. }
  1028. }
  1029. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  1030. row["isSelected"] = false
  1031. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1032. let idMe = User.getMyPin()!
  1033. if row["f_pin"] as? String ?? "" == idMe {
  1034. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  1035. if second > 60 {
  1036. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1037. row["lock"] = "2"
  1038. row["reff_id"] = ""
  1039. DispatchQueue.global().async {
  1040. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1041. do {
  1042. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1043. "lock" : "2"
  1044. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1045. } catch {
  1046. rollback.pointee = true
  1047. print("Access database error: \(error.localizedDescription)")
  1048. }
  1049. })
  1050. }
  1051. } else {
  1052. let second = 60 - second
  1053. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1054. }
  1055. } else {
  1056. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  1057. if hasMessageId != nil {
  1058. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  1059. if second > 60 {
  1060. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1061. row["lock"] = "2"
  1062. row["reff_id"] = ""
  1063. DispatchQueue.global().async {
  1064. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1065. do {
  1066. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1067. "lock" : "2"
  1068. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1069. } catch {
  1070. rollback.pointee = true
  1071. print("Access database error: \(error.localizedDescription)")
  1072. }
  1073. })
  1074. }
  1075. } else {
  1076. let second = 60 - second
  1077. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1078. }
  1079. } else {
  1080. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1081. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1082. }
  1083. }
  1084. }
  1085. tempData.append(row)
  1086. }
  1087. if tempData.count != 0 && (dataMessages.firstIndex(where: { $0["message_id"] as? String == tempData[0]["message_id"] as? String }) == nil) {
  1088. let lastIndex = tempData.count - 1
  1089. for i in 0..<tempData.count {
  1090. self.tableChatView.beginUpdates()
  1091. dataMessages.insert(tempData[lastIndex - i], at: 0)
  1092. if dataMessages.firstIndex(where: { $0["chat_date"] as? String == tempData[lastIndex - i]["chat_date"] as? String }) != nil {
  1093. tableChatView.insertRows(at: [IndexPath(row: 0, section: currentIndexpath!.section)], with: .top)
  1094. } else {
  1095. tableChatView.insertSections(IndexSet(integer: 0), with: .top)
  1096. tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
  1097. }
  1098. self.tableChatView.endUpdates()
  1099. }
  1100. }
  1101. cursorData.close()
  1102. gettingDataMessage = false
  1103. }
  1104. } catch {
  1105. rollback.pointee = true
  1106. print("Access database error: \(error.localizedDescription)")
  1107. }
  1108. })
  1109. }
  1110. private func getData(offset: Int64 = 0) {
  1111. // let queryCount = "SELECT COUNT(*) FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0"
  1112. // var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '3' OR message_scope_id = '18') AND is_call_center = 0 order by server_date asc LIMIT CASE WHEN (\(queryCount))-\(dataMessages.count)>=20 THEN 20 ELSE (\(queryCount))-\(dataMessages.count) END OFFSET CASE WHEN (\(queryCount))>=\(20*multipleOffsetUp) THEN (\(queryCount))-\(20*multipleOffsetUp) ELSE 0 END"
  1113. var query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin, last_edited, gif_id, is_forwarded_message, attachment_speciality, is_pinned, is_bot FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 order by server_date asc LIMIT -1 OFFSET \(offset)"
  1114. if isContactCenter {
  1115. if complaintId.isEmpty {
  1116. query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND message_scope_id = '\(MessageScope.CHATROOM)' AND broadcast_flag = 0 AND is_call_center = 1 order by server_date asc LIMIT -1 OFFSET \(offset)"
  1117. } else {
  1118. query = "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared FROM MESSAGE where message_scope_id = '\(MessageScope.CHATROOM)' AND broadcast_flag = 0 AND is_call_center = 1 AND call_center_id = '\(complaintId)' order by server_date asc LIMIT -1 OFFSET \(offset)"
  1119. }
  1120. if isRequestContactCenter && !isDirectCC {
  1121. viewButton.isHidden = true
  1122. viewTextfield.isHidden = true
  1123. var row: [String: Any?] = [:]
  1124. row["f_pin"] = nil
  1125. row["message_id"] = ""
  1126. row["chat_date"] = "Today".localized()
  1127. let listStringName: [String] = ["Messaging".localized(), "Secure SMS".localized(), "VoIP Call".localized(), "Email".localized(), "Video Call".localized(), "GSM Call".localized(), "GPT Chatbot".localized(), "WhatsApp"]
  1128. var data : [CategoryCC] = []
  1129. let channels : [String] = ["0", "4", "1", "3", "2", "5", "7", "6"]
  1130. if Utils.getDefaultCC() == "No" {
  1131. let category = CategoryCC.getDatafromParent(parent: CategoryCC.default_parent)
  1132. for i in 0..<category.count {
  1133. data.append(CategoryCC(id: "level0_\(i)", service_id: category[i].service_id, service_name: category[i].service_name, parent: category[i].parent, description: category[i].description, is_tablet: "0"))
  1134. }
  1135. } else {
  1136. for i in 0..<listStringName.count {
  1137. data.append(CategoryCC(id: "level0_\(channels[i])", service_id: "", service_name: listStringName[i], parent: "\(i)", description: "", is_tablet: "0"))
  1138. }
  1139. row["attachment_flag"] = "503"
  1140. }
  1141. row["category_cc"] = data
  1142. dataDates.append("Today".localized())
  1143. dataMessages.append(row)
  1144. } else if isDirectCC {
  1145. dataDates.append("Today".localized())
  1146. }
  1147. }
  1148. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1149. do {
  1150. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  1151. var tempImages: [ImageGrouping] = []
  1152. var idxOff = 0
  1153. while cursorData.next() {
  1154. var row: [String: Any?] = [:]
  1155. row["message_id"] = cursorData.string(forColumnIndex: 0)
  1156. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  1157. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  1158. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  1159. row["server_date"] = cursorData.string(forColumnIndex: 4)
  1160. row["status"] = cursorData.string(forColumnIndex: 5)
  1161. row["message_text"] = cursorData.string(forColumnIndex: 6)
  1162. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  1163. row["video_id"] = cursorData.string(forColumnIndex: 8)
  1164. row["image_id"] = cursorData.string(forColumnIndex: 9)
  1165. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  1166. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  1167. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  1168. row["file_id"] = cursorData.string(forColumnIndex: 13)
  1169. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  1170. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  1171. row["lock"] = cursorData.string(forColumnIndex: 16)
  1172. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  1173. row["blog_id"] = cursorData.string(forColumnIndex: 18) ?? ""
  1174. row["credential"] = cursorData.string(forColumnIndex: 19) ?? ""
  1175. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20) ?? ""
  1176. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21) ?? ""
  1177. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22) ?? ""
  1178. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  1179. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24) ?? ""
  1180. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  1181. row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 26) ?? ""
  1182. row[TypeDataMessage.is_pinned] = cursorData.string(forColumnIndex: 27) ?? ""
  1183. row[TypeDataMessage.is_bot] = Int (cursorData.string(forColumnIndex: 28) ?? "0")
  1184. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
  1185. while cursorStatus.next() {
  1186. row["status"] = cursorStatus.string(forColumnIndex: 0)
  1187. }
  1188. cursorStatus.close()
  1189. }
  1190. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  1191. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  1192. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  1193. if let dirPath = paths.first {
  1194. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  1195. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  1196. if ((row["video_id"] as? String ?? "") != "") {
  1197. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
  1198. row["progress"] = 100.0
  1199. } else {
  1200. row["progress"] = 0.0
  1201. }
  1202. } else {
  1203. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent){
  1204. row["progress"] = 100.0
  1205. } else {
  1206. row["progress"] = 0.0
  1207. }
  1208. }
  1209. }
  1210. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  1211. row["isSelected"] = false
  1212. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1213. let idMe = User.getMyPin()!
  1214. if row["f_pin"] as? String ?? "" == idMe {
  1215. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  1216. if second > 60 {
  1217. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1218. row["lock"] = "2"
  1219. row["reff_id"] = ""
  1220. DispatchQueue.global().async {
  1221. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1222. do {
  1223. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1224. "lock" : "2"
  1225. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1226. } catch {
  1227. rollback.pointee = true
  1228. print("Access database error: \(error.localizedDescription)")
  1229. }
  1230. })
  1231. }
  1232. } else {
  1233. let second = 60 - second
  1234. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1235. }
  1236. } else {
  1237. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  1238. if hasMessageId != nil {
  1239. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  1240. if second > 60 {
  1241. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1242. row["lock"] = "2"
  1243. row["reff_id"] = ""
  1244. DispatchQueue.global().async {
  1245. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1246. do {
  1247. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1248. "lock" : "2"
  1249. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1250. } catch {
  1251. rollback.pointee = true
  1252. print("Access database error: \(error.localizedDescription)")
  1253. }
  1254. })
  1255. }
  1256. } else {
  1257. let second = 60 - second
  1258. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1259. }
  1260. } else {
  1261. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1262. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1263. }
  1264. }
  1265. }
  1266. if (dataMessages.count == 0 || dataMessages.last!["f_pin"] as? String ?? "" == row["f_pin"] as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["message_text"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["reff_id"] as? String ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (row["credential"] as? String ?? "") != "1" && (row["read_receipts"] as? String ?? "") != "8" {
  1267. 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 {
  1268. if tempImages.count >= 4 {
  1269. groupImages[tempImages[0].messageId] = tempImages
  1270. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1271. for _ in 1..<tempImages.count {
  1272. dataMessages.remove(at: idxTemp + 1)
  1273. }
  1274. }
  1275. }
  1276. tempImages.removeAll()
  1277. }
  1278. tempImages.append(ImageGrouping(messageId: row["message_id"] as? String ?? "", thumbId: row["thumb_id"] as? String ?? "", imageId: row["image_id"] as? String ?? "", status: row["status"] as? String ?? "", time: row["server_date"] as? String ?? "", lPin: row["l_pin"] as? String ?? "", dataMessage: row, dataPerson: dataPerson, dataGroup: [:], dataTopic: [:]))
  1279. } else if tempImages.count >= 4 {
  1280. groupImages[tempImages[0].messageId] = tempImages
  1281. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1282. for _ in 1..<tempImages.count {
  1283. dataMessages.remove(at: idxTemp + 1)
  1284. }
  1285. }
  1286. tempImages.removeAll()
  1287. } else if tempImages.count != 0 {
  1288. tempImages.removeAll()
  1289. }
  1290. if offset > 0 && idxOff == 0 {
  1291. self.markerCounter = row["message_id"] as? String
  1292. }
  1293. dataMessages.append(row)
  1294. idxOff+=1
  1295. }
  1296. if tempImages.count >= 4 {
  1297. if tempImages.count > 30 {
  1298. tempImages.removeSubrange(30..<tempImages.count)
  1299. }
  1300. groupImages[tempImages[0].messageId] = tempImages
  1301. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1302. for _ in 1..<tempImages.count {
  1303. dataMessages.remove(at: idxTemp + 1)
  1304. }
  1305. }
  1306. }
  1307. cursorData.close()
  1308. }
  1309. } catch {
  1310. rollback.pointee = true
  1311. print("Access database error: \(error.localizedDescription)")
  1312. }
  1313. })
  1314. }
  1315. func getSecondsDifferenceFromTwoDates(start: Date, end: Date) -> Int {
  1316. let diff = Int(end.timeIntervalSince1970 - start.timeIntervalSince1970)
  1317. let hours = diff / 3600
  1318. let seconds = (diff - hours * 3600)
  1319. return seconds
  1320. }
  1321. func chatDate(stringDate: String) -> String {
  1322. let date = Date(milliseconds: Int64(stringDate)!)
  1323. let calendar = Calendar.current
  1324. if (calendar.isDateInToday(date)) {
  1325. if !dataDates.contains("Today".localized()){
  1326. dataDates.append("Today".localized())
  1327. }
  1328. return "Today".localized()
  1329. } else {
  1330. let startOfNow = calendar.startOfDay(for: Date())
  1331. let startOfTimeStamp = calendar.startOfDay(for: date)
  1332. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  1333. let day = -(components.day!)
  1334. if day == 1{
  1335. if !dataDates.contains("Yesterday".localized()){
  1336. dataDates.append("Yesterday".localized())
  1337. }
  1338. return "Yesterday".localized()
  1339. } else if day < 7 {
  1340. let formatter = DateFormatter()
  1341. formatter.dateFormat = "EEEE"
  1342. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  1343. if lang == "id" {
  1344. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1345. }
  1346. if !dataDates.contains(formatter.string(from: date)){
  1347. dataDates.append(formatter.string(from: date))
  1348. }
  1349. return formatter.string(from: date)
  1350. } else {
  1351. let formatter = DateFormatter()
  1352. formatter.dateFormat = "EE, dd MMM"
  1353. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  1354. if lang == "id" {
  1355. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1356. }
  1357. let stringFormat = formatter.string(from: date as Date)
  1358. if !dataDates.contains(stringFormat){
  1359. dataDates.append(stringFormat)
  1360. }
  1361. return stringFormat
  1362. }
  1363. }
  1364. }
  1365. func updateProgress(_ data: [AnyHashable : Any]){
  1366. var isImage = false
  1367. var idx = dataMessages.lastIndex(where: { $0["video_id"] as? String == data["name"] as? String || $0["video_id"] as? String == data["video_id"] as? String })
  1368. if (idx == nil) {
  1369. idx = dataMessages.lastIndex(where: { $0["image_id"] as? String == data["name"] as? String || $0["image_id"] as? String == data["image_id"] as? String })
  1370. isImage = true
  1371. }
  1372. if (idx != nil) {
  1373. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  1374. if section == nil {
  1375. return
  1376. }
  1377. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == dataMessages[idx!]["message_id"] as? String})
  1378. if row == nil {
  1379. return
  1380. }
  1381. DispatchQueue.main.async {
  1382. let indexPath = IndexPath(row: row!, section: section!)
  1383. if(self.fakeProgMultip < self.maxFakeProgMultip){
  1384. self.fakeProgMultip = self.fakeProgMultip + 1
  1385. }
  1386. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  1387. let progress = max(data["progress"] as! Double, fakeProgress)
  1388. if(data["progress"] as! Double == 100.0){
  1389. self.fakeProgMultip = 0
  1390. }
  1391. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1392. for view in cell.contentView.subviews {
  1393. if !(view is UITextView) && !(view is UIImageView) {
  1394. for viewInContainer in view.subviews {
  1395. if viewInContainer is UIImageView {
  1396. if viewInContainer.subviews.count == 0 {
  1397. return
  1398. }
  1399. var containerView : UIView?
  1400. if (isImage) {
  1401. containerView = viewInContainer.subviews[0]
  1402. } else if viewInContainer.subviews.count > 1 {
  1403. containerView = viewInContainer.subviews[1]
  1404. }
  1405. if let loading = containerView?.layer.sublayers?[1] as? CAShapeLayer {
  1406. loading.strokeEnd = CGFloat(progress / 100)
  1407. if (progress == 100.0) {
  1408. self.dataMessages[idx!]["progress"] = progress
  1409. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1410. }
  1411. }
  1412. }
  1413. }
  1414. }
  1415. }
  1416. }
  1417. }
  1418. } else {
  1419. idx = dataMessages.lastIndex(where: { $0["file_id"] as? String == data["name"] as? String || $0["file_id"] as? String == data["file_id"] as? String })
  1420. if (idx != nil) {
  1421. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  1422. if section == nil {
  1423. return
  1424. }
  1425. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == dataMessages[idx!]["message_id"] as? String})
  1426. if row == nil {
  1427. return
  1428. }
  1429. DispatchQueue.main.async {
  1430. let indexPath = IndexPath(row: row!, section: section!)
  1431. if(self.fakeProgMultip < self.maxFakeProgMultip){
  1432. self.fakeProgMultip = self.fakeProgMultip + 1
  1433. }
  1434. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  1435. let progress = max(data["progress"] as! Double, fakeProgress)
  1436. if(data["progress"] as! Double == 100.0){
  1437. self.fakeProgMultip = 0
  1438. }
  1439. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1440. for view in cell.contentView.subviews {
  1441. if !(view is UITextView) && !(view is UIImageView) {
  1442. for viewSubviews in view.subviews {
  1443. if !(viewSubviews is UITextView) {
  1444. for viewInContainer in viewSubviews.subviews {
  1445. if !(viewInContainer is UITextView) && !(viewInContainer is UIImageView) {
  1446. if let cont = viewInContainer.layer.sublayers {
  1447. if cont.count < 2 {
  1448. return
  1449. }
  1450. }
  1451. if let layers = viewInContainer.layer.sublayers {
  1452. if let loading = layers [1] as? CAShapeLayer {
  1453. loading.strokeEnd = CGFloat(progress / 100)
  1454. if (progress == 100.0) {
  1455. self.dataMessages[idx!]["progress"] = progress
  1456. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1457. }
  1458. }
  1459. }
  1460. }
  1461. }
  1462. }
  1463. }
  1464. }
  1465. }
  1466. }
  1467. }
  1468. }
  1469. }
  1470. }
  1471. @objc func onUploadChat(notification: NSNotification) {
  1472. let data:[AnyHashable : Any] = notification.userInfo!
  1473. updateProgress(data)
  1474. }
  1475. @objc func onCheckNewMessages(notification: NSNotification) {
  1476. var query = "SELECT COUNT(*) AS total FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND (message_scope_id = '\(MessageScope.WHISPER)' OR message_scope_id = '\(MessageScope.FORM)' OR message_scope_id = '\(MessageScope.CALL)' OR message_scope_id = '\(MessageScope.MISSED_CALL)') AND is_call_center = 0 order by server_date asc"
  1477. if isContactCenter {
  1478. if complaintId.isEmpty {
  1479. query = "SELECT COUNT(*) AS total FROM MESSAGE where (f_pin='\(dataPerson["f_pin"]!!)' or l_pin='\(dataPerson["f_pin"]!!)') AND message_scope_id = '\(MessageScope.CHATROOM)' AND broadcast_flag = 0 AND is_call_center = 1 order by server_date asc"
  1480. } else {
  1481. query = "SELECT COUNT(*) AS total FROM MESSAGE where message_scope_id = '\(MessageScope.CHATROOM)' AND broadcast_flag = 0 AND is_call_center = 1 AND call_center_id = '\(complaintId)' order by server_date asc"
  1482. }
  1483. }
  1484. var countMessagesNow: Int64 = 0
  1485. DispatchQueue.main.async { [self] in
  1486. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1487. do {
  1488. if let cursorCount = Database.shared.getRecords(fmdb: fmdb, query: query), cursorCount.next() {
  1489. countMessagesNow = Int64(cursorCount.int(forColumnIndex: 0))
  1490. cursorCount.close()
  1491. }
  1492. }catch{}
  1493. })
  1494. if dataMessages.count < countMessagesNow {
  1495. self.counter = Int(countMessagesNow) - dataMessages.count
  1496. getData(offset: Int64(self.dataMessages.count))
  1497. tableChatView.reloadData()
  1498. if !self.indicatorCounterBSTB.isDescendant(of: self.view) && !self.buttonScrollToBottom.isDescendant(of: self.view) {
  1499. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.markerCounter })
  1500. if indexMessage != nil {
  1501. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1502. 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 })
  1503. self.tableChatView.scrollToRow(at: IndexPath(row: row!, section: section!), at: .top, animated: true)
  1504. }
  1505. } else if self.buttonScrollToBottom.isDescendant(of: self.view) {
  1506. if !self.indicatorCounterBSTB.isDescendant(of: self.view) {
  1507. addCounterAtButttonScrollToBottom()
  1508. } else {
  1509. self.labelCounter.text = "\(counter)"
  1510. }
  1511. } else {
  1512. addButtonScrollToBottom()
  1513. addCounterAtButttonScrollToBottom()
  1514. }
  1515. }
  1516. }
  1517. }
  1518. @objc func onUpdatedMessage(notification: NSNotification) {
  1519. DispatchQueue.main.async {
  1520. let data:[AnyHashable : Any] = notification.userInfo!
  1521. let messageId = data["message_id"] as? String ?? ""
  1522. let messageIdNotif = data["message_id_notif"] as? String ?? ""
  1523. let isPinned = data["is_pinned"] as? String ?? ""
  1524. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId})
  1525. if idx != nil{
  1526. self.dataMessages[idx!][TypeDataMessage.is_pinned] = isPinned
  1527. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1528. 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 ?? "" })
  1529. if row != nil && section != nil {
  1530. DispatchQueue.main.async {
  1531. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1532. }
  1533. }
  1534. let dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  1535. self.pinAllMessages(dataMessages: dataMessagesPin)
  1536. if !messageIdNotif.isEmpty {
  1537. self.appendNewMessage(messageId: messageIdNotif)
  1538. }
  1539. }
  1540. }
  1541. }
  1542. @objc func onReceiveMessage(notification: NSNotification) {
  1543. DispatchQueue.main.async { [self] in
  1544. let data:[AnyHashable : Any] = notification.userInfo!
  1545. if let dataMessage = data["message"] as? TMessage {
  1546. let chatData = dataMessage.mBodies
  1547. if (dataMessage.getCode() == CoreMessage_TMessageCode.PUSH_MEMBER_ROOM_CONTACT_CENTER && isContactCenter) {
  1548. let data = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  1549. if !data.isEmpty {
  1550. if let jsonArray = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
  1551. var members = ""
  1552. let idMe = User.getMyPin()!
  1553. var user : [User] = []
  1554. for json in jsonArray {
  1555. if "\(json)" != idMe {
  1556. if members.isEmpty {
  1557. members = "\(json)"
  1558. } else {
  1559. members += ",\(json)"
  1560. }
  1561. if let userData = User.getData(pin: "\(json)") {
  1562. user.append(userData)
  1563. } else {
  1564. Nexilis.addFriend (fpin: "\(json)") { result in
  1565. DispatchQueue.main.async {
  1566. if result {
  1567. let userData = User.getData(pin: "\(json)")!
  1568. user.append(userData)
  1569. } else {
  1570. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  1571. imageView.tintColor = .white
  1572. let banner = FloatingNotificationBanner(title: "Server busy, please try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  1573. banner.show()
  1574. }
  1575. }
  1576. }
  1577. }
  1578. }
  1579. }
  1580. self.users = user
  1581. self.fPinContacCenter = members
  1582. self.changeAppBar()
  1583. SecureUserDefaults.shared.set(members, forKey: "inEditorPersonal")
  1584. }
  1585. }
  1586. } else if (dataMessage.getCode() == CoreMessage_TMessageCode.ACCEPT_CALL_CENTER) {
  1587. if !self.isRequestContactCenter || !isContactCenter {
  1588. return
  1589. }
  1590. SecureUserDefaults.shared.set(dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN), forKey: "inEditorPersonal")
  1591. let date = Date()
  1592. let formatter = DateFormatter()
  1593. formatter.dateFormat = "HH:mm"
  1594. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1595. self.fPinContacCenter = dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN)
  1596. self.complaintId = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  1597. self.channelContactCenter = dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL)
  1598. var row: [String: Any?] = [:]
  1599. row["category_cc"] = "You are connecting with ".localized() + dataMessage.getBody(key: CoreMessage_TMessageKey.F_DISPLAY_NAME).trimmingCharacters(in: .whitespaces) + " at ".localized() + formatter.string(from: date as Date) + ".\n" + "In order to improve our service, all conversations will be recorded\naccording to state regulations".localized()
  1600. row["message_id"] = ""
  1601. row["message_text"] = "You are connecting with ".localized() + dataMessage.getBody(key: CoreMessage_TMessageKey.F_DISPLAY_NAME).trimmingCharacters(in: .whitespaces) + " at ".localized() + formatter.string(from: date as Date) + ".\n" + "In order to improve our service, all conversations will be recorded\naccording to state regulations".localized()
  1602. row["chat_date"] = "Today".localized()
  1603. self.dataMessages.append(row)
  1604. self.users.append(User.getData(pin: dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN))!)
  1605. self.changeAppBar()
  1606. self.setRightButtonItem()
  1607. self.dateStartCC = "\(Date().currentTimeMillis())"
  1608. self.tableChatView.beginUpdates()
  1609. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  1610. self.tableChatView.endUpdates()
  1611. self.tableChatView.scrollToBottom()
  1612. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  1613. if dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL) != "0" {
  1614. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: "startTimeCC")
  1615. SecureUserDefaults.shared.set(dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL), forKey: "channelCC")
  1616. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  1617. self.dismiss(animated: true, completion: {
  1618. self.removeAllObjectBeforeDismissVC()
  1619. })
  1620. })
  1621. } else {
  1622. viewButton.isHidden = false
  1623. viewTextfield.isHidden = false
  1624. }
  1625. } else if (dataMessage.getCode() == CoreMessage_TMessageCode.INVITE_END_CONTACT_CENTER || dataMessage.getCode() == CoreMessage_TMessageCode.END_CALL_CENTER || dataMessage.getCode() == CoreMessage_TMessageCode.INVITE_EXIT_CONTACT_CENTER) && !fromVCAC {
  1626. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1627. if onGoingCC.isEmpty || !isContactCenter {
  1628. return
  1629. }
  1630. let requester = onGoingCC.components(separatedBy: ",")[0]
  1631. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1632. let fPin = dataMessage.getCode() == CoreMessage_TMessageCode.END_CALL_CENTER ? chatData[CoreMessage_TMessageKey.F_PIN] : dataMessage.getPIN()
  1633. if fPin == officer || fPin == requester {
  1634. DispatchQueue.global().async {
  1635. let date = "\(Date().currentTimeMillis())"
  1636. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1637. do {
  1638. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  1639. "type" : self.channelContactCenter,
  1640. "title" : "Contact Center".localized(),
  1641. "time" : self.dateStartCC,
  1642. "f_pin" : officer,
  1643. "data" : self.complaintId,
  1644. "time_end" : date,
  1645. "complaint_id" : self.complaintId,
  1646. "members" : "",
  1647. "requester": requester
  1648. ], replace: true)
  1649. } catch {
  1650. rollback.pointee = true
  1651. print("Access database error: \(error.localizedDescription)")
  1652. }
  1653. })
  1654. }
  1655. self.dismissKeyboard()
  1656. self.disableEditor()
  1657. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  1658. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  1659. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  1660. DispatchQueue.main.async {
  1661. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1662. imageView.tintColor = .white
  1663. let banner = FloatingNotificationBanner(title: "Call Center Session has ended".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  1664. banner.show()
  1665. }
  1666. timeoutCC.invalidate()
  1667. DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
  1668. if !(self.presentedViewController is EditorPersonal) {
  1669. self.dismiss(animated: true, completion: {
  1670. self.removeAllObjectBeforeDismissVC()
  1671. })
  1672. }
  1673. DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
  1674. self.dismiss(animated: true, completion: {
  1675. self.removeAllObjectBeforeDismissVC()
  1676. })
  1677. }
  1678. })
  1679. } else {
  1680. var members = ""
  1681. self.users.removeAll(where: {$0.pin == fPin})
  1682. for user in self.users {
  1683. if members.isEmpty {
  1684. members = "\(user.pin)"
  1685. } else {
  1686. members = ",\(user.pin)"
  1687. }
  1688. }
  1689. SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
  1690. self.fPinContacCenter = members
  1691. self.changeAppBar()
  1692. }
  1693. }
  1694. else if (chatData[CoreMessage_TMessageKey.F_PIN] == self.dataPerson["f_pin"]!! && !self.isContactCenter && (chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == MessageScope.WHISPER)) || (self.isContactCenter && chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID] == "5" && self.complaintId == chatData[CoreMessage_TMessageKey.CALL_CENTER_ID]) {
  1695. if chatData[CoreMessage_TMessageKey.F_PIN] == nil {
  1696. return
  1697. }
  1698. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]})
  1699. if idx != nil {
  1700. self.dataMessages[idx!][TypeDataMessage.message_text] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1701. self.dataMessages[idx!][TypeDataMessage.last_edit] = Int64(chatData[CoreMessage_TMessageKey.LAST_EDIT]!)
  1702. self.dataMessages[idx!][TypeDataMessage.status] = chatData[CoreMessage_TMessageKey.STATUS]
  1703. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1704. 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 })
  1705. if row != nil && section != nil {
  1706. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1707. }
  1708. return
  1709. }
  1710. var row: [String: Any?] = [:]
  1711. row["message_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_ID]
  1712. row["f_pin"] = chatData[CoreMessage_TMessageKey.F_PIN]
  1713. row["l_pin"] = chatData[CoreMessage_TMessageKey.L_PIN]
  1714. row["message_scope_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]
  1715. row["server_date"] = chatData[CoreMessage_TMessageKey.SERVER_DATE]
  1716. row["status"] = chatData[CoreMessage_TMessageKey.STATUS]
  1717. row["message_text"] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1718. if (chatData.keys.contains(CoreMessage_TMessageKey.AUDIO_ID)) {
  1719. row["audio_id"] = chatData[CoreMessage_TMessageKey.AUDIO_ID]
  1720. } else {
  1721. row["audio_id"] = ""
  1722. }
  1723. if (chatData.keys.contains(CoreMessage_TMessageKey.GIF_ID)) {
  1724. row["gif_id"] = chatData[CoreMessage_TMessageKey.GIF_ID]
  1725. } else {
  1726. row["gif_id"] = ""
  1727. }
  1728. if (chatData.keys.contains(CoreMessage_TMessageKey.VIDEO_ID)) {
  1729. row["video_id"] = chatData[CoreMessage_TMessageKey.VIDEO_ID]
  1730. } else {
  1731. row["video_id"] = ""
  1732. }
  1733. if (chatData.keys.contains(CoreMessage_TMessageKey.IMAGE_ID)) {
  1734. row["image_id"] = chatData[CoreMessage_TMessageKey.IMAGE_ID]
  1735. } else {
  1736. row["image_id"] = ""
  1737. }
  1738. if (chatData.keys.contains(CoreMessage_TMessageKey.THUMB_ID)) {
  1739. row["thumb_id"] = chatData[CoreMessage_TMessageKey.THUMB_ID]
  1740. } else {
  1741. row["thumb_id"] = ""
  1742. }
  1743. if (chatData.keys.contains(CoreMessage_TMessageKey.READ_RECEIPTS)) {
  1744. row["read_receipts"] = chatData[CoreMessage_TMessageKey.READ_RECEIPTS]
  1745. } else {
  1746. row["read_receipts"] = ""
  1747. }
  1748. if (chatData.keys.contains(CoreMessage_TMessageKey.CREDENTIAL)) {
  1749. row["credential"] = chatData[CoreMessage_TMessageKey.CREDENTIAL]
  1750. } else {
  1751. row["credential"] = ""
  1752. }
  1753. row["chat_id"] = ""
  1754. if (chatData.keys.contains(CoreMessage_TMessageKey.FILE_ID)) {
  1755. row["file_id"] = chatData[CoreMessage_TMessageKey.FILE_ID]
  1756. } else {
  1757. row["file_id"] = ""
  1758. }
  1759. row["progress"] = 0.0
  1760. row["attachment_flag"] = chatData[CoreMessage_TMessageKey.ATTACHMENT_FLAG]
  1761. row["reff_id"] = chatData[CoreMessage_TMessageKey.REF_ID] ?? ""
  1762. row["lock"] = ""
  1763. row["is_stared"] = "0"
  1764. row[TypeDataMessage.spec_file] = chatData[CoreMessage_TMessageKey.ATTACHMENT_SPECIALITY]
  1765. row[TypeDataMessage.is_forwarded] = Int(chatData[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] ?? "0")
  1766. row[TypeDataMessage.is_bot] = Int(chatData[CoreMessage_TMessageKey.IS_BOT] ?? "0")
  1767. row["isSelected"] = false
  1768. if !self.dataDates.contains("Today".localized()) {
  1769. self.dataDates.append("Today".localized())
  1770. self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
  1771. }
  1772. row["chat_date"] = "Today".localized()
  1773. row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
  1774. self.counter += 1
  1775. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1776. self.listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1777. }
  1778. self.tableChatView.beginUpdates()
  1779. self.dataMessages.append(row)
  1780. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
  1781. self.tableChatView.endUpdates()
  1782. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1783. var timer = Timer()
  1784. var minute = 60
  1785. self.timerCredential[row["message_id"] as? String ?? ""] = timer
  1786. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1787. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  1788. minute -= 1
  1789. self.listTimerCredential[row["message_id"] as? String ?? ""] = minute
  1790. if minute == 0 {
  1791. timer.invalidate()
  1792. self.listTimerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1793. self.timerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1794. SecureUserDefaults.shared.removeValue(forKey: row["message_id"] as? String ?? "")
  1795. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == row["message_id"] as? String})
  1796. if idx != nil {
  1797. self.dataMessages[idx!]["lock"] = "2"
  1798. self.dataMessages[idx!]["reff_id"] = ""
  1799. }
  1800. DispatchQueue.global().async {
  1801. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1802. do {
  1803. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1804. "lock" : "2"
  1805. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1806. } catch {
  1807. rollback.pointee = true
  1808. print("Access database error: \(error.localizedDescription)")
  1809. }
  1810. })
  1811. }
  1812. }
  1813. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  1814. 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})
  1815. if row != nil && section != nil{
  1816. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1817. }
  1818. })
  1819. }
  1820. if self.isContactCenter {
  1821. let idMe = User.getMyPin()!
  1822. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1823. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1824. if officer == idMe {
  1825. self.timeoutCC.invalidate()
  1826. } else if !fromVCAC {
  1827. if !self.showToast30s {
  1828. self.view.makeToast("Please reply within 30 seconds so the call center session doesn't end.".localized(), duration: 3)
  1829. sendTyping(l_pin: fPinContacCenter, isTyping: true)
  1830. self.showToast30s = true
  1831. }
  1832. }
  1833. }
  1834. if chatData[CoreMessage_TMessageKey.FORMAT] == "1" {
  1835. self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1836. self.tableChatView.scrollToBottom()
  1837. } else if self.currentIndexpath?.row == (self.dataMessages.count - 2) {
  1838. if (self.viewIfLoaded?.window != nil) {
  1839. self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1840. }
  1841. self.tableChatView.scrollToBottom()
  1842. 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) {
  1843. self.counter = 0
  1844. self.updateCounter(counter: self.counter)
  1845. }
  1846. let lastMarkerCounter = markerCounter
  1847. if self.markerCounter != nil {
  1848. self.markerCounter = nil
  1849. }
  1850. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  1851. if indexMessage != nil {
  1852. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1853. 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 })
  1854. if row != nil && section != nil {
  1855. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1856. }
  1857. }
  1858. }
  1859. else if self.currentIndexpath == nil {
  1860. self.counter = 0
  1861. self.updateCounter(counter: self.counter)
  1862. if (self.viewIfLoaded?.window != nil) {
  1863. self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
  1864. }
  1865. }
  1866. else if self.counter != 0 {
  1867. if !self.indicatorCounterBSTB.isDescendant(of: self.view) && self.buttonScrollToBottom.isDescendant(of: self.view) {
  1868. self.markerCounter = row["message_id"] as? String
  1869. self.addCounterAtButttonScrollToBottom()
  1870. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.markerCounter })
  1871. if indexMessage != nil {
  1872. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1873. 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 })
  1874. if row != nil && section != nil {
  1875. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1876. }
  1877. }
  1878. } else if self.indicatorCounterBSTB.isDescendant(of: self.view) {
  1879. self.labelCounter.text = "\(self.counter)"
  1880. }
  1881. }
  1882. } else {
  1883. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1884. }
  1885. } else if !self.isContactCenter {
  1886. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1887. }
  1888. }
  1889. }
  1890. private func disableEditor() {
  1891. view.addSubview(containerAction)
  1892. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1893. NSLayoutConstraint.activate([
  1894. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1895. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1896. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1897. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1898. ])
  1899. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1900. let labelDisable = UILabel()
  1901. containerAction.addSubview(labelDisable)
  1902. labelDisable.translatesAutoresizingMaskIntoConstraints = false
  1903. NSLayoutConstraint.activate([
  1904. labelDisable.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1905. labelDisable.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1906. ])
  1907. labelDisable.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1908. labelDisable.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1909. labelDisable.text = "Call center session is over".localized()
  1910. }
  1911. @objc func onStatusChat(notification: NSNotification) {
  1912. DispatchQueue.main.async {
  1913. let data:[AnyHashable : Any] = notification.userInfo!
  1914. if let dataMessage = data["message"] as? TMessage {
  1915. let chatData = dataMessage.mBodies
  1916. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1917. let requester = onGoingCC.components(separatedBy: ",")[0]
  1918. let idMe = User.getMyPin()!
  1919. if chatData[CoreMessage_TMessageKey.F_PIN] == self.dataPerson["f_pin"]!! || chatData[CoreMessage_TMessageKey.L_PIN] == self.dataPerson["f_pin"]!! || chatData[CoreMessage_TMessageKey.L_PIN] == self.fPinContacCenter || requester == idMe {
  1920. if (chatData.keys.contains(CoreMessage_TMessageKey.MESSAGE_ID) && !(chatData[CoreMessage_TMessageKey.MESSAGE_ID]!).contains("-2,")) {
  1921. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! })
  1922. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) }) {
  1923. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) {
  1924. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1925. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1926. }
  1927. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1928. }
  1929. if (idx != nil) {
  1930. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1931. self.updateStatusDelete(idx: idx, chatData: chatData)
  1932. } else {
  1933. self.updateStatusMessage(idx: idx, chatData: chatData)
  1934. }
  1935. }
  1936. }
  1937. else if (chatData.keys.contains("message_id")) {
  1938. var idMessage = dataMessage.getBody(key: "message_id")
  1939. if idMessage.contains("'") {
  1940. idMessage = idMessage.replacingOccurrences(of: "'", with: "")
  1941. }
  1942. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == idMessage })
  1943. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == idMessage }) }) {
  1944. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == idMessage }) {
  1945. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1946. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1947. }
  1948. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1949. }
  1950. if (idx != nil) {
  1951. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1952. self.updateStatusDelete(idx: idx, chatData: chatData)
  1953. } else {
  1954. self.updateStatusMessage(idx: idx, chatData: chatData)
  1955. }
  1956. }
  1957. }
  1958. else {
  1959. let listMessageId = chatData[CoreMessage_TMessageKey.MESSAGE_ID]!.split(separator: ",")
  1960. for i in 1..<listMessageId.count {
  1961. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == listMessageId[i] })
  1962. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == listMessageId[i] }) }) {
  1963. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == listMessageId[i] }) {
  1964. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1965. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1966. }
  1967. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1968. }
  1969. if (idx != nil) {
  1970. self.updateStatusMessage(idx: idx, chatData: chatData)
  1971. }
  1972. }
  1973. }
  1974. }
  1975. }
  1976. }
  1977. }
  1978. @objc func onFailedSendMessage(notification: NSNotification) {
  1979. DispatchQueue.main.async {
  1980. let data:[AnyHashable : Any] = notification.userInfo!
  1981. let messageId = data["message_id"] as? String ?? ""
  1982. let status = data["status"] as? String ?? ""
  1983. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  1984. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  1985. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  1986. self.groupImages[idxMessageIdParent].value[idxInImages].status = status
  1987. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = status
  1988. }
  1989. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1990. }
  1991. if (idx != nil) {
  1992. do {
  1993. self.dataMessages[idx!]["status"] = status
  1994. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1995. 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 })
  1996. if row != nil && section != nil {
  1997. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1998. }
  1999. } catch {
  2000. }
  2001. }
  2002. }
  2003. }
  2004. @objc func onRefreshCallLog(notification: NSNotification) {
  2005. DispatchQueue.main.async {
  2006. let data:[AnyHashable : Any] = notification.userInfo!
  2007. let messageId = data["message_id"] as? String ?? ""
  2008. let pin = data["pin"] as? String ?? ""
  2009. if pin == self.dataPerson["f_pin"]!! {
  2010. self.appendNewMessage(messageId: messageId)
  2011. }
  2012. }
  2013. }
  2014. private func appendNewMessage(messageId: String) {
  2015. var row: [String: Any?] = [:]
  2016. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2017. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: "SELECT message_id, f_pin, l_pin, message_scope_id, server_date, status, message_text, audio_id, video_id, image_id, thumb_id, read_receipts, chat_id, file_id, attachment_flag, reff_id, lock, is_stared, blog_id, credential, is_call_center, call_center_id, opposite_pin, last_edited, gif_id, is_forwarded_message, attachment_speciality, is_pinned, is_bot from MESSAGE where message_id = '\(messageId)'"), cursorData.next() {
  2018. row["message_id"] = cursorData.string(forColumnIndex: 0)
  2019. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  2020. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  2021. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  2022. row["server_date"] = cursorData.string(forColumnIndex: 4)
  2023. row["status"] = cursorData.string(forColumnIndex: 5)
  2024. row["message_text"] = cursorData.string(forColumnIndex: 6)
  2025. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  2026. row["video_id"] = cursorData.string(forColumnIndex: 8)
  2027. row["image_id"] = cursorData.string(forColumnIndex: 9)
  2028. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  2029. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  2030. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  2031. row["file_id"] = cursorData.string(forColumnIndex: 13)
  2032. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  2033. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  2034. row["lock"] = cursorData.string(forColumnIndex: 16)
  2035. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  2036. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  2037. row["credential"] = cursorData.string(forColumnIndex: 19)
  2038. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  2039. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  2040. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  2041. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  2042. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  2043. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  2044. row[TypeDataMessage.spec_file] = cursorData.string(forColumnIndex: 26)
  2045. row[TypeDataMessage.is_pinned] = cursorData.string(forColumnIndex: 27)
  2046. row[TypeDataMessage.is_bot] = Int (cursorData.string(forColumnIndex: 28) ?? "0")
  2047. row["progress"] = 0.0
  2048. row["isSelected"] = false
  2049. row["chat_date"] = "Today".localized()
  2050. cursorData.close()
  2051. }
  2052. })
  2053. DispatchQueue.main.async {
  2054. if !self.dataDates.contains("Today".localized()) {
  2055. self.dataDates.append("Today".localized())
  2056. self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
  2057. }
  2058. self.tableChatView.beginUpdates()
  2059. self.dataMessages.append(row)
  2060. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
  2061. self.tableChatView.endUpdates()
  2062. }
  2063. }
  2064. private func updateStatusDelete(idx: Int?, chatData: [String: String]) {
  2065. do {
  2066. if self.dataMessages[idx!]["lock"] != nil && self.dataMessages[idx!]["lock"] as? String ?? "" == "1" {
  2067. return
  2068. }
  2069. self.dataMessages[idx!]["lock"] = "1"
  2070. self.dataMessages[idx!]["reff_id"] = ""
  2071. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  2072. 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 })
  2073. if row != nil && section != nil {
  2074. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2075. }
  2076. if self.listTimerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""] != nil {
  2077. self.listTimerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  2078. self.timerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""]?.invalidate()
  2079. self.timerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  2080. SecureUserDefaults.shared.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  2081. }
  2082. if self.reffId != nil && self.reffId == chatData["message_id"]! {
  2083. self.deleteReplyView()
  2084. }
  2085. } catch {
  2086. }
  2087. }
  2088. private func updateStatusMessage(idx: Int?, chatData: [String: String]) {
  2089. do {
  2090. if Int(self.dataMessages[idx!]["status"] as? String ?? "")! > Int(chatData[CoreMessage_TMessageKey.STATUS]!)! {
  2091. return
  2092. }
  2093. self.dataMessages[idx!]["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  2094. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  2095. 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 })
  2096. if row != nil && section != nil {
  2097. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2098. }
  2099. } catch {
  2100. }
  2101. }
  2102. @objc func onTyping(notification: NSNotification) {
  2103. DispatchQueue.main.async { [self] in
  2104. let data:[AnyHashable : Any] = notification.userInfo!
  2105. let message: TMessage = data["message"] as! TMessage
  2106. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  2107. if !onGoingCC.isEmpty {
  2108. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  2109. if message.getBody(key: CoreMessage_TMessageKey.F_PIN) != officer {
  2110. //print("RESET TIMER")
  2111. // timeoutCC.invalidate()
  2112. // timeoutCC = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: {_ in
  2113. // let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  2114. // imageView.tintColor = .white
  2115. // let banner = FloatingNotificationBanner(title: "Customer doesn't respond in 30 second, so call center session will be ended automatically.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  2116. // banner.show()
  2117. // self.endCallCenter()
  2118. // })
  2119. }
  2120. } else {
  2121. }
  2122. }
  2123. }
  2124. @objc func onUnfriend(notification: NSNotification) {
  2125. let data:[AnyHashable : Any] = notification.userInfo!
  2126. DispatchQueue.main.async { [self] in
  2127. if data["state"] as! Int == 99 && (data["message"] as? String ?? "").components(separatedBy: ",")[0] == "delete_buddy" {
  2128. removed = true
  2129. if forwardSession || copySession || deleteSession || isSearching {
  2130. cancelAction()
  2131. }
  2132. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {[self] in
  2133. navigationItem.rightBarButtonItem = nil
  2134. navigationItem.rightBarButtonItems = nil
  2135. changeAppBar()
  2136. view.addSubview(containerAction)
  2137. containerAction.translatesAutoresizingMaskIntoConstraints = false
  2138. NSLayoutConstraint.activate([
  2139. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  2140. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  2141. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  2142. containerAction.heightAnchor.constraint(equalToConstant: 120)
  2143. ])
  2144. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  2145. let labelUnfriend = UILabel()
  2146. containerAction.addSubview(labelUnfriend)
  2147. labelUnfriend.translatesAutoresizingMaskIntoConstraints = false
  2148. NSLayoutConstraint.activate([
  2149. labelUnfriend.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  2150. labelUnfriend.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  2151. ])
  2152. labelUnfriend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  2153. labelUnfriend.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  2154. labelUnfriend.text = "You have unfriended this user".localized()
  2155. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  2156. if contactChatNav.viewIfLoaded?.window != nil {
  2157. contactChatNav.dismiss(animated: true)
  2158. }
  2159. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  2160. if self.fromNotification {
  2161. self.didTapExit()
  2162. } else {
  2163. self.navigationController?.popViewController(animated: true)
  2164. }
  2165. })
  2166. })
  2167. } else if data["state"] as! Int == 01 {
  2168. if let dataMessage = try! JSONSerialization.jsonObject(with: (data["message"] as? String ?? "").data(using: .utf8)!, options: []) as? [String: String] {
  2169. if(dataMessage["l_pin"] == dataPerson["f_pin"]!){
  2170. if let block = dataMessage["block"] {
  2171. if(block == "-1"){
  2172. dismissKeyboard()
  2173. }
  2174. blockedView(blocked: block)
  2175. if contactChatNav.viewIfLoaded?.window != nil {
  2176. contactChatNav.dismiss(animated: true)
  2177. }
  2178. cancelAction()
  2179. }
  2180. }
  2181. setRightButtonItem()
  2182. }
  2183. }
  2184. }
  2185. }
  2186. func blockedView(blocked: String) {
  2187. dismissKeyboard()
  2188. view.addSubview(containerAction)
  2189. containerAction.translatesAutoresizingMaskIntoConstraints = false
  2190. NSLayoutConstraint.activate([
  2191. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  2192. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  2193. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  2194. containerAction.heightAnchor.constraint(equalToConstant: 120)
  2195. ])
  2196. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  2197. let labelBlocked = UILabel()
  2198. containerAction.addSubview(labelBlocked)
  2199. labelBlocked.translatesAutoresizingMaskIntoConstraints = false
  2200. NSLayoutConstraint.activate([
  2201. labelBlocked.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  2202. labelBlocked.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  2203. ])
  2204. labelBlocked.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  2205. labelBlocked.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  2206. if blocked == "1" {
  2207. labelBlocked.text = "You blocked this user".localized()
  2208. } else {
  2209. labelBlocked.text = "You have been blocked by this user".localized()
  2210. }
  2211. }
  2212. @objc func seeProfileTapped() {
  2213. if dataPerson["f_pin"] == "-999" || dataPerson["isOfficial"] == "1" || removed || copySession || forwardSession || deleteSession || isContactCenter {
  2214. return
  2215. }
  2216. dismissKeyboard()
  2217. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
  2218. controller.data = dataPerson["f_pin"]!!
  2219. controller.checkReadMessage = {
  2220. self.dataPerson.removeAll()
  2221. self.getDataProfile(fPin: self.unique_l_pin)
  2222. self.changeAppBar()
  2223. if self.currentIndexpath == nil {
  2224. var listData = self.dataMessages
  2225. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2226. if listData.count != 0 && !self.isContactCenter {
  2227. let idMe = User.getMyPin() as String?
  2228. for i in 0...listData.count - 1 {
  2229. if listData[i]["f_pin"] as? String != idMe {
  2230. self.sendReadMessageStatus(chat_id: "", f_pin: self.dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: listData[i]["message_id"] as? String ?? "")
  2231. }
  2232. }
  2233. }
  2234. } else {
  2235. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.currentIndexpath!.section] })
  2236. var listData = dataMessages
  2237. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2238. if listData.count != 0 && !self.isContactCenter {
  2239. let idMe = User.getMyPin() as String?
  2240. for i in 0...listData.count - 1 {
  2241. if listData[i]["f_pin"] as? String != idMe {
  2242. self.sendReadMessageStatus(chat_id: "", f_pin: self.dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: listData[i]["message_id"] as? String ?? "")
  2243. }
  2244. }
  2245. }
  2246. }
  2247. }
  2248. navigationController?.show(controller, sender: nil)
  2249. }
  2250. @IBAction func voiceTapped(_ sender: UIButton) {
  2251. if (self.constraintBottomAttachment.constant != 0.0) {
  2252. constraintBottomAttachment.constant = 0.0
  2253. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2254. self.viewSticker.removeFromSuperview()
  2255. }
  2256. }
  2257. @IBAction func imageTapped(_ sender: UIButton) {
  2258. if (self.constraintBottomAttachment.constant != 0.0) {
  2259. constraintBottomAttachment.constant = 0.0
  2260. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2261. self.viewSticker.removeFromSuperview()
  2262. }
  2263. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2264. return
  2265. }
  2266. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  2267. if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
  2268. alertController.addAction(action)
  2269. }
  2270. if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
  2271. alertController.addAction(action)
  2272. }
  2273. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  2274. self.present(alertController, animated: true)
  2275. }
  2276. private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
  2277. return UIAlertAction(title: title, style: .default) { [unowned self] _ in
  2278. switch type {
  2279. case "image":
  2280. var config = PHPickerConfiguration()
  2281. config.filter = .images
  2282. config.preferredAssetRepresentationMode = .automatic
  2283. let picker = PHPickerViewController(configuration: config)
  2284. picker.delegate = self
  2285. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  2286. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  2287. }
  2288. if !isBlackCancelButton {
  2289. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2290. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2291. }
  2292. present(picker, animated: true, completion: nil)
  2293. case "video":
  2294. var config = PHPickerConfiguration()
  2295. config.filter = .videos
  2296. config.preferredAssetRepresentationMode = .automatic
  2297. let picker = PHPickerViewController(configuration: config)
  2298. picker.delegate = self
  2299. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  2300. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  2301. }
  2302. if !isBlackCancelButton {
  2303. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2304. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2305. }
  2306. present(picker, animated: true, completion: nil)
  2307. case "imageCamera":
  2308. if isContactCenter && channelContactCenter == "2" {
  2309. self.view.makeToast("You can't take photo when Video Call".localized(), duration: 3)
  2310. return
  2311. }
  2312. imageVideoPicker.present(source: .imageCamera)
  2313. case "videoCamera":
  2314. if isContactCenter && channelContactCenter == "2" {
  2315. self.view.makeToast("You can't take video when Video Call".localized(), duration: 3)
  2316. return
  2317. }
  2318. imageVideoPicker.present(source: .videoCamera)
  2319. default:
  2320. break
  2321. }
  2322. }
  2323. }
  2324. @IBAction func photoTapped(_ sender: UIButton) {
  2325. if (self.constraintBottomAttachment.constant != 0.0) {
  2326. constraintBottomAttachment.constant = 0.0
  2327. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2328. self.viewSticker.removeFromSuperview()
  2329. }
  2330. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2331. return
  2332. }
  2333. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  2334. if let action = self.actionImageVideo(for: "imageCamera", title: "Take Photo".localized()) {
  2335. alertController.addAction(action)
  2336. }
  2337. if let action = self.actionImageVideo(for: "videoCamera", title: "Take Video".localized()) {
  2338. alertController.addAction(action)
  2339. }
  2340. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  2341. self.present(alertController, animated: true)
  2342. }
  2343. @IBAction func stickerTapped(_ sender: UIButton) {
  2344. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2345. return
  2346. }
  2347. if textFieldSend.isFirstResponder {
  2348. dismissKeyboard()
  2349. }
  2350. DispatchQueue.main.async {
  2351. if !self.viewSticker.isDescendant(of: self.view) {
  2352. self.constraintBottomAttachment.constant = 200.0
  2353. self.view.addSubview(self.viewSticker)
  2354. self.viewSticker.translatesAutoresizingMaskIntoConstraints = false
  2355. NSLayoutConstraint.activate([
  2356. self.viewSticker.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  2357. self.viewSticker.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  2358. self.viewSticker.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  2359. self.viewSticker.heightAnchor.constraint(equalToConstant: 200)
  2360. ])
  2361. let layout = UICollectionViewFlowLayout()
  2362. layout.scrollDirection = .vertical
  2363. let collectionSticker = UICollectionView(frame: .zero, collectionViewLayout: layout)
  2364. collectionSticker.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellSticker")
  2365. collectionSticker.delegate = self
  2366. collectionSticker.dataSource = self
  2367. collectionSticker.backgroundColor = .clear
  2368. self.viewSticker.addSubview(collectionSticker)
  2369. collectionSticker.translatesAutoresizingMaskIntoConstraints = false
  2370. NSLayoutConstraint.activate([
  2371. collectionSticker.topAnchor.constraint(equalTo: self.viewSticker.topAnchor, constant: 20),
  2372. collectionSticker.bottomAnchor.constraint(equalTo: self.viewSticker.bottomAnchor, constant: -20),
  2373. collectionSticker.leadingAnchor.constraint(equalTo: self.viewSticker.leadingAnchor, constant: 20),
  2374. collectionSticker.trailingAnchor.constraint(equalTo: self.viewSticker.trailingAnchor, constant: -20)
  2375. ])
  2376. if (self.currentIndexpath != nil) {
  2377. DispatchQueue.main.async {
  2378. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  2379. }
  2380. } else {
  2381. self.tableChatView.scrollToBottom()
  2382. }
  2383. } else {
  2384. self.constraintBottomAttachment.constant = 0.0
  2385. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2386. self.viewSticker.removeFromSuperview()
  2387. }
  2388. }
  2389. }
  2390. @IBAction func fileTapped(_ sender: UIButton) {
  2391. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2392. return
  2393. }
  2394. if (self.constraintBottomAttachment.constant != 0.0) {
  2395. constraintBottomAttachment.constant = 0.0
  2396. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2397. self.viewSticker.removeFromSuperview()
  2398. }
  2399. documentPicker.present()
  2400. }
  2401. @objc func sendTapped() {
  2402. sendChat(message_text: textFieldSend.text!, viewController: self)
  2403. }
  2404. @objc func showChooserACKConfidential() {
  2405. // dismissKeyboard()
  2406. let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
  2407. 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)
  2408. 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)
  2409. let imageSecret = resizeImage(image: UIImage(named: "pb_icon_secret_msg_on", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2410. let imageSticker = resizeImage(image: UIImage(named: "Sticker---Emoji", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2411. let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
  2412. self.isConfidential = !self.isConfidential
  2413. if self.isConfidential {
  2414. self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
  2415. } else {
  2416. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2417. }
  2418. if self.isAck {
  2419. self.isAck = false
  2420. }
  2421. if self.isSecret {
  2422. self.isSecret = false
  2423. }
  2424. })
  2425. let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
  2426. self.isAck = !self.isAck
  2427. if self.isAck {
  2428. self.buttonAckConfidential.setImage(imageAck, for: .normal)
  2429. } else {
  2430. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2431. }
  2432. if self.isConfidential {
  2433. self.isConfidential = false
  2434. }
  2435. if self.isSecret {
  2436. self.isSecret = false
  2437. }
  2438. })
  2439. let secretAction = UIAlertAction(title: "Secret Message".localized(), style: .default, handler: { (UIAlertAction) in
  2440. self.isSecret = !self.isSecret
  2441. if self.isSecret {
  2442. self.buttonAckConfidential.setImage(imageSecret, for: .normal)
  2443. } else {
  2444. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2445. }
  2446. if self.isConfidential {
  2447. self.isConfidential = false
  2448. }
  2449. if self.isAck {
  2450. self.isAck = false
  2451. }
  2452. })
  2453. let stickerAction = UIAlertAction(title: "Open Sticker".localized(), style: .default, handler: { (UIAlertAction) in
  2454. self.stickerTapped(UIButton())
  2455. })
  2456. confidentialAction.setValue(imageConfidential, forKey: "image")
  2457. ackAction.setValue(imageAck, forKey: "image")
  2458. secretAction.setValue(imageSecret, forKey: "image")
  2459. secretAction.setValue(imageSecret, forKey: "image")
  2460. stickerAction.setValue(imageSticker, forKey: "image")
  2461. alertController.addAction(confidentialAction)
  2462. alertController.addAction(ackAction)
  2463. alertController.addAction(secretAction)
  2464. // alertController.addAction(stickerAction)
  2465. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
  2466. self.isConfidential = false
  2467. self.isAck = false
  2468. self.isSecret = false
  2469. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2470. }))
  2471. self.present(alertController, animated: true, completion: nil)
  2472. }
  2473. public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
  2474. self.isConfidential = isConfidential
  2475. self.isAck = isAck
  2476. let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2477. let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2478. if isAck {
  2479. buttonAckConfidential.setImage(imageAck, for: .normal)
  2480. } else if isConfidential {
  2481. buttonAckConfidential.setImage(imageConfidential, for: .normal)
  2482. } else {
  2483. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2484. }
  2485. }
  2486. @objc func addRoom(sender: UIBarButtonItem) {
  2487. let controller = QmeraCallContactViewController()
  2488. controller.isDismiss = { user in
  2489. DispatchQueue.global().async {
  2490. _ = Nexilis.write(message: CoreMessage_TMessageBank.getCCRoomInvite(l_pin: user.pin, ticket_id: self.complaintId, channel: self.channelContactCenter))
  2491. }
  2492. }
  2493. controller.selectedUser.append(contentsOf: users)
  2494. controller.isInviteCC = true
  2495. self.navigationController?.show(controller, sender: nil)
  2496. }
  2497. @objc func audioVideoCall(sender: UIBarButtonItem) {
  2498. if sender.tag == 0 {
  2499. if !Nexilis.checkingAccess(key: "audio_call") {
  2500. self.view.makeToast("Feature disabled..".localized(), duration: 3)
  2501. return
  2502. }
  2503. let goAudioCall = Nexilis.checkMicPermission()
  2504. if !goAudioCall{
  2505. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2506. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2507. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2508. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2509. }
  2510. }))
  2511. self.navigationController?.present(alert, animated: true, completion: nil)
  2512. return
  2513. }
  2514. if let pin = dataPerson["f_pin"] {
  2515. if !CheckConnection.isConnectedToNetwork() {
  2516. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2517. imageView.tintColor = .white
  2518. 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)
  2519. banner.show()
  2520. return
  2521. }
  2522. let controller = QmeraAudioViewController()
  2523. controller.user = User.getData(pin: pin)
  2524. controller.isOutgoing = true
  2525. controller.modalPresentationStyle = .overCurrentContext
  2526. present(controller, animated: true, completion: nil)
  2527. }
  2528. } else {
  2529. if !Nexilis.checkingAccess(key: "video_call") {
  2530. self.view.makeToast("Feature disabled..".localized(), duration: 3)
  2531. return
  2532. }
  2533. let goAudioCall = Nexilis.checkMicPermission()
  2534. let goVideoCall = Nexilis.checkCameraPermission()
  2535. if goVideoCall == 0 {
  2536. let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
  2537. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2538. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2539. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2540. }
  2541. }))
  2542. self.navigationController?.present(alert, animated: true, completion: nil)
  2543. return
  2544. } else if goVideoCall == -1 {
  2545. return
  2546. }
  2547. if !CheckConnection.isConnectedToNetwork() {
  2548. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2549. imageView.tintColor = .white
  2550. 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)
  2551. banner.show()
  2552. return
  2553. }
  2554. let videoVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
  2555. videoVC.dataPerson.append(dataPerson)
  2556. self.show(videoVC, sender: nil)
  2557. }
  2558. }
  2559. @objc func dismissKeyboard() {
  2560. if isSearching {
  2561. searchBar.resignFirstResponder()
  2562. } else {
  2563. textFieldSend.resignFirstResponder() // dismiss keyoard
  2564. if (self.constraintBottomAttachment.constant != 0.0) {
  2565. constraintBottomAttachment.constant = 0.0
  2566. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2567. self.viewSticker.removeFromSuperview()
  2568. }
  2569. }
  2570. }
  2571. @objc func didTapExit() {
  2572. if complaintId.isEmpty || fromVCAC {
  2573. self.dismiss(animated: true, completion: {
  2574. self.removeAllObjectBeforeDismissVC()
  2575. })
  2576. } else if !complaintId.isEmpty {
  2577. let alert = LibAlertController(title: "Interaction with Call Center is in progress".localized(), message: "Are you sure you want to end the Call Center?".localized(), preferredStyle: .alert)
  2578. alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
  2579. alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  2580. self.endCallCenter()
  2581. }))
  2582. self.present(alert, animated: true, completion: nil)
  2583. }
  2584. }
  2585. private func removeAllObjectBeforeDismissVC() {
  2586. for timer in self.timerCredential.values {
  2587. timer.invalidate()
  2588. }
  2589. self.timeoutCC.invalidate()
  2590. SecureUserDefaults.shared.removeValue(forKey: "inEditorPersonal")
  2591. NotificationCenter.default.removeObserver(self)
  2592. self.removeFromParent()
  2593. if !self.isContactCenter {
  2594. let l_pin = self.dataPerson["f_pin"]!!
  2595. var data: [String: Any] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
  2596. if listMentionInTextField.count > 0 {
  2597. var dataMention: [[String: String]] = []
  2598. for list in listMentionInTextField {
  2599. var dataTemp: [String: String] = [:]
  2600. dataTemp["f_pin_mention"] = list.pin
  2601. dataTemp["upper"] = list.ex_block
  2602. dataMention.append(dataTemp)
  2603. }
  2604. data["list_mention"] = dataMention
  2605. }
  2606. if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
  2607. let jsonString = String(data: jsonData, encoding: .utf8) {
  2608. SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
  2609. }
  2610. }
  2611. }
  2612. public func endCallCenter() {
  2613. timeoutCC.invalidate()
  2614. let complaintId = self.complaintId
  2615. let idMe = User.getMyPin()!
  2616. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  2617. let requester = onGoingCC.components(separatedBy: ",")[0]
  2618. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  2619. DispatchQueue.global().async {
  2620. let date = "\(Date().currentTimeMillis())"
  2621. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2622. do {
  2623. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  2624. "type" : self.channelContactCenter,
  2625. "title" : "Contact Center".localized(),
  2626. "time" : self.dateStartCC,
  2627. "f_pin" : officer,
  2628. "data" : self.complaintId,
  2629. "time_end" : date,
  2630. "complaint_id" : self.complaintId,
  2631. "members" : "",
  2632. "requester": requester
  2633. ], replace: true)
  2634. } catch {
  2635. rollback.pointee = true
  2636. print("Access database error: \(error.localizedDescription)")
  2637. }
  2638. })
  2639. if officer == idMe {
  2640. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: requester))
  2641. } else {
  2642. if requester == idMe {
  2643. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: officer))
  2644. } else {
  2645. _ = Nexilis.write(message: CoreMessage_TMessageBank.leaveCCRoomInvite(ticket_id: complaintId))
  2646. }
  2647. }
  2648. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  2649. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  2650. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  2651. }
  2652. self.dismiss(animated: true, completion: {
  2653. self.removeAllObjectBeforeDismissVC()
  2654. })
  2655. }
  2656. @objc func keyboardWillHide(notification: NSNotification) {
  2657. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  2658. let info:NSDictionary = notification.userInfo! as NSDictionary
  2659. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2660. self.constraintViewTextField.constant = 0
  2661. self.constraintBottomAttachment.constant = 0
  2662. self.constraintBottomContainerMultpileSelectSession.constant = 0
  2663. if self.contraintBottomMention.constant > 0 {
  2664. self.contraintBottomMention.constant = 25 + constraintBottomAttachment.constant + self.heightTextFieldSend.constant + self.viewTextfield.bounds.height
  2665. }
  2666. keyboardHeightForMention = nil
  2667. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2668. self.view.layoutIfNeeded()
  2669. })
  2670. }
  2671. }
  2672. @objc func keyboardWillShow(notification: NSNotification) {
  2673. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  2674. // if (self.constraintBottomAttachment.constant != 0.0) {
  2675. // self.constraintBottomAttachment.constant = 0.0
  2676. // self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2677. // self.viewSticker.removeFromSuperview()
  2678. // }
  2679. let info:NSDictionary = notification.userInfo! as NSDictionary
  2680. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  2681. let keyboardHeight: CGFloat = keyboardSize.height
  2682. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2683. if self.constraintBottomAttachment.constant != keyboardHeight || self.constraintViewTextField.constant != keyboardHeight - 60 {
  2684. // self.constraintViewTextField.constant = keyboardHeight - 60
  2685. self.constraintBottomAttachment.constant = keyboardHeight
  2686. if self.contraintBottomMention.constant > 0 {
  2687. self.contraintBottomMention.constant = 25 + constraintBottomAttachment.constant + self.heightTextFieldSend.constant + self.viewTextfield.bounds.height
  2688. }
  2689. self.keyboardHeightForMention = keyboardHeight
  2690. if isSearching {
  2691. self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
  2692. }
  2693. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2694. self.view.layoutIfNeeded()
  2695. })
  2696. if isSearching {
  2697. self.tableChatView.scrollToBottom()
  2698. } else {
  2699. if (self.currentIndexpath != nil) {
  2700. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  2701. } else {
  2702. self.tableChatView.scrollToBottom()
  2703. }
  2704. }
  2705. }
  2706. } else if isEditingMessage {
  2707. let info:NSDictionary = notification.userInfo! as NSDictionary
  2708. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  2709. let keyboardHeight: CGFloat = keyboardSize.height
  2710. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2711. let constant: CGFloat = 0 - keyboardHeight - 15
  2712. constraintBottomeditTextView.constant = constant
  2713. constraintBottomSendEditTV.constant = constant
  2714. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2715. self.view.layoutIfNeeded()
  2716. })
  2717. }
  2718. }
  2719. private func sendChat(message_scope_id:String = MessageScope.WHISPER, status:String = "1", message_text:String = "", credential:String = "0", attachment_flag: String = "0", ex_blog_id: String = "", message_large_text: String = "", ex_format: String = "", image_id: String = "", audio_id: String = "", video_id: String = "", file_id: String = "", thumb_id: String = "", reff_id: String = "", read_receipts: String = "4", chat_id: String = "", is_call_center: String = "0", call_center_id: String = "", viewController: UIViewController, isAutoSendCC : Bool = false, gif_id: String = "", is_forwarded: Int = 0, is_secret: Int = 0) {
  2720. if viewController is EditorPersonal && file_id == "" && dataMessageForward == nil && !isAutoSendCC{
  2721. if ((textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray && attachment_flag != "11") || textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ) {
  2722. dismissKeyboard()
  2723. viewController.view.makeToast("Write Messages".localized(), duration: 3)
  2724. if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized()) {
  2725. textFieldSend.text = ""
  2726. }
  2727. if (self.heightTextFieldSend.constant != 40) {
  2728. self.heightTextFieldSend.constant = 40
  2729. }
  2730. return
  2731. }
  2732. }
  2733. var reff_id = reff_id
  2734. if (reffId != nil) {
  2735. reff_id = reffId!
  2736. }
  2737. var is_call_center = is_call_center
  2738. var call_center_id = call_center_id
  2739. var l_pin = dataPerson["f_pin"]!!
  2740. var message_scope_id = message_scope_id
  2741. var chat_id = chat_id
  2742. if (isContactCenter) {
  2743. if fPinContacCenter.isEmpty && isRequestContactCenter {
  2744. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintBottomAttachment.constant == 0 {
  2745. textFieldSend.text = "Send message".localized()
  2746. textFieldSend.textColor = UIColor.lightGray
  2747. } else if constraintBottomAttachment.constant != 0 {
  2748. textFieldSend.text = ""
  2749. }
  2750. dismissKeyboard()
  2751. self.view.makeToast("Unable to send message. Waiting for the officer to accept your request".localized(), duration: 3)
  2752. return
  2753. }
  2754. is_call_center = "1"
  2755. call_center_id = complaintId
  2756. l_pin = fPinContacCenter
  2757. message_scope_id = MessageScope.CHATROOM
  2758. chat_id = complaintId
  2759. if isAutoSendCC {
  2760. timeoutCC = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: {_ in
  2761. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  2762. imageView.tintColor = .white
  2763. let banner = FloatingNotificationBanner(title: "Customer doesn't respond in 30 second, so call center session will be ended automatically.".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  2764. banner.show()
  2765. self.endCallCenter()
  2766. })
  2767. }
  2768. }
  2769. var message_text = message_text
  2770. message_text = message_text.replacingOccurrences(of: "\n •", with: "\n•")
  2771. if message_text.hasPrefix(" •") {
  2772. message_text = message_text.replacingOccurrences(of: " •", with: "•")
  2773. }
  2774. let regex = try! NSRegularExpression(pattern: #"(?m)^\s{2}([0-9]+\.)"#)
  2775. message_text = regex.stringByReplacingMatches(in: message_text,
  2776. options: [],
  2777. range: NSRange(location: 0, length: message_text.utf16.count),
  2778. withTemplate: "$1")
  2779. // Check if text contains bullet points or numbered list using regex
  2780. if !message_text.isEmpty {
  2781. message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
  2782. }
  2783. let idMe = User.getMyPin() as String?
  2784. var opposite_pin = ""
  2785. if isContactCenter {
  2786. opposite_pin = ""
  2787. } else {
  2788. opposite_pin = l_pin
  2789. }
  2790. var credential = credential
  2791. if isConfidential {
  2792. credential = "1"
  2793. }
  2794. var read_receipts = read_receipts
  2795. if isAck {
  2796. read_receipts = "8"
  2797. }
  2798. if message_text.contains("@") && listMentionInTextField.count > 0 {
  2799. var diff: Int = 0
  2800. for i in 0..<listMentionInTextField.count {
  2801. let mention = listMentionInTextField[i]
  2802. guard let exBlockStr = mention.ex_block, let exBlock = Int(exBlockStr) else {
  2803. continue // skip if ex_block is nil or not an integer
  2804. }
  2805. let nameWithMention = ("@" + mention.fullName).trimmingCharacters(in: .whitespaces)
  2806. let pinString = "@\(mention.pin)"
  2807. let upperBound = exBlock + diff
  2808. let lowerBound = upperBound - nameWithMention.count + 1
  2809. guard lowerBound >= 0, upperBound < message_text.count else {
  2810. continue // prevent index out-of-range
  2811. }
  2812. var afterMention = ""
  2813. let nextCharIndex = message_text.index(message_text.startIndex, offsetBy: upperBound + 1, limitedBy: message_text.endIndex)
  2814. if let index = nextCharIndex, index < message_text.endIndex {
  2815. let nextChar = message_text[index]
  2816. if nextChar != "\n" && nextChar != " " {
  2817. afterMention = " "
  2818. }
  2819. }
  2820. let startIndex = message_text.index(message_text.startIndex, offsetBy: lowerBound)
  2821. let endIndex = message_text.index(message_text.startIndex, offsetBy: upperBound + 1)
  2822. let range = startIndex..<endIndex
  2823. if message_text[range] == nameWithMention {
  2824. message_text.replaceSubrange(range, with: pinString + afterMention)
  2825. diff += (pinString + afterMention).count - nameWithMention.count
  2826. }
  2827. }
  2828. }
  2829. var is_secret = is_secret
  2830. if isSecret {
  2831. is_secret = 1
  2832. }
  2833. if Nexilis.checkingAccess(key: "message_guard") {
  2834. let guardLite = MessageGuardLite(limits: .defaults())
  2835. var isSanitizedText = false
  2836. var isSanitizedHtml = false
  2837. let res = guardLite.sanitizeText(message_text.data(using: .utf8)!)
  2838. if res.verdict == .sanitized {
  2839. isSanitizedText = true
  2840. }
  2841. if let clean = res.data, let str = String(data: clean, encoding: .utf8) {
  2842. if MessageGuardLite.containsHtmlTags(str) {
  2843. let res2 = guardLite.sanitizeHtml(res.data ?? Data())
  2844. if res2.verdict == .sanitized {
  2845. isSanitizedHtml = true
  2846. }
  2847. if let str2 = String(data: clean, encoding: .utf8), isSanitizedHtml {
  2848. message_text = str2
  2849. }
  2850. } else if isSanitizedText {
  2851. message_text = str
  2852. }
  2853. }
  2854. var protectionType = ""
  2855. if isSanitizedText && isSanitizedHtml {
  2856. protectionType = "text & html"
  2857. } else if isSanitizedText {
  2858. protectionType = "text"
  2859. } else if isSanitizedHtml {
  2860. protectionType = "html"
  2861. }
  2862. if !protectionType.isEmpty {
  2863. DispatchQueue.main.async {
  2864. self.view.makeToast("Your message is protected with sanitized \(protectionType) (Message Guard)".localized(), duration: 3, position: .center)
  2865. }
  2866. }
  2867. }
  2868. sendTyping(l_pin: l_pin, isTyping: true)
  2869. let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin, gif_id: gif_id, isForwarded: "\(is_forwarded)", isSecret: "\(is_secret)", specFile: specFileString)
  2870. Nexilis.addQueueMessage(message: message)
  2871. let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
  2872. if credential == "1" {
  2873. self.listTimerCredential[messageId] = 60
  2874. }
  2875. var row: [String: Any?] = [:]
  2876. row["message_id"] = messageId
  2877. row["f_pin"] = idMe
  2878. row["l_pin"] = dataPerson["f_pin"]!!
  2879. row["message_scope_id"] = message_scope_id
  2880. row["server_date"] = "\(Date().currentTimeMillis())"
  2881. row["status"] = status
  2882. row["message_text"] = message_text
  2883. row["audio_id"] = audio_id
  2884. row["video_id"] = video_id
  2885. row["image_id"] = image_id
  2886. row["thumb_id"] = thumb_id
  2887. row["read_receipts"] = read_receipts
  2888. row["credential"] = credential
  2889. row["chat_id"] = chat_id
  2890. row["file_id"] = file_id
  2891. row["blog_id"] = ex_blog_id
  2892. row["attachment_flag"] = attachment_flag
  2893. row["reff_id"] = reff_id
  2894. row["progress"] = 0.0
  2895. row["lock"] = "0"
  2896. row["is_stared"] = "0"
  2897. row["gif_id"] = gif_id
  2898. row[TypeDataMessage.is_forwarded] = is_forwarded
  2899. row["isSelected"] = false
  2900. row[TypeDataMessage.is_call_center] = is_call_center
  2901. row[TypeDataMessage.call_center_id] = call_center_id
  2902. row[TypeDataMessage.opposite_pin] = opposite_pin
  2903. row[TypeDataMessage.spec_file] = specFileString
  2904. specFileString = ""
  2905. lastTextLength = 0
  2906. if !dataDates.contains("Today".localized()) {
  2907. dataDates.append("Today".localized())
  2908. tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .none)
  2909. }
  2910. row["chat_date"] = "Today".localized()
  2911. self.tableChatView.beginUpdates()
  2912. dataMessages.append(row)
  2913. tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
  2914. self.tableChatView.endUpdates()
  2915. if credential == "1" {
  2916. var timer = Timer()
  2917. var minute = 60
  2918. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  2919. minute -= 1
  2920. self.listTimerCredential[messageId] = minute
  2921. if minute == 0 {
  2922. timer.invalidate()
  2923. self.listTimerCredential.removeValue(forKey: messageId)
  2924. self.timerCredential.removeValue(forKey: messageId)
  2925. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
  2926. if idx != nil {
  2927. self.dataMessages[idx!]["lock"] = "2"
  2928. self.dataMessages[idx!]["reff_id"] = ""
  2929. }
  2930. DispatchQueue.global().async {
  2931. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2932. do {
  2933. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  2934. "lock" : "2"
  2935. ], _where: "message_id = '\(messageId)'")
  2936. } catch {
  2937. rollback.pointee = true
  2938. print("Access database error: \(error.localizedDescription)")
  2939. }
  2940. })
  2941. }
  2942. }
  2943. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  2944. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).firstIndex(where: { $0["message_id"] as? String == messageId})
  2945. if row != nil && section != nil{
  2946. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2947. }
  2948. })
  2949. self.timerCredential[messageId] = timer
  2950. }
  2951. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintBottomAttachment.constant == 0 {
  2952. textFieldSend.text = "Send message".localized()
  2953. textFieldSend.textColor = UIColor.lightGray
  2954. } else if constraintBottomAttachment.constant != 0 {
  2955. textFieldSend.text = ""
  2956. heightTextFieldSend.constant = 40
  2957. }
  2958. deleteReplyView()
  2959. deleteLinkPreview()
  2960. listMentionInTextField.removeAll()
  2961. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  2962. self.tableChatView.scrollToBottom()
  2963. if self.markerCounter != nil {
  2964. let lastMarkerCounter = self.markerCounter
  2965. self.markerCounter = nil
  2966. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  2967. if indexMessage != nil {
  2968. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  2969. 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 })
  2970. if row != nil && section != nil {
  2971. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2972. }
  2973. }
  2974. }
  2975. // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  2976. // self.timerFakeProgress = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
  2977. // self.updateProgress(row as [AnyHashable : Any])
  2978. // if self.fakeProgMultip == self.maxFakeProgMultip {
  2979. // self.timerFakeProgress?.invalidate()
  2980. // self.fakeProgMultip = 0
  2981. // }
  2982. // }
  2983. // }
  2984. }
  2985. @objc func addFriendReqAction(sender: UIButton) {
  2986. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  2987. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2988. imageView.tintColor = .white
  2989. 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)
  2990. banner.show()
  2991. return
  2992. }
  2993. Nexilis.showLoader()
  2994. let lPin = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  2995. let messageId = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  2996. let isAccept = (sender.tag == 0)
  2997. DispatchQueue.global().async {
  2998. if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getAddFriendApproval(lPin: lPin ?? "", isAccept: isAccept), timeout: 5 * 1000) {
  2999. if response.isOk() {
  3000. self.deleteMessage(l_pin: self.dataPerson["f_pin"]!!, message_id: messageId ?? "", scope: MessageScope.WHISPER, type: "1", chat: "")
  3001. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
  3002. if idx != nil {
  3003. self.dataMessages.remove(at: idx!)
  3004. if (idx == self.dataMessages.count - 1) {
  3005. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  3006. }
  3007. for i in 0..<self.dataDates.count {
  3008. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[i] }).count == 0 {
  3009. self.dataDates.remove(at: i)
  3010. }
  3011. }
  3012. }
  3013. DispatchQueue.main.async {
  3014. Nexilis.hideLoader {
  3015. if self.dataMessages.count == 0 {
  3016. if self.fromNotification {
  3017. self.didTapExit()
  3018. } else {
  3019. self.navigationController?.popViewController(animated: true)
  3020. }
  3021. } else {
  3022. self.tableChatView.reloadData()
  3023. }
  3024. UIApplication.shared.visibleViewController?.view.makeToast(sender.tag == 0 ? "Friend request has been accepted".localized() : "Friend request has been rejected".localized(), duration: 3)
  3025. }
  3026. }
  3027. } else {
  3028. Nexilis.hideLoader {
  3029. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  3030. imageView.tintColor = .white
  3031. let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  3032. banner.show()
  3033. }
  3034. }
  3035. } else {
  3036. Nexilis.hideLoader {
  3037. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  3038. imageView.tintColor = .white
  3039. let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  3040. banner.show()
  3041. }
  3042. }
  3043. }
  3044. }
  3045. @objc func ccAction(sender: UIButton) {
  3046. if self.nowSelectedCategoryCC == "CantReturn" {
  3047. if sender.tag == 503 {
  3048. self.view.makeToast("You can't request Call Center more than one".localized(), duration: 3)
  3049. } else if sender.tag == 504 {
  3050. busyCCAction(sender: sender)
  3051. }
  3052. return
  3053. }
  3054. if self.nowSelectedCategoryCC == "endCC" {
  3055. return
  3056. }
  3057. let id = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  3058. let service_id = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  3059. let level = id!.substring(from: 5, to: 5)
  3060. let levelNow = self.nowSelectedCategoryCC.substring(from: 5, to: 5)
  3061. var isRequest = false
  3062. var channel = 0
  3063. var row: [String: Any?] = [:]
  3064. if nowSelectedCategoryCC.isEmpty || level > levelNow {
  3065. if Utils.getDefaultCC() == "No" && !showToastTwiceClick {
  3066. self.view.makeToast("You can press your choice again to change category".localized(), duration: 3)
  3067. showToastTwiceClick = true
  3068. }
  3069. row["message_id"] = ""
  3070. row["chat_date"] = "Today".localized()
  3071. let dataChat: [CategoryCC] = CategoryCC.getDatafromParent(parent: service_id!)
  3072. if dataChat.count != 0 {
  3073. var data : [CategoryCC] = []
  3074. for i in 0..<dataChat.count {
  3075. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(i)", service_id: dataChat[i].service_id, service_name: dataChat[i].service_name, parent: id!, description: dataChat[i].description, is_tablet: dataChat[i].is_tablet))
  3076. }
  3077. row["category_cc"] = data
  3078. } else if dataMessages[Int(level)!]["attachment_flag"] == nil {
  3079. let listStringName: [String] = ["Informasi Umum Produk Call 1500046", "Informasi Spesifik Produk"]
  3080. var data : [CategoryCC] = []
  3081. for i in 0..<listStringName.count {
  3082. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(i)", service_id: service_id!, service_name: listStringName[i], parent: id!, description: "", is_tablet: "0"))
  3083. }
  3084. row["category_cc"] = data
  3085. row["attachment_flag"] = "502"
  3086. } else if dataMessages[Int(level)!]["attachment_flag"] != nil && dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" == "502" {
  3087. if id == "level\(Int(level)!)_0" {
  3088. if let url = URL(string: "tel://1500046") {
  3089. UIApplication.shared.open(url)
  3090. }
  3091. return
  3092. } else {
  3093. let listStringName: [String] = ["Messaging".localized(), "Secure SMS".localized(), "VoIP Call".localized(), "Email".localized(), "Video Call".localized(), "GSM Call".localized(), "GPT Chatbot".localized(), "WhatsApp"]
  3094. // let listStringName: [String] = ["Chat with a Representative".localized(), "Video Call a Representative".localized(), "Call a Representative".localized()]
  3095. var data : [CategoryCC] = []
  3096. let channels : [String] = ["0", "4", "1", "3", "2", "5", "7", "6"]
  3097. for i in 0..<listStringName.count {
  3098. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(channels[i])", service_id: service_id!, service_name: listStringName[i], parent: id!, description: "", is_tablet: "0"))
  3099. }
  3100. row["category_cc"] = data
  3101. row["attachment_flag"] = "503"
  3102. }
  3103. } else {
  3104. channel = Int((id?.components(separatedBy: "_")[1])!)!
  3105. if channel == 1 || channel == 2 {
  3106. if channel == 2 {
  3107. let goAudioCall = Nexilis.checkMicPermission()
  3108. let goVideoCall = Nexilis.checkCameraPermission()
  3109. if goVideoCall == 0 {
  3110. let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 && channel == 2 ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
  3111. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  3112. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  3113. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  3114. }
  3115. }))
  3116. self.navigationController?.present(alert, animated: true, completion: nil)
  3117. return
  3118. } else if goVideoCall == -1 {
  3119. return
  3120. }
  3121. } else if channel == 1 {
  3122. let goAudioCall = Nexilis.checkMicPermission()
  3123. if !goAudioCall{
  3124. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  3125. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  3126. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  3127. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  3128. }
  3129. }))
  3130. self.navigationController?.present(alert, animated: true, completion: nil)
  3131. return
  3132. }
  3133. }
  3134. } else if channel == 3 {
  3135. requestEmailContactCenter(channel)
  3136. return
  3137. } else if channel == 4 {
  3138. requestSMSContactCenter(channel)
  3139. return
  3140. } else if channel == 5 {
  3141. requestGSMCallContactCenter(channel)
  3142. return
  3143. } else if channel == 6 {
  3144. requestWhatsappContactCenter(channel)
  3145. return
  3146. } else if channel == 7 {
  3147. APIS.openSmartChatbot()
  3148. return
  3149. }
  3150. row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
  3151. isRequest = true
  3152. }
  3153. if dataMessages[Int(level)!]["attachment_flag"] == nil || dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" != "503" {
  3154. dataMessages.append(row)
  3155. self.nowSelectedCategoryCC = id!
  3156. self.tableChatView.beginUpdates()
  3157. tableChatView.insertRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
  3158. self.tableChatView.endUpdates()
  3159. }
  3160. } else {
  3161. if id == self.nowSelectedCategoryCC {
  3162. if level == "0" {
  3163. self.nowSelectedCategoryCC = ""
  3164. } else {
  3165. let categoryCC = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
  3166. self.nowSelectedCategoryCC = categoryCC[0].parent
  3167. }
  3168. dataMessages.remove(at: dataMessages.count - 1)
  3169. tableChatView.deleteRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
  3170. tableChatView.reloadData()
  3171. } else {
  3172. return
  3173. }
  3174. }
  3175. if Utils.getDefaultCC() == "No" {
  3176. if sender.backgroundColor != .orangeBNI {
  3177. var button = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
  3178. if dataMessages[Int(level)!]["attachment_flag"] != nil && dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" == "503" {
  3179. button = dataMessages[dataMessages.count - 1]["category_cc"] as! [CategoryCC]
  3180. }
  3181. for i in button {
  3182. if i.id == id! {
  3183. i.isActive = true
  3184. break
  3185. }
  3186. }
  3187. sender.backgroundColor = .orangeBNI
  3188. dataMessages[dataMessages.count - 2]["category_cc"] = button
  3189. } else {
  3190. let button = dataMessages[dataMessages.count - 1]["category_cc"] as! [CategoryCC]
  3191. for i in button {
  3192. if i.id == id! {
  3193. i.isActive = false
  3194. break
  3195. }
  3196. }
  3197. sender.backgroundColor = .clear
  3198. dataMessages[dataMessages.count - 1]["category_cc"] = button
  3199. }
  3200. }
  3201. if isRequest {
  3202. requestContactCenter(channel: channel, service_id: service_id!, row: row)
  3203. } else {
  3204. self.tableChatView.scrollToBottom()
  3205. }
  3206. }
  3207. private func directCC() {
  3208. if channelContactCenter == "1" || channelContactCenter == "2" {
  3209. if channelContactCenter == "2" {
  3210. let goAudioCall = Nexilis.checkMicPermission()
  3211. let goVideoCall = Nexilis.checkCameraPermission()
  3212. if goVideoCall == 0 {
  3213. let alert = LibAlertController(title: "Attention!".localized(), message: !goAudioCall && goVideoCall == 0 && channelContactCenter == "2" ? "Please allow microphone & camera permission in your settings".localized() : !goAudioCall ? "Please allow microphone permission in your settings".localized() : "Please allow camera permission in your settings", preferredStyle: .alert)
  3214. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  3215. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  3216. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  3217. }
  3218. }))
  3219. self.navigationController?.present(alert, animated: true, completion: nil)
  3220. return
  3221. } else if goVideoCall == -1 {
  3222. return
  3223. }
  3224. } else if channelContactCenter == "1" {
  3225. let goAudioCall = Nexilis.checkMicPermission()
  3226. if !goAudioCall{
  3227. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  3228. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  3229. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  3230. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  3231. }
  3232. }))
  3233. self.navigationController?.present(alert, animated: true, completion: nil)
  3234. return
  3235. }
  3236. }
  3237. }
  3238. var row: [String: Any?] = [:]
  3239. row["message_id"] = ""
  3240. row["chat_date"] = "Today".localized()
  3241. row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
  3242. dataMessages.append(row)
  3243. nowSelectedCategoryCC = "CantReturn"
  3244. self.tableChatView.beginUpdates()
  3245. tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .none)
  3246. self.tableChatView.endUpdates()
  3247. requestContactCenter(channel: Int(channelContactCenter)!, service_id: serviceIdCC, row: row)
  3248. }
  3249. @objc func busyCCAction(sender: UIButton) {
  3250. let id = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  3251. let service_id = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  3252. let level = id!.substring(from: 5, to: 5)
  3253. var row: [String: Any?] = [:]
  3254. if id == "level\(Int(level)!)_0" {
  3255. SecureUserDefaults.shared.set(true, forKey: "waitingRequestCC")
  3256. DispatchQueue.global().async {
  3257. let message = CoreMessage_TMessageBank.getQueuingCallCenter(p_channel: Int(self.channelContactCenter)!)
  3258. message.mBodies[CoreMessage_TMessageKey.CATEGORY_ID] = "\(service_id!)"
  3259. _ = Nexilis.writeSync(message: message, timeout: 30 * 1000)
  3260. }
  3261. row["category_cc"] = "Thank you for contacting us,\none of our officers will contact you soon".localized()
  3262. } else {
  3263. row["category_cc"] = "Thank you for being awesome,\nhave a great day!".localized()
  3264. }
  3265. row["message_id"] = ""
  3266. row["chat_date"] = "Today".localized()
  3267. self.nowSelectedCategoryCC = "endCC"
  3268. self.tableChatView.beginUpdates()
  3269. dataMessages.append(row)
  3270. tableChatView.insertRows(at: [IndexPath(row: Int(level)!, section: 0)], with: .none)
  3271. self.tableChatView.endUpdates()
  3272. self.tableChatView.scrollToBottom()
  3273. // DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
  3274. // self.dismiss(animated: true)
  3275. // })
  3276. }
  3277. func requestEmailContactCenter(_ channel: Int){
  3278. let idMe = User.getMyPin() as String?
  3279. let complaintId = "CMP_\(idMe!)_\(String(Date().currentTimeMillis()))EML"
  3280. let message = CoreMessage_TMessageBank.getRequestEmailCallCenter(p_channel: channel)
  3281. if let response = Nexilis.writeSync(message: message) {
  3282. if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") {
  3283. DispatchQueue.main.async {
  3284. let email = response.getBody(key: CoreMessage_TMessageKey.EMAIL, default_value: "")
  3285. let officer = response.getBody(key: CoreMessage_TMessageKey.L_PIN, default_value: "")
  3286. if email.isEmpty {
  3287. self.view.makeToast("Invalid Email Address".localized(), duration: 3)
  3288. return
  3289. }
  3290. // TODO: check if mail available
  3291. Nexilis.openmailAction(to: email)
  3292. }
  3293. }
  3294. }
  3295. }
  3296. func requestSMSContactCenter(_ channel: Int){
  3297. // let idMe = User.getMyPin()!
  3298. // let complaintId = "CMP_\(idMe)_\(String(Date().currentTimeMillis()))SMS"
  3299. // isRequestContactCenter = true
  3300. var phone = Utils.getSMSCenter()
  3301. if phone.substring(from: 0, to: 0) == "0" {
  3302. phone = "+62" + phone.substring(from: 1, to: phone.count)
  3303. }
  3304. APIS.sendSMS(phoneNumber: phone)
  3305. // let tmessage = TMessage()
  3306. // tmessage.mCode = CoreMessage_TMessageCode.ACCEPT_CALL_CENTER
  3307. // tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
  3308. // tmessage.mPIN = idMe
  3309. // tmessage.mBodies[CoreMessage_TMessageKey.F_PIN] = me
  3310. // tmessage.mBodies[CoreMessage_TMessageKey.UPLINE_PIN] = me
  3311. // tmessage.mBodies[CoreMessage_TMessageKey.CHANNEL] = channel
  3312. // tmessage.mBodies[CoreMessage_TMessageKey.CALL_CENTER_ID] = complaint_id
  3313. }
  3314. func requestGSMCallContactCenter(_ channel: Int){
  3315. var phone = Utils.getCallCenter()
  3316. if phone.substring(from: 0, to: 0) == "0" {
  3317. phone = "+62" + phone.substring(from: 1, to: phone.count)
  3318. }
  3319. if let url = URL(string: "tel://\(phone)") {
  3320. UIApplication.shared.open(url)
  3321. }
  3322. }
  3323. func requestWhatsappContactCenter(_ channel: Int){
  3324. var phone = Utils.getWhatsappCenter()
  3325. if phone.substring(from: 0, to: 0) == "0" {
  3326. phone = "+62" + phone.substring(from: 1, to: phone.count)
  3327. }
  3328. APIS.sendWhatsapp(phoneNumber: phone)
  3329. }
  3330. func requestContactCenter(channel: Int, service_id: String, row: [String: Any?]) {
  3331. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  3332. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  3333. imageView.tintColor = .white
  3334. 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)
  3335. banner.show()
  3336. return
  3337. }
  3338. DispatchQueue.global().async {
  3339. let message = CoreMessage_TMessageBank.getRequestCallCenter(p_channel: channel, category_id: service_id)
  3340. message.mBodies["wlc_device"] = "\(UIDevice.current.model)(\(UIDevice.current.name))"
  3341. message.mBodies["wlc_time"] = "\(Date().currentTimeMillis())"
  3342. message.mBodies["wlc_longitude"] = "\(self.longitude)"
  3343. message.mBodies["wlc_latitude"] = "\(self.latitude)"
  3344. if !self.contextCC.isEmpty {
  3345. let dataSplit = self.contextCC.components(separatedBy: "~")
  3346. var activity = ""
  3347. var titleErr = ""
  3348. if dataSplit.count > 1 {
  3349. activity = dataSplit[1]
  3350. }
  3351. if dataSplit.count > 2 {
  3352. titleErr = dataSplit[2]
  3353. }
  3354. message.mBodies["wlc_activity"] = "\(activity)"
  3355. message.mBodies["wlc_error_description"] = "\(titleErr)"
  3356. }
  3357. if let response = Nexilis.writeSync(message: message) {
  3358. if !self.isDirectCC {
  3359. DispatchQueue.main.async {
  3360. self.dataMessages.append(row)
  3361. self.nowSelectedCategoryCC = "CantReturn"
  3362. self.tableChatView.beginUpdates()
  3363. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  3364. self.tableChatView.endUpdates()
  3365. self.tableChatView.scrollToBottom()
  3366. }
  3367. }
  3368. if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") {
  3369. DispatchQueue.main.async {
  3370. SecureUserDefaults.shared.set(true, forKey: "waitingRequestCC")
  3371. let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "")
  3372. if data.isEmpty {
  3373. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  3374. var row: [String: Any?] = [:]
  3375. row["message_id"] = ""
  3376. row["chat_date"] = "Today".localized()
  3377. row["attachment_flag"] = "504"
  3378. let listStringName: [String] = ["Yes".localized(), "No".localized()]
  3379. var data : [CategoryCC] = []
  3380. for i in 0..<listStringName.count {
  3381. data.append(CategoryCC(id: "level\(self.dataMessages.count + 1)_\(i)", service_id: service_id, service_name: listStringName[i], parent: "", description: "", is_tablet: "0"))
  3382. }
  3383. row["category_cc"] = data
  3384. self.dataMessages.append(row)
  3385. self.channelContactCenter = "\(channel)"
  3386. self.tableChatView.beginUpdates()
  3387. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  3388. self.tableChatView.endUpdates()
  3389. self.tableChatView.scrollToBottom()
  3390. } else {
  3391. self.fPinContacCenter = data
  3392. }
  3393. }
  3394. } else {
  3395. DispatchQueue.main.async {
  3396. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  3397. var row: [String: Any?] = [:]
  3398. row["message_id"] = ""
  3399. row["chat_date"] = "Today".localized()
  3400. row["attachment_flag"] = "504"
  3401. let listStringName: [String] = ["Yes".localized(), "No".localized()]
  3402. var data : [CategoryCC] = []
  3403. for i in 0..<listStringName.count {
  3404. data.append(CategoryCC(id: "level\(self.dataMessages.count + 1)_\(i)", service_id: service_id, service_name: listStringName[i], parent: "", description: "", is_tablet: "0"))
  3405. }
  3406. row["category_cc"] = data
  3407. self.dataMessages.append(row)
  3408. self.channelContactCenter = "\(channel)"
  3409. self.tableChatView.beginUpdates()
  3410. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  3411. self.tableChatView.endUpdates()
  3412. self.tableChatView.scrollToBottom()
  3413. }
  3414. }
  3415. }
  3416. }
  3417. }
  3418. private func sendReadMessageStatus(chat_id: String, f_pin: String, message_scope_id: String, message_id: String) {
  3419. let message = CoreMessage_TMessageBank.getUpdateRead(p_chat_id: chat_id, p_f_pin: f_pin, p_scope_id: message_scope_id, qty: 1)
  3420. let fPin = message.getBody(key: CoreMessage_TMessageKey.F_PIN)
  3421. let scope = message.getBody(key: CoreMessage_TMessageKey.SCOPE_ID)
  3422. message.mBodies[CoreMessage_TMessageKey.SERVER_DATE] = String(Date().currentTimeMillis())
  3423. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3424. let valueListGroupImages = listGroupImages.value
  3425. message.mStatus = CoreMessage_TMessageUtil.getTID()
  3426. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  3427. var mId = ""
  3428. for i in 0..<valueListGroupImages.count {
  3429. if mId.isEmpty {
  3430. mId = "-2,\(valueListGroupImages[i].messageId)"
  3431. } else {
  3432. mId = mId + "," + valueListGroupImages[i].messageId
  3433. }
  3434. }
  3435. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = mId
  3436. } else {
  3437. message.mStatus = CoreMessage_TMessageUtil.getTID()
  3438. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  3439. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
  3440. }
  3441. if (fPin.elementsEqual("-999") || scope.elementsEqual("16") || scope.elementsEqual("15")){
  3442. return
  3443. }
  3444. DispatchQueue.global().async {
  3445. var isBackground = true
  3446. while isBackground {
  3447. DispatchQueue.main.sync {
  3448. isBackground = API.nGetCLXConnState() == 0 || !API.bInetConnAvailable() || APIS.checkAppStateisBackground()
  3449. }
  3450. if isBackground {
  3451. Thread.sleep(forTimeInterval: 1.0)
  3452. } else {
  3453. if let resp = Nexilis.writeAndWait(message: message) {
  3454. if resp.isOk() {
  3455. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3456. let valueListGroupImages = listGroupImages.value
  3457. for i in 0..<valueListGroupImages.count {
  3458. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3459. do {
  3460. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3461. "status" : "4"
  3462. ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
  3463. } catch {
  3464. rollback.pointee = true
  3465. print("Access database error: \(error.localizedDescription)")
  3466. }
  3467. })
  3468. }
  3469. } else {
  3470. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3471. do {
  3472. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3473. "status" : "4"
  3474. ], _where: "message_id = '\(message_id)'")
  3475. } catch {
  3476. rollback.pointee = true
  3477. print("Access database error: \(error.localizedDescription)")
  3478. }
  3479. })
  3480. }
  3481. } else {
  3482. DispatchQueue.main.sync {
  3483. self.sendReadMessageStatus(chat_id: chat_id, f_pin: fPin, message_scope_id: message_scope_id, message_id: message_id)
  3484. }
  3485. }
  3486. } else {
  3487. DispatchQueue.main.sync {
  3488. self.sendReadMessageStatus(chat_id: chat_id, f_pin: fPin, message_scope_id: message_scope_id, message_id: message_id)
  3489. }
  3490. }
  3491. }
  3492. }
  3493. }
  3494. if let index = dataMessages.firstIndex(where: {$0["message_id"] as? String == message_id}) {
  3495. dataMessages[index]["status"] = "4"
  3496. let auto: Bool = SecureUserDefaults.shared.value(forKey: "autoDownload") ?? false
  3497. if auto {
  3498. if dataMessages[index]["image_id"] as? String != nil && !((dataMessages[index]["image_id"] as? String)!.isEmpty) {
  3499. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3500. let valueListGroupImages = listGroupImages.value
  3501. for i in 0..<valueListGroupImages.count {
  3502. Download().startHTTP(forKey:valueListGroupImages[i].imageId) { (name, progress) in
  3503. guard progress == 100 else {
  3504. return
  3505. }
  3506. do {
  3507. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3508. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3509. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3510. if let dirPath = paths.first {
  3511. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(valueListGroupImages[i].imageId)
  3512. if FileManager.default.fileExists(atPath: imageURL.path) {
  3513. let image = UIImage(contentsOfFile: imageURL.path)
  3514. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3515. if save {
  3516. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3517. }
  3518. }
  3519. else if FileEncryption.shared.isSecureExists(filename: valueListGroupImages[i].imageId) {
  3520. if var secureData = try FileEncryption.shared.readSecure(filename: valueListGroupImages[i].imageId) {
  3521. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  3522. if dataDecrypt != nil {
  3523. secureData = dataDecrypt!
  3524. }
  3525. let image = UIImage(data: secureData)
  3526. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3527. if save {
  3528. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3529. }
  3530. }
  3531. }
  3532. }
  3533. }
  3534. catch {
  3535. }
  3536. DispatchQueue.main.async { [self] in
  3537. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3538. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3539. if row != nil && section != nil{
  3540. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3541. }
  3542. }
  3543. }
  3544. }
  3545. } else {
  3546. Download().startHTTP(forKey:dataMessages[index]["image_id"] as? String ?? "") { (name, progress) in
  3547. guard progress == 100 else {
  3548. return
  3549. }
  3550. do {
  3551. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3552. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3553. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3554. if let dirPath = paths.first {
  3555. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["image_id"] as? String ?? "")
  3556. if FileManager.default.fileExists(atPath: imageURL.path) {
  3557. let image = UIImage(contentsOfFile: imageURL.path)
  3558. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3559. if save {
  3560. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3561. }
  3562. }
  3563. else if FileEncryption.shared.isSecureExists(filename: self.dataMessages[index]["image_id"] as? String ?? "") {
  3564. if var secureData = try FileEncryption.shared.readSecure(filename: self.dataMessages[index]["image_id"] as? String ?? "") {
  3565. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  3566. if dataDecrypt != nil {
  3567. secureData = dataDecrypt!
  3568. }
  3569. let image = UIImage(data: secureData)
  3570. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3571. if save {
  3572. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3573. }
  3574. }
  3575. }
  3576. }
  3577. } catch {
  3578. }
  3579. DispatchQueue.main.async { [self] in
  3580. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3581. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3582. if row != nil && section != nil{
  3583. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3584. }
  3585. }
  3586. }
  3587. }
  3588. } else if dataMessages[index]["video_id"] as? String != nil && !((dataMessages[index]["video_id"] as? String)!.isEmpty){
  3589. Download().startHTTP(forKey: dataMessages[index]["video_id"] as? String ?? "") { (name, progress) in
  3590. guard progress == 100 else {
  3591. return
  3592. }
  3593. do {
  3594. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3595. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3596. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3597. if let dirPath = paths.first {
  3598. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["video_id"] as? String ?? "")
  3599. if FileManager.default.fileExists(atPath: videoURL.path) {
  3600. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3601. if save {
  3602. PHPhotoLibrary.shared().performChanges({
  3603. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
  3604. }) { saved, error in
  3605. }
  3606. }
  3607. }
  3608. else if FileEncryption.shared.isSecureExists(filename: self.dataMessages[index]["video_id"] as? String ?? "") {
  3609. if var secureData = try FileEncryption.shared.readSecure(filename: self.dataMessages[index]["video_id"] as? String ?? "") {
  3610. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  3611. if dataDecrypt != nil {
  3612. secureData = dataDecrypt!
  3613. }
  3614. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  3615. let tempPath = cachesDirectory.appendingPathComponent(name)
  3616. try secureData.write(to: tempPath)
  3617. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3618. if save {
  3619. PHPhotoLibrary.shared().performChanges({
  3620. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
  3621. }) { saved, error in
  3622. }
  3623. }
  3624. }
  3625. }
  3626. }
  3627. } catch {
  3628. }
  3629. DispatchQueue.main.async { [self] in
  3630. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3631. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3632. if row != nil && section != nil{
  3633. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3634. }
  3635. }
  3636. }
  3637. }
  3638. else if dataMessages[index]["file_id"] as? String != nil && !((dataMessages[index]["file_id"] as? String)!.isEmpty) {
  3639. Download().startHTTP(forKey: dataMessages[index]["file_id"] as? String ?? "") { (name, progress) in
  3640. guard progress == 100 else {
  3641. return
  3642. }
  3643. DispatchQueue.main.async { [self] in
  3644. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3645. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3646. if row != nil && section != nil{
  3647. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3648. }
  3649. }
  3650. }
  3651. }
  3652. }
  3653. }
  3654. }
  3655. private func sendTyping(l_pin: String, isTyping: Bool = false) {
  3656. DispatchQueue.global().async {
  3657. let tmessage = CoreMessage_TMessageBank.getUpdateTypingStatus(p_opposite: l_pin, p_scope: MessageScope.WHISPER, p_status: isTyping ? "3": "4")
  3658. _ = Nexilis.write(message: tmessage)
  3659. }
  3660. }
  3661. private func getCounter() {
  3662. Database.shared.database?.inTransaction({ fmdb, rollback in
  3663. if let c = Database().getRecords(fmdb: fmdb, query: "SELECT counter FROM MESSAGE_SUMMARY where l_pin='\(dataPerson["f_pin"]!!)'"), c.next() {
  3664. counter = Int(c.int(forColumnIndex: 0))
  3665. c.close()
  3666. }
  3667. })
  3668. }
  3669. private func updateCounter(counter: Int) {
  3670. DispatchQueue.global().async {
  3671. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3672. do {
  3673. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", cvalues: [
  3674. "counter" : "\(counter)"
  3675. ], _where: "l_pin = '\(self.dataPerson["f_pin"]!!)'")
  3676. } catch {
  3677. rollback.pointee = true
  3678. print("Access database error: \(error.localizedDescription)")
  3679. }
  3680. })
  3681. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  3682. }
  3683. }
  3684. private func addButtonScrollToBottom() {
  3685. if tableChatView.alpha != 1 || isSearching {
  3686. return
  3687. }
  3688. self.view.addSubview(buttonScrollToBottom)
  3689. buttonScrollToBottom.translatesAutoresizingMaskIntoConstraints = false
  3690. NSLayoutConstraint.activate([
  3691. buttonScrollToBottom.bottomAnchor.constraint(equalTo: buttonSendChat.topAnchor, constant: -50),
  3692. buttonScrollToBottom.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  3693. buttonScrollToBottom.widthAnchor.constraint(equalToConstant: 60),
  3694. buttonScrollToBottom.heightAnchor.constraint(equalToConstant: 30.0)
  3695. ])
  3696. buttonScrollToBottom.backgroundColor = .greenColor
  3697. buttonScrollToBottom.setImage(UIImage(systemName: "chevron.down.circle"), for: .normal)
  3698. buttonScrollToBottom.imageView?.contentMode = .scaleAspectFit
  3699. buttonScrollToBottom.imageView?.tintColor = .white
  3700. buttonScrollToBottom.contentVerticalAlignment = .fill
  3701. buttonScrollToBottom.contentHorizontalAlignment = .fill
  3702. buttonScrollToBottom.imageEdgeInsets.top = 2.0
  3703. buttonScrollToBottom.imageEdgeInsets.bottom = 2.0
  3704. buttonScrollToBottom.layer.cornerRadius = 10.0
  3705. buttonScrollToBottom.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
  3706. buttonScrollToBottom.clipsToBounds = true
  3707. buttonScrollToBottom.addTarget(self, action: #selector(scrollTobottomAction), for: .touchUpInside)
  3708. }
  3709. private func addCounterAtButttonScrollToBottom() {
  3710. if tableChatView.alpha != 1 || isSearching {
  3711. return
  3712. }
  3713. self.view.addSubview(indicatorCounterBSTB)
  3714. indicatorCounterBSTB.translatesAutoresizingMaskIntoConstraints = false
  3715. indicatorCounterBSTB.backgroundColor = .systemRed
  3716. indicatorCounterBSTB.layer.cornerRadius = 7.5
  3717. indicatorCounterBSTB.clipsToBounds = true
  3718. indicatorCounterBSTB.layer.borderWidth = 0.5
  3719. indicatorCounterBSTB.layer.borderColor = UIColor.secondaryColor.cgColor
  3720. NSLayoutConstraint.activate([
  3721. indicatorCounterBSTB.bottomAnchor.constraint(equalTo: buttonScrollToBottom.topAnchor, constant: 5),
  3722. indicatorCounterBSTB.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -50),
  3723. indicatorCounterBSTB.widthAnchor.constraint(greaterThanOrEqualToConstant: 15),
  3724. indicatorCounterBSTB.heightAnchor.constraint(equalToConstant: 15)
  3725. ])
  3726. indicatorCounterBSTB.addSubview(labelCounter)
  3727. labelCounter.translatesAutoresizingMaskIntoConstraints = false
  3728. NSLayoutConstraint.activate([
  3729. labelCounter.leadingAnchor.constraint(equalTo: indicatorCounterBSTB.leadingAnchor, constant: 2),
  3730. labelCounter.trailingAnchor.constraint(equalTo: indicatorCounterBSTB.trailingAnchor, constant: -2),
  3731. labelCounter.centerXAnchor.constraint(equalTo: indicatorCounterBSTB.centerXAnchor),
  3732. ])
  3733. labelCounter.font = UIFont.systemFont(ofSize: 11)
  3734. labelCounter.text = "\(counter)"
  3735. labelCounter.textColor = .secondaryColor
  3736. labelCounter.textAlignment = .center
  3737. }
  3738. @objc func scrollTobottomAction() {
  3739. tableChatView.scrollToBottom()
  3740. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [self] in
  3741. if buttonScrollToBottom.isDescendant(of: self.view) {
  3742. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  3743. buttonScrollToBottom.removeFromSuperview()
  3744. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3745. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3746. indicatorCounterBSTB.removeFromSuperview()
  3747. }
  3748. }
  3749. }
  3750. }
  3751. private func checkNewMessage(tableView: UITableView) {
  3752. // let indexPathFirst = tableView.indexPathsForVisibleRows?.first
  3753. // if indexPathFirst != nil {
  3754. // let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPathFirst!.section] })
  3755. // if self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPathFirst!.row]["message_id"] as? String }) == 0 && !gettingDataMessage {
  3756. // gettingDataMessage = true
  3757. // addDataMessage()
  3758. // }
  3759. // }
  3760. currentIndexpath = tableView.indexPathsForVisibleRows?.last
  3761. let indexFirst = tableView.indexPathsForVisibleRows?.first
  3762. if indexFirst != nil {
  3763. let dataMessages = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  3764. if dataMessages.count == 0 || dataMessages.count - 1 < currentIndexpath!.row {
  3765. return
  3766. }
  3767. let contentHeight = tableView.contentSize.height
  3768. let scrollViewHeight = tableView.frame.height
  3769. let fullContentOffset = contentHeight - scrollViewHeight
  3770. let contentOffsetY = tableView.contentOffset.y
  3771. if ((currentIndexpath!.section == dataDates.count - 1 && indexFirst!.row != dataMessages.count - 1) || indexFirst!.section != dataDates.count - 1) && fullContentOffset - contentOffsetY > 100 {
  3772. if !buttonScrollToBottom.isDescendant(of: self.view) {
  3773. addButtonScrollToBottom()
  3774. addCounterAtButttonScrollToBottom()
  3775. }
  3776. } else if (indexFirst!.section == dataDates.count - 1 && indexFirst!.row == dataMessages.count - 1) || fullContentOffset - contentOffsetY < 50 {
  3777. if buttonScrollToBottom.isDescendant(of: self.view) {
  3778. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  3779. buttonScrollToBottom.removeFromSuperview()
  3780. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3781. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3782. indicatorCounterBSTB.removeFromSuperview()
  3783. }
  3784. }
  3785. }
  3786. // let indexPathFirst = tableChatView.indexPathsForVisibleRows?.first
  3787. // if indexPathFirst != nil && listViewOnSection.count != 0 && listViewOnSection.count - 1 >= indexPathFirst!.section {
  3788. // let headerView = listViewOnSection[indexPathFirst!.section]
  3789. // if headerView.isHidden {
  3790. // headerView.isHidden = false
  3791. // }
  3792. // }
  3793. var listData = dataMessages[0...currentIndexpath!.row]
  3794. listData = listData.filter({$0["status"] as? String != "4" && $0["status"] as? String != "8"})
  3795. if listData.count != 0 && !isContactCenter {
  3796. let idMe = User.getMyPin() as String?
  3797. for i in 0...listData.count - 1 {
  3798. if listData[i]["f_pin"] as? String != idMe {
  3799. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: listData[i]["message_id"] as? String ?? "")
  3800. }
  3801. }
  3802. }
  3803. }
  3804. if counter == 0 {
  3805. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3806. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3807. indicatorCounterBSTB.removeFromSuperview()
  3808. }
  3809. updateCounter(counter: 0)
  3810. } else if counter != 0 && currentIndexpath != nil {
  3811. let dataFilter = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  3812. if dataFilter.count == 0 {
  3813. return
  3814. }
  3815. let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == dataFilter[currentIndexpath!.row]["message_id"] as? String})
  3816. if idx == nil {
  3817. return
  3818. }
  3819. if (dataMessages.count - counter) <= idx! {
  3820. let countUpdate = idx! - (dataMessages.count - counter)
  3821. counter = counter - (countUpdate + 1)
  3822. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3823. labelCounter.text = "\(counter)"
  3824. }
  3825. updateCounter(counter: counter)
  3826. }
  3827. }
  3828. }
  3829. }
  3830. //EPV
  3831. extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewControllerDelegate {
  3832. public func didSelect(imagevideo: Any?) {
  3833. if (imagevideo != nil) {
  3834. let imageVideoData = imagevideo as! [UIImagePickerController.InfoKey: Any]
  3835. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3836. previewImageVC.imageVideoData = imageVideoData
  3837. if (textFieldSend.textColor != .lightGray) {
  3838. previewImageVC.currentTextTextField = textFieldSend.text
  3839. }
  3840. previewImageVC.modalPresentationStyle = .custom
  3841. previewImageVC.delegate = self
  3842. previewImageVC.isAck = self.isAck
  3843. previewImageVC.isConfidential = self.isConfidential
  3844. previewImageVC.isCC = self.isContactCenter
  3845. self.present(previewImageVC, animated: true, completion: nil)
  3846. }
  3847. }
  3848. public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  3849. if !isBlackCancelButton {
  3850. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  3851. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  3852. }
  3853. guard let result = results.first else {
  3854. picker.dismiss(animated: true, completion: nil)
  3855. return
  3856. }
  3857. if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
  3858. picker.dismiss(animated: true, completion: {
  3859. Nexilis.showLoader(text: "Preparing...".localized())
  3860. result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
  3861. if error != nil {
  3862. self.loadAnimatedMedia(from: result.itemProvider) { data, isGIF in
  3863. guard let data = data else {
  3864. print("Failed to load media")
  3865. return
  3866. }
  3867. DispatchQueue.main.async {
  3868. Nexilis.hideLoader() {
  3869. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3870. if (self.textFieldSend.textColor != .lightGray) {
  3871. previewImageVC.currentTextTextField = self.textFieldSend.text
  3872. }
  3873. if isGIF {
  3874. previewImageVC.fromCopy = true
  3875. previewImageVC.isGIF = true
  3876. previewImageVC.dataGIF = data
  3877. previewImageVC.modalPresentationStyle = .custom
  3878. previewImageVC.delegate = self
  3879. previewImageVC.isAck = self.isAck
  3880. previewImageVC.isConfidential = self.isConfidential
  3881. } else {
  3882. let fileManager = FileManager.default
  3883. let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
  3884. let destinationURL = documentsDirectory.appendingPathComponent(UUID().uuidString + ".mov")
  3885. do {
  3886. try data.write(to: destinationURL)
  3887. previewImageVC.modalPresentationStyle = .custom
  3888. previewImageVC.urlVideoPhpPicker = destinationURL
  3889. previewImageVC.delegate = self
  3890. previewImageVC.isAck = self.isAck
  3891. previewImageVC.isConfidential = self.isConfidential
  3892. previewImageVC.isCC = self.isContactCenter
  3893. } catch {
  3894. }
  3895. }
  3896. self.present(previewImageVC, animated: true, completion: nil)
  3897. }
  3898. }
  3899. }
  3900. } else if let data = data {
  3901. DispatchQueue.main.async {
  3902. Nexilis.hideLoader() {
  3903. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3904. if (self.textFieldSend.textColor != .lightGray) {
  3905. previewImageVC.currentTextTextField = self.textFieldSend.text
  3906. }
  3907. previewImageVC.fromCopy = true
  3908. previewImageVC.isGIF = true
  3909. previewImageVC.dataGIF = data
  3910. previewImageVC.modalPresentationStyle = .custom
  3911. previewImageVC.delegate = self
  3912. previewImageVC.isAck = self.isAck
  3913. previewImageVC.isConfidential = self.isConfidential
  3914. self.present(previewImageVC, animated: true, completion: nil)
  3915. }
  3916. }
  3917. }
  3918. }
  3919. })
  3920. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
  3921. picker.dismiss(animated: true, completion: {
  3922. Nexilis.showLoader(text: "Preparing...".localized())
  3923. result.itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image") { data, error in
  3924. if let data = data {
  3925. do {
  3926. DispatchQueue.main.async {
  3927. Nexilis.hideLoader {
  3928. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3929. if (self.textFieldSend.textColor != .lightGray) {
  3930. previewImageVC.currentTextTextField = self.textFieldSend.text
  3931. }
  3932. previewImageVC.fromCopy = true
  3933. previewImageVC.image = UIImage(data: data)
  3934. previewImageVC.modalPresentationStyle = .custom
  3935. previewImageVC.delegate = self
  3936. previewImageVC.isAck = self.isAck
  3937. previewImageVC.isConfidential = self.isConfidential
  3938. self.present(previewImageVC, animated: true, completion: nil)
  3939. }
  3940. }
  3941. } catch {
  3942. print("Error loading image data: \(error)")
  3943. }
  3944. } else {
  3945. print("Error: \(String(describing: error))")
  3946. }
  3947. }
  3948. })
  3949. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
  3950. picker.dismiss(animated: true, completion: {
  3951. Nexilis.showLoader(text: "Preparing...".localized())
  3952. result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { url, error in
  3953. if let url = url {
  3954. let fileManager = FileManager.default
  3955. let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
  3956. var nameFile = url.lastPathComponent
  3957. if nameFile.contains("&uuid"){
  3958. nameFile = UUID().uuidString + ".mov"
  3959. }
  3960. let destinationURL = documentsDirectory.appendingPathComponent(nameFile)
  3961. do {
  3962. if fileManager.fileExists(atPath: destinationURL.path) {
  3963. try fileManager.removeItem(at: destinationURL)
  3964. }
  3965. try fileManager.copyItem(at: url, to: destinationURL)
  3966. DispatchQueue.main.async {
  3967. Nexilis.hideLoader {
  3968. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3969. if (self.textFieldSend.textColor != .lightGray) {
  3970. previewImageVC.currentTextTextField = self.textFieldSend.text
  3971. }
  3972. previewImageVC.modalPresentationStyle = .custom
  3973. previewImageVC.urlVideoPhpPicker = destinationURL
  3974. previewImageVC.delegate = self
  3975. previewImageVC.isAck = self.isAck
  3976. previewImageVC.isConfidential = self.isConfidential
  3977. previewImageVC.isCC = self.isContactCenter
  3978. self.present(previewImageVC, animated: true, completion: nil)
  3979. }
  3980. }
  3981. } catch {
  3982. print("Error copying video file: \(error.localizedDescription)")
  3983. }
  3984. }
  3985. }
  3986. })
  3987. }
  3988. }
  3989. func loadAnimatedMedia(from provider: NSItemProvider, completion: @escaping (Data?, Bool) -> Void) {
  3990. // First: real GIF
  3991. if provider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
  3992. provider.loadFileRepresentation(forTypeIdentifier: "com.compuserve.gif") { url, error in
  3993. if let url = url, let data = try? Data(contentsOf: url) {
  3994. completion(data, true) // true = isGIF
  3995. } else {
  3996. // fallback
  3997. self.loadQuickTimeMovie(from: provider, completion: completion)
  3998. }
  3999. }
  4000. } else {
  4001. // fallback directly
  4002. self.loadQuickTimeMovie(from: provider, completion: completion)
  4003. }
  4004. }
  4005. private func loadQuickTimeMovie(from provider: NSItemProvider, completion: @escaping (Data?, Bool) -> Void) {
  4006. if provider.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") {
  4007. provider.loadFileRepresentation(forTypeIdentifier: "com.apple.quicktime-movie") { url, error in
  4008. if let url = url, let data = try? Data(contentsOf: url) {
  4009. completion(data, false) // false = it's MOV, not GIF
  4010. } else {
  4011. completion(nil, false)
  4012. }
  4013. }
  4014. } else {
  4015. completion(nil, false)
  4016. }
  4017. }
  4018. func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController, specFile: String) {
  4019. specFileString = specFile
  4020. 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)
  4021. }
  4022. }
  4023. //EQL
  4024. extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPreviewControllerDataSource {
  4025. public func didSelectDocument(document: Any?) {
  4026. if (document != nil) {
  4027. self.previewItem = (document as! [URL])[0] as NSURL
  4028. specFileString = ""
  4029. let previewController = QLPreviewController()
  4030. previewController.dataSource = self
  4031. let vcHandleFile = UIViewController()
  4032. let nc = UINavigationController(rootViewController: vcHandleFile)
  4033. let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
  4034. let navBarAppearance = UINavigationBarAppearance()
  4035. nc.defaultStyle()
  4036. nc.modalPresentationStyle = .pageSheet
  4037. navBarAppearance.configureWithOpaqueBackground()
  4038. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
  4039. navBarAppearance.titleTextAttributes = attributes
  4040. nc.navigationBar.standardAppearance = navBarAppearance
  4041. nc.navigationBar.scrollEdgeAppearance = navBarAppearance
  4042. let backButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
  4043. vcHandleFile.navigationItem.leftBarButtonItem = backButton
  4044. let sendButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
  4045. buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
  4046. buttonSpec.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
  4047. buttonSpec.addTarget(self, action: #selector(showConfigurationFile), for: .touchUpInside)
  4048. let barButtonItemSpec = UIBarButtonItem(customView: buttonSpec)
  4049. vcHandleFile.navigationItem.rightBarButtonItems = [sendButton, barButtonItemSpec]
  4050. backButton.navigation = nc
  4051. sendButton.navigation = nc
  4052. if let viewVc = vcHandleFile.view {
  4053. vcHandleFile.title = self.previewItem?.lastPathComponent
  4054. vcHandleFile.addChild(previewController)
  4055. previewController.dataSource = self
  4056. previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
  4057. previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  4058. viewVc.addSubview(previewController.view)
  4059. previewController.didMove(toParent: vcHandleFile)
  4060. self.present(nc, animated: true)
  4061. }
  4062. }
  4063. }
  4064. @objc private func showConfigurationFile() {
  4065. let modalVC = UIViewController()
  4066. if let viewModal = modalVC.view {
  4067. viewModal.backgroundColor = .whiteBubbleColor
  4068. let closeButton = UIButton(type: .close)
  4069. viewModal.addSubview(closeButton)
  4070. closeButton.anchor(top: viewModal.topAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingRight: 15, width: 30, height: 30)
  4071. closeButton.layer.cornerRadius = 15
  4072. closeButton.clipsToBounds = true
  4073. closeButton.backgroundColor = .lightGray.withAlphaComponent(0.1)
  4074. let config = UIImage.SymbolConfiguration(pointSize: 18, weight: .semibold)
  4075. closeButton.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
  4076. closeButton.addAction(UIAction { _ in
  4077. modalVC.dismiss(animated: true)
  4078. }, for: .touchUpInside)
  4079. let imageSpec = UIButton(type: .custom)
  4080. viewModal.addSubview(imageSpec)
  4081. imageSpec.anchor(top: viewModal.topAnchor, left: viewModal.leftAnchor, paddingTop: 25, paddingLeft: 15, width: 40, height: 40)
  4082. imageSpec.layer.cornerRadius = 20
  4083. imageSpec.clipsToBounds = true
  4084. imageSpec.backgroundColor = .lightGray.withAlphaComponent(0.1)
  4085. imageSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 35, height: 35)), for: .normal)
  4086. let title = UILabel()
  4087. title.text = "Option for Attachment".localized()
  4088. viewModal.addSubview(title)
  4089. title.anchor(top: viewModal.topAnchor, left: imageSpec.rightAnchor, paddingTop: 23, paddingLeft: 10)
  4090. title.textColor = .label
  4091. title.font = .boldSystemFont(ofSize: 16)
  4092. let subtitle = UILabel()
  4093. subtitle.text = "Select option :".localized()
  4094. viewModal.addSubview(subtitle)
  4095. subtitle.anchor(top: title.bottomAnchor, left: imageSpec.rightAnchor, paddingLeft: 10)
  4096. subtitle.textColor = .gray
  4097. subtitle.font = .systemFont(ofSize: 14)
  4098. tableViewConfigFile = UITableView()
  4099. viewModal.addSubview(tableViewConfigFile)
  4100. tableViewConfigFile.backgroundColor = .white
  4101. tableViewConfigFile.layer.cornerRadius = 8.0
  4102. tableViewConfigFile.clipsToBounds = true
  4103. tableViewConfigFile.anchor(top: imageSpec.bottomAnchor, left: viewModal.leftAnchor, bottom: viewModal.bottomAnchor, right: viewModal.rightAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 80, paddingRight: 15)
  4104. tableViewConfigFile.register(UITableViewCell.self, forCellReuseIdentifier: "cellConfigFile")
  4105. tableViewConfigFile.dataSource = self
  4106. tableViewConfigFile.delegate = self
  4107. tableViewConfigFile.separatorStyle = .singleLine
  4108. tableViewConfigFile.tableFooterView = UIView()
  4109. if #available(iOS 15.0, *) {
  4110. tableViewConfigFile.sectionHeaderTopPadding = 0
  4111. }
  4112. if #available(iOS 15.0, *) {
  4113. if let sheet = modalVC.sheetPresentationController {
  4114. sheet.detents = [.medium()]
  4115. }
  4116. } else {
  4117. // Fallback on earlier versions
  4118. }
  4119. }
  4120. UIApplication.shared.visibleViewController?.present(modalVC, animated: true)
  4121. }
  4122. @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
  4123. sender.navigation.dismiss(animated: true, completion: nil)
  4124. }
  4125. @objc private func sendDocument(sender: navigationQLPreviewDocument) {
  4126. DispatchQueue.global().async {
  4127. if Nexilis.checkingAccess(key: "content_inspection") {
  4128. DispatchQueue.main.async {
  4129. Nexilis.showLoader(text: "Scanning File...".localized())
  4130. }
  4131. let result = (self.previewItem! as URL).validateFile()
  4132. DispatchQueue.main.async {
  4133. Nexilis.hideLoader {
  4134. sender.navigation.dismiss(animated: true, completion: {
  4135. if result == 1 {
  4136. sendIt()
  4137. } else {
  4138. APIS.showWarningFile(type: result)
  4139. }
  4140. })
  4141. }
  4142. }
  4143. } else {
  4144. DispatchQueue.main.async {
  4145. sendIt()
  4146. }
  4147. }
  4148. func sendIt() {
  4149. sender.navigation.dismiss(animated: true, completion: nil)
  4150. guard let previewItem = self.previewItem else { return }
  4151. guard var dataFile = try? Data(contentsOf: previewItem as URL) else { return }
  4152. func sanitizeFile(mimeType: String, sanitizeAction: (Data) -> MessageGuardLite.Result) -> Data? {
  4153. DispatchQueue.main.async {
  4154. Nexilis.showLoader(text: "Sanitizing your \(mimeType.contains("pdf") ? "pdf file" : "image") (Message Guard)".localized())
  4155. }
  4156. let res = sanitizeAction(dataFile)
  4157. defer {
  4158. DispatchQueue.main.async { Nexilis.hideLoader {} }
  4159. }
  4160. if res.verdict == .block {
  4161. DispatchQueue.main.async {
  4162. Nexilis.hideLoader {
  4163. APIS.showMessageGuardFile(mime: res.mime)
  4164. }
  4165. }
  4166. return nil
  4167. }
  4168. return res.data ?? Data()
  4169. }
  4170. func processIt(with data: Data) {
  4171. guard let urlFile = self.previewItem?.absoluteString else { return }
  4172. let originalFileName = (urlFile as NSString).lastPathComponent.removingPercentEncoding ?? "file"
  4173. let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_\(originalFileName)"
  4174. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  4175. let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
  4176. if !FileManager.default.fileExists(atPath: fileURL.path) {
  4177. try? data.write(to: fileURL)
  4178. }
  4179. DispatchQueue.main.async {
  4180. self.sendChat(
  4181. message_text: "\(originalFileName)|",
  4182. attachment_flag: "6",
  4183. file_id: renamedNameFile,
  4184. viewController: self
  4185. )
  4186. }
  4187. }
  4188. if Nexilis.checkingAccess(key: "message_guard") {
  4189. DispatchQueue.global().async {
  4190. let guardLite = MessageGuardLite(limits: .defaults())
  4191. let mimeType = MessageGuardLite.sniffMime(dataFile)
  4192. if mimeType == "image/png" || mimeType == "image/jpeg" {
  4193. if let sanitized = sanitizeFile(mimeType: mimeType, sanitizeAction: guardLite.sanitizeImage) {
  4194. dataFile = sanitized
  4195. } else { return }
  4196. } else if mimeType == "application/pdf" {
  4197. if let sanitized = sanitizeFile(mimeType: mimeType, sanitizeAction: guardLite.sanitizePdf) {
  4198. dataFile = sanitized
  4199. } else { return }
  4200. }
  4201. processIt(with: dataFile)
  4202. }
  4203. } else {
  4204. processIt(with: dataFile)
  4205. }
  4206. }
  4207. }
  4208. }
  4209. public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  4210. return self.previewItem != nil ? 1 : 0
  4211. }
  4212. public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  4213. return self.previewItem!
  4214. }
  4215. }
  4216. //ETV
  4217. extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
  4218. func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?) {
  4219. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  4220. previewImageVC.image = image
  4221. previewImageVC.isGIF = image == nil
  4222. previewImageVC.fromCopy = true
  4223. previewImageVC.dataGIF = dataGIF
  4224. previewImageVC.currentTextTextField = textFieldSend.text
  4225. previewImageVC.modalPresentationStyle = .custom
  4226. previewImageVC.delegate = self
  4227. previewImageVC.isAck = self.isAck
  4228. previewImageVC.isConfidential = self.isConfidential
  4229. self.present(previewImageVC, animated: true, completion: nil)
  4230. }
  4231. public func textViewDidChangeSelection(_ textView: UITextView) {
  4232. lastPositionCursorMention = textView.selectedRange.location
  4233. var isShowMention = false
  4234. let fulltextForMention = textView.text.prefix(lastPositionCursorMention)
  4235. let lines = fulltextForMention.split(separator: "\n")
  4236. if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
  4237. let words = lines[lastLineIndex].split(separator: " ")
  4238. if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
  4239. let mentionText = words[lastWordIndex]
  4240. let lastChar = fulltextForMention.last
  4241. if lastChar != "\n" && lastChar != " " {
  4242. if mentionText.starts(with: "@") || (mentionText.count >= 2 && (self.textFieldSend.textColor != UIColor.lightGray || heightTableEditMention != nil) && extractFromAtIfSymbolsBefore(String(mentionText)) == nil) {
  4243. showMention(text: mentionText.starts(with: "@") ? String(mentionText.dropFirst()) : String(mentionText))
  4244. isShowMention = true
  4245. } else if let textM = extractFromAtIfSymbolsBefore(String(mentionText)) {
  4246. showMention(text: String(textM.dropFirst()))
  4247. isShowMention = true
  4248. }
  4249. }
  4250. }
  4251. }
  4252. if !isShowMention {
  4253. hideMention()
  4254. }
  4255. if var nowTextFieldSend = self.textFieldSend {
  4256. if isEditingMessage {
  4257. nowTextFieldSend = editTextView
  4258. }
  4259. if let sr = nowTextFieldSend.selectedTextRange {
  4260. if let fnt = nowTextFieldSend.font {
  4261. let cursorPosition = textView.caretRect(for: sr.start).origin
  4262. let doubleCurrentLine = cursorPosition.y / fnt.lineHeight
  4263. if doubleCurrentLine.isFinite {
  4264. let currentLine = Int(ceil(doubleCurrentLine))
  4265. UIView.animate(withDuration: 0.3) {
  4266. let layoutManager = textView.layoutManager
  4267. var numberOfLines = 0
  4268. var index = 0
  4269. let numberOfGlyphs = layoutManager.numberOfGlyphs
  4270. while index < numberOfGlyphs {
  4271. var lineRange = NSRange()
  4272. layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
  4273. index = NSMaxRange(lineRange)
  4274. numberOfLines += 1
  4275. }
  4276. if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
  4277. if self.isEditingMessage {
  4278. self.constraintHeighteditTextView.constant = 40
  4279. } else {
  4280. self.heightTextFieldSend.constant = 40
  4281. }
  4282. } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
  4283. if self.isEditingMessage {
  4284. self.constraintHeighteditTextView.constant = 95.0
  4285. } else {
  4286. self.heightTextFieldSend.constant = 95.0
  4287. }
  4288. } else if currentLine < 4 && numberOfLines < 5 {
  4289. if (nowTextFieldSend.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend.contentSize.height) {
  4290. if self.isEditingMessage {
  4291. self.constraintHeighteditTextView.constant = nowTextFieldSend.contentSize.height
  4292. } else {
  4293. self.heightTextFieldSend.constant = nowTextFieldSend.contentSize.height
  4294. }
  4295. }
  4296. }
  4297. }
  4298. }
  4299. }
  4300. }
  4301. }
  4302. if self.isEditingMessage && textView == editTextView {
  4303. if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  4304. buttonSendEdit.isEnabled = false
  4305. } else if !buttonSendEdit.isEnabled {
  4306. buttonSendEdit.isEnabled = true
  4307. }
  4308. }
  4309. //indention code:
  4310. let text = textView.text ?? ""
  4311. let cursorLocation = textView.selectedRange.location
  4312. // Find current line range where cursor is
  4313. if let lineRange = (text as NSString).lineRange(for: NSRange(location: cursorLocation, length: 0)) as NSRange? {
  4314. let line = (text as NSString).substring(with: lineRange)
  4315. // Detect bullet (" •") or numbered (" 1.") list
  4316. if line.hasPrefix(" •") || line.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  4317. var bulletEnd = lineRange.location + 2
  4318. if !line.hasPrefix(" •") {
  4319. bulletEnd = lineRange.location + 3
  4320. }
  4321. // Prevent cursor before bullet/number
  4322. if cursorLocation < bulletEnd {
  4323. DispatchQueue.main.async {
  4324. textView.selectedRange = NSRange(location: bulletEnd, length: 0)
  4325. }
  4326. }
  4327. }
  4328. }
  4329. }
  4330. func extractFromAtIfSymbolsBefore(_ text: String) -> String? {
  4331. guard let atIndex = text.firstIndex(of: "@") else {
  4332. return nil
  4333. }
  4334. let beforeAt = text[..<atIndex]
  4335. let afterAt = text[atIndex...]
  4336. // Define symbols as anything that's not a letter or digit
  4337. let symbolSet = CharacterSet.letters.union(.decimalDigits).inverted
  4338. let isAllSymbols = beforeAt.unicodeScalars.allSatisfy { symbolSet.contains($0) }
  4339. return isAllSymbols ? String(afterAt) : nil
  4340. }
  4341. public func textViewDidChange(_ textView: UITextView) {
  4342. if textView.text.count == 0 {
  4343. isAlwaysHideLinkPreview = false
  4344. }
  4345. if allowTyping {
  4346. allowTyping = false
  4347. if isContactCenter && !fPinContacCenter.isEmpty {
  4348. sendTyping(l_pin: fPinContacCenter, isTyping: true)
  4349. } else {
  4350. sendTyping(l_pin: dataPerson["f_pin"]!!, isTyping: true)
  4351. }
  4352. DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
  4353. self.allowTyping = true
  4354. })
  4355. }
  4356. timerCheckLink?.invalidate()
  4357. timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
  4358. self.checkLink(fullText: textView.text)
  4359. })
  4360. let text = textView.text ?? ""
  4361. let cursorPosition = textView.selectedRange.location
  4362. let tempListMention = listMentionInTextField
  4363. if listMentionInTextField.count > 0 {
  4364. for j in 0..<listMentionInTextField.count {
  4365. var index = j
  4366. if tempListMention.count != listMentionInTextField.count {
  4367. index = j - (tempListMention.count - listMentionInTextField.count)
  4368. }
  4369. var upper = (Int(listMentionInTextField[index].ex_block ?? "0") ?? 0)
  4370. if cursorPosition <= upper {
  4371. upper += text.count - lastTextLength
  4372. listMentionInTextField[index].ex_block = "\(upper)"
  4373. }
  4374. let lower = upper - listMentionInTextField[index].fullName.count
  4375. let name = listMentionInTextField[index].fullName.trimmingCharacters(in: .whitespaces)
  4376. if textView.text.substring(from: lower, to: upper) != "@\(name)" {
  4377. listMentionInTextField.remove(at: index)
  4378. }
  4379. }
  4380. }
  4381. //indention code:
  4382. // Handle Bullets (- [space] + letter → • )
  4383. let bulletPattern = #"(?<=\n|^)- (\S)"#
  4384. if let match = text.range(of: bulletPattern, options: .regularExpression) {
  4385. let matchedText = text[match]
  4386. if let spaceIndex = matchedText.firstIndex(of: " ") {
  4387. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  4388. let replacedText = text.replacingOccurrences(of: matchedText, with: " • \(firstLetter)", range: match)
  4389. let newCursorPosition = cursorPosition + 2 // Adjust cursor position
  4390. textView.text = replacedText
  4391. DispatchQueue.main.async {
  4392. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  4393. }
  4394. }
  4395. }
  4396. // Handle Numbered Lists (e.g., "1. " [space] + letter → " 1.")
  4397. let numberPattern = #"(?<=\n|^)(\d+)\. (\S)"# // Matches "1. X"
  4398. if let match = text.range(of: numberPattern, options: .regularExpression) {
  4399. let matchedText = text[match]
  4400. let replacedText = text.replacingOccurrences(of: matchedText, with: " \(matchedText)", range: match)
  4401. let newCursorPosition = cursorPosition + 2 // Adjust cursor
  4402. textView.text = replacedText
  4403. DispatchQueue.main.async {
  4404. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  4405. }
  4406. }
  4407. handleRichText(textView)
  4408. lastTextLength = text.count
  4409. }
  4410. private func showMention(text: String) {
  4411. if self.contraintBottomMention.constant < 0 {
  4412. if !isEditingMessage {
  4413. self.contraintBottomMention.constant = 25 + constraintBottomAttachment.constant + self.heightTextFieldSend.constant + self.viewTextfield.bounds.height
  4414. UIView.animate(withDuration: 0.5, animations: {
  4415. self.view.layoutIfNeeded()
  4416. })
  4417. }
  4418. }
  4419. listMentionWithText.removeAll()
  4420. Database.shared.database?.inTransaction({ fmdb, rollback in
  4421. do {
  4422. if Utils.getGPTBotName().lowercased().contains(text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()) || text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  4423. let gptUser = User(pin: "-997",
  4424. firstName: Utils.getGPTBotName(),
  4425. lastName: "",
  4426. thumb: "",
  4427. userType: "0",
  4428. official: "1")
  4429. listMentionWithText.insert(gptUser, at: 0)
  4430. }
  4431. listMentionWithText.removeAll(where: { listMentionInTextField.contains($0) })
  4432. var nowTableMention = tableMention!
  4433. var nowHeightTableMention = heightTableMention!
  4434. if isEditingMessage {
  4435. nowTableMention = tableMentionEdit
  4436. if heightTableEditMention != nil {
  4437. nowHeightTableMention = heightTableEditMention
  4438. } else {
  4439. return
  4440. }
  4441. }
  4442. if listMentionWithText.count > 0 {
  4443. if listMentionWithText.count < 5 {
  4444. nowHeightTableMention.constant = CGFloat(44 * listMentionWithText.count)
  4445. } else {
  4446. nowHeightTableMention.constant = 44 * 4
  4447. }
  4448. nowTableMention.reloadData()
  4449. } else {
  4450. nowHeightTableMention.constant = 44
  4451. self.hideMention()
  4452. }
  4453. } catch {
  4454. rollback.pointee = true
  4455. print("Access database error: \(error.localizedDescription)")
  4456. }
  4457. })
  4458. }
  4459. private func hideMention() {
  4460. if self.contraintBottomMention.constant > 0 {
  4461. listMentionWithText.removeAll()
  4462. tableMention.reloadData()
  4463. self.contraintBottomMention.constant = 0 - self.heightTableMention.constant
  4464. UIView.animate(withDuration: 0.5, animations: {
  4465. self.view.layoutIfNeeded()
  4466. })
  4467. } else if self.heightTableEditMention != nil && self.heightTableEditMention.constant != 0 {
  4468. listMentionWithText.removeAll()
  4469. tableMentionEdit.reloadData()
  4470. self.heightTableEditMention.constant = 0
  4471. }
  4472. }
  4473. private func checkLink(fullText: String) {
  4474. if !isAlwaysHideLinkPreview {
  4475. var text = ""
  4476. let listTextSplitBreak = fullText.components(separatedBy: "\n")
  4477. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  4478. if indexFirstLinkSplitBreak != nil {
  4479. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  4480. 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) })
  4481. if indexFirstLinkSplitSpace != nil {
  4482. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  4483. }
  4484. }
  4485. if !text.isEmpty {
  4486. var stringURl = text
  4487. if stringURl.starts(with: "www.") {
  4488. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  4489. }
  4490. var dataURL = ""
  4491. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4492. do {
  4493. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
  4494. while cursor.next() {
  4495. if let data = cursor.string(forColumnIndex: 0) {
  4496. dataURL = data
  4497. }
  4498. }
  4499. cursor.close()
  4500. }
  4501. } catch {
  4502. rollback.pointee = true
  4503. print("Access database error: \(error.localizedDescription)")
  4504. }
  4505. })
  4506. if !dataURL.isEmpty {
  4507. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  4508. let imageUrl = data["imageUrl"] as? String
  4509. let link = data["link"] as? String ?? ""
  4510. if imageUrl == nil || (link.contains("youtube.com") && link.contains("watch?v=") && !imageUrl!.contains("img.youtube.com/vi/")) {
  4511. dataURL = ""
  4512. }
  4513. }
  4514. }
  4515. if !dataURL.isEmpty {
  4516. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  4517. let title = data["title"] as? String ?? ""
  4518. let description = data["description"] as? String ?? ""
  4519. let imageUrl = data["imageUrl"] as? String
  4520. if self.showingLink != text {
  4521. self.showingLink = text
  4522. self.deleteLinkPreview()
  4523. if !textFieldSend.text.isEmpty || textFieldSend.text.contains(text){
  4524. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  4525. }
  4526. }
  4527. }
  4528. } else {
  4529. let urlConfig = URLSessionConfiguration.default
  4530. let sessionDelegate = SelfSignedURLSessionDelegate()
  4531. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  4532. let slp = SwiftLinkPreview(session: session,
  4533. workQueue: SwiftLinkPreview.defaultWorkQueue,
  4534. responseQueue: DispatchQueue.main,
  4535. cache: DisabledCache.instance)
  4536. let preview = slp.preview(stringURl,
  4537. onSuccess: { result in
  4538. print("MASUK SINI KAH? :\(result)")
  4539. if result.title == nil {
  4540. self.checkLink(fullText: fullText)
  4541. return
  4542. }
  4543. let title = result.title?.trimmingCharacters(in: .whitespacesAndNewlines)
  4544. .nilIfEmpty ?? URL(string: text)?.host ?? "Untitled"
  4545. let description: String
  4546. if text.contains("google.com") {
  4547. description = "" // special rule for google
  4548. } else {
  4549. description = result.description?.trimmingCharacters(in: .whitespacesAndNewlines)
  4550. .nilIfEmpty ?? ""
  4551. }
  4552. let imageUrl = self.youtubeThumbnail(from: text)
  4553. ?? result.image
  4554. ?? result.icon
  4555. ?? ""
  4556. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4557. do {
  4558. var dataJson: [String: Any] = [:]
  4559. dataJson["title"] = title
  4560. dataJson["description"] = description
  4561. dataJson["imageUrl"] = imageUrl
  4562. dataJson["link"] = text
  4563. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  4564. return
  4565. }
  4566. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  4567. "id" : "\(Date().currentTimeMillis().toHex())",
  4568. "link" : text,
  4569. "data_link" : json,
  4570. "retry": 0
  4571. ], replace: true)
  4572. } catch {
  4573. rollback.pointee = true
  4574. print("Access database error: \(error.localizedDescription)")
  4575. }
  4576. })
  4577. if self.showingLink != text {
  4578. self.showingLink = text
  4579. self.deleteLinkPreview()
  4580. if !self.textFieldSend.text.isEmpty || self.textFieldSend.text.contains(text){
  4581. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  4582. }
  4583. }
  4584. },
  4585. onError: { error in
  4586. print("onError? :\(error)")
  4587. self.deleteLinkPreview()
  4588. })
  4589. }
  4590. } else {
  4591. deleteLinkPreview()
  4592. }
  4593. }
  4594. }
  4595. func youtubeThumbnail(from url: String) -> String? {
  4596. guard let url = URL(string: url) else { return nil }
  4597. let host = url.host ?? ""
  4598. if host.contains("youtube.com"),
  4599. let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems,
  4600. let videoId = queryItems.first(where: { $0.name == "v" })?.value {
  4601. return "https://img.youtube.com/vi/\(videoId)/hqdefault.jpg"
  4602. }
  4603. if host.contains("youtu.be") {
  4604. let videoId = url.lastPathComponent
  4605. return "https://img.youtube.com/vi/\(videoId)/hqdefault.jpg"
  4606. }
  4607. return nil
  4608. }
  4609. private func buildPreviewLink(imageUrl: String?, title: String, description: String?, stringURl: String) {
  4610. if !self.viewTextfield.subviews.contains(self.containerLink){
  4611. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  4612. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
  4613. if self.contraintBottomMention.constant > 0 {
  4614. self.contraintBottomMention.constant = self.contraintBottomMention.constant + 80 + self.heightTextFieldSend.constant
  4615. }
  4616. }, completion: nil)
  4617. }
  4618. self.viewTextfield.addSubview(self.containerLink)
  4619. self.containerLink.translatesAutoresizingMaskIntoConstraints = false
  4620. self.containerLink.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  4621. self.containerLink.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor).isActive = true
  4622. self.containerLink.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  4623. self.containerLink.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  4624. self.containerLink.backgroundColor = .secondaryColor
  4625. if self.reffId != nil {
  4626. self.bottomAnchorPreviewReply.isActive = false
  4627. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  4628. self.bottomAnchorPreviewReply.isActive = true
  4629. }
  4630. let imagePreview = UIImageView()
  4631. if imageUrl != nil {
  4632. self.containerLink.addSubview(imagePreview)
  4633. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  4634. imagePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor).isActive = true
  4635. imagePreview.bottomAnchor.constraint(equalTo: self.containerLink.bottomAnchor).isActive = true
  4636. imagePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor).isActive = true
  4637. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  4638. imagePreview.loadImageAsync(with: imageUrl)
  4639. imagePreview.contentMode = .scaleAspectFit
  4640. }
  4641. let titlePreview = UILabel()
  4642. self.containerLink.addSubview(titlePreview)
  4643. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  4644. if imageUrl != nil {
  4645. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  4646. } else {
  4647. titlePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  4648. }
  4649. titlePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor, constant: 25.0).isActive = true
  4650. titlePreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  4651. titlePreview.text = title
  4652. titlePreview.font = UIFont.systemFont(ofSize: 14.0 + offset(), weight: .bold)
  4653. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  4654. let descPreview = UILabel()
  4655. self.containerLink.addSubview(descPreview)
  4656. descPreview.translatesAutoresizingMaskIntoConstraints = false
  4657. if imageUrl != nil {
  4658. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  4659. } else {
  4660. descPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  4661. }
  4662. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  4663. descPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  4664. descPreview.text = description
  4665. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  4666. descPreview.textColor = .gray
  4667. descPreview.numberOfLines = 1
  4668. let linkPreview = UILabel()
  4669. self.containerLink.addSubview(linkPreview)
  4670. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  4671. if imageUrl != nil {
  4672. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  4673. } else {
  4674. linkPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  4675. }
  4676. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor).isActive = true
  4677. linkPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  4678. linkPreview.text = stringURl
  4679. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  4680. linkPreview.textColor = .gray
  4681. linkPreview.numberOfLines = 1
  4682. let cancelPreview = UIButton(type: .custom)
  4683. self.containerLink.addSubview(cancelPreview)
  4684. cancelPreview.translatesAutoresizingMaskIntoConstraints = false
  4685. cancelPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -10).isActive = true
  4686. cancelPreview.centerYAnchor.constraint(equalTo: self.containerLink.centerYAnchor).isActive = true
  4687. cancelPreview.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  4688. cancelPreview.addTarget(nil, action: #selector(self.removeLinkPreviewUntilEmptyTextView), for: .touchUpInside)
  4689. cancelPreview.backgroundColor = .clear
  4690. cancelPreview.tintColor = .mainColor
  4691. }
  4692. public func textViewDidBeginEditing(_ textView: UITextView) {
  4693. if textView.textColor == UIColor.lightGray {
  4694. textView.text = nil
  4695. textView.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  4696. }
  4697. }
  4698. public func textViewDidEndEditing(_ textView: UITextView) {
  4699. if textView.text.isEmpty && textView != editTextView {
  4700. textView.text = "Send message".localized()
  4701. textView.textColor = UIColor.lightGray
  4702. }
  4703. }
  4704. public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  4705. if listMentionInTextField.count > 0 {
  4706. for i in 0..<listMentionInTextField.count {
  4707. if lastPositionCursorMention == Int(listMentionInTextField[i].ex_block!)! + 1 {
  4708. let fulltextForMention = textView.text.substring(from: 0, to: lastPositionCursorMention - 1)
  4709. let diff = textView.text.count - fulltextForMention.count
  4710. var text = textView.text ?? ""
  4711. let nameMention = listMentionInTextField[i].fullName.trimmingCharacters(in: .whitespaces)
  4712. let rangeReplacement = NSRange(location: lastPositionCursorMention - nameMention.count - 1, length: nameMention.count + 1)
  4713. let replacementText = ""
  4714. let copyAttributedText = text.richText(isEditing: true, listMentionInTextField: listMentionInTextField)
  4715. copyAttributedText.removeAttribute(.foregroundColor, range: rangeReplacement)
  4716. textView.attributedText = copyAttributedText
  4717. // Replace the old text with the new text using the replaceSubrange(_:with:) method
  4718. if let startIndex = text.index(text.startIndex, offsetBy: rangeReplacement.location, limitedBy: text.endIndex),
  4719. let endIndex = text.index(startIndex, offsetBy: rangeReplacement.length, limitedBy: text.endIndex) {
  4720. text.replaceSubrange(startIndex..<endIndex, with: replacementText)
  4721. }
  4722. listMentionInTextField.remove(at: i)
  4723. textView.attributedText = text.richText(isEditing: true, listMentionInTextField: listMentionInTextField)
  4724. let newPosition = textView.position(from: textView.beginningOfDocument, offset: textView.text.count - diff)
  4725. textView.selectedTextRange = textView.textRange(from: newPosition!, to: newPosition!)
  4726. textViewDidChangeSelection(textView)
  4727. handleRichText(textView)
  4728. return false
  4729. }
  4730. }
  4731. }
  4732. let indent = handleIndent(textView, range, text)
  4733. if !indent {
  4734. handleRichText(textView)
  4735. return indent
  4736. }
  4737. if (self.textFieldSend.text.count == 0) {
  4738. return text != "\n"
  4739. }
  4740. return true
  4741. }
  4742. private func handleIndent(_ textView: UITextView, _ range: NSRange, _ text: String) -> Bool {
  4743. guard let nsText = textView.text as NSString? else { return true }
  4744. let newText = nsText.replacingCharacters(in: range, with: text)
  4745. var lines = newText.components(separatedBy: "\n")
  4746. // Ensure range location is valid, considering Unicode scalars
  4747. guard let textRange = Range(range, in: textView.text) else { return true }
  4748. let prefixText = textView.text[..<textRange.lowerBound]
  4749. let affectedLineIndex = prefixText.components(separatedBy: "\n").count - 1
  4750. guard affectedLineIndex >= 0, affectedLineIndex < lines.count else { return true }
  4751. let affectedLine = lines[affectedLineIndex]
  4752. // Auto-indent new lines based on previous line
  4753. if text == "\n" {
  4754. let previousLine = lines[affectedLineIndex]
  4755. if previousLine.hasPrefix(" •") {
  4756. let newBullet = "\n • "
  4757. textView.text = nsText.replacingCharacters(in: range, with: newBullet)
  4758. DispatchQueue.main.async {
  4759. textView.selectedRange = NSRange(location: range.location + newBullet.utf16.count, length: 0)
  4760. }
  4761. return false
  4762. }
  4763. if let match = previousLine.range(of: #"^\s{2}(\d+)\."#, options: .regularExpression),
  4764. let numberMatch = previousLine[match].components(separatedBy: ".").first,
  4765. let number = Int(numberMatch.trimmingCharacters(in: .whitespaces)) {
  4766. let newNumber = "\n \(number + 1). "
  4767. textView.text = nsText.replacingCharacters(in: range, with: newNumber)
  4768. DispatchQueue.main.async {
  4769. textView.selectedRange = NSRange(location: range.location + newNumber.utf16.count, length: 0)
  4770. }
  4771. return false
  4772. }
  4773. }
  4774. // Handle Backspace on Empty Bullet (Convert " • " → "- ")
  4775. if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
  4776. lines[affectedLineIndex] = "- " // Replace " • " with "- "
  4777. textView.text = lines.joined(separator: "\n")
  4778. DispatchQueue.main.async {
  4779. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  4780. }
  4781. return false
  4782. }
  4783. // Handle Backspace on bullet
  4784. if text.isEmpty, newText.hasPrefix(" •"), newText.substring(with: NSRange(location: range.location - 1, length: 2)) == " •" {
  4785. return false
  4786. }
  4787. // Handle Backspace on Numbered List
  4788. if text.isEmpty, newText.hasPrefix(" "), newText.substring(with: NSRange(location: range.location - 2, length: 2)) == " " {
  4789. lines[affectedLineIndex] = affectedLine.trimmingCharacters(in: .whitespaces)
  4790. textView.text = lines.joined(separator: "\n")
  4791. DispatchQueue.main.async {
  4792. textView.selectedRange = NSRange(location: range.location - 2, length: 0)
  4793. }
  4794. return false
  4795. }
  4796. return true
  4797. }
  4798. private func handleRichText(_ textView: UITextView) {
  4799. textView.attributedText = textView.text.richText(isEditing: true, listMentionInTextField: self.listMentionInTextField)
  4800. }
  4801. func isGIFData(_ data: Data) -> Bool {
  4802. let gifSignature: [UInt8] = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]
  4803. let rawData = [UInt8](data.prefix(6))
  4804. return rawData == gifSignature
  4805. }
  4806. public func textView(_ textView: UITextView, shouldInteractWith URL: URL?, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  4807. var urlString: String?
  4808. if let url = URL {
  4809. urlString = url.absoluteString
  4810. } else {
  4811. if let range = Range(characterRange, in: textView.text) {
  4812. let tappedText = String(textView.text[range])
  4813. urlString = tappedText
  4814. }
  4815. }
  4816. guard let finalURL = urlString else {
  4817. return false
  4818. }
  4819. switch interaction {
  4820. case .invokeDefaultAction:
  4821. let gesture = ObjectGesture()
  4822. gesture.message_id = finalURL
  4823. tapMessageText(gesture)
  4824. return false
  4825. case .presentActions:
  4826. UIPasteboard.general.string = finalURL
  4827. self.view.makeToast("Link Copied".localized(), duration: 3)
  4828. return false
  4829. case .preview:
  4830. return true
  4831. @unknown default:
  4832. return true
  4833. }
  4834. }
  4835. }
  4836. //EUC
  4837. extension EditorPersonal: UIContextMenuInteractionDelegate {
  4838. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
  4839. if showMenuContext {
  4840. showMenuContext = false
  4841. interaction.view!.removeInteraction(interaction)
  4842. }
  4843. }
  4844. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
  4845. if textFieldSend.isFirstResponder {
  4846. textFieldSend.resignFirstResponder()
  4847. }
  4848. let indexPath = self.tableChatView.indexPathForRow(at: interaction.view!.convert(location, to: self.tableChatView))
  4849. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  4850. var star: UIAction
  4851. if (dataMessages[indexPath!.row]["is_stared"] as? String ?? "" == "0") {
  4852. star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star"), handler: {(_) in
  4853. if self.removed {
  4854. return
  4855. }
  4856. DispatchQueue.global().async {
  4857. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4858. do {
  4859. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4860. "is_stared" : 1
  4861. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  4862. } catch {
  4863. rollback.pointee = true
  4864. print("Access database error: \(error.localizedDescription)")
  4865. }
  4866. })
  4867. }
  4868. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4869. if idx != nil{
  4870. self.dataMessages[idx!]["is_stared"] = "1"
  4871. }
  4872. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4873. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  4874. })
  4875. } else {
  4876. star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash"), handler: {(_) in
  4877. if self.removed {
  4878. return
  4879. }
  4880. DispatchQueue.global().async {
  4881. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4882. do {
  4883. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4884. "is_stared" : 0
  4885. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  4886. } catch {
  4887. rollback.pointee = true
  4888. print("Access database error: \(error.localizedDescription)")
  4889. }
  4890. })
  4891. }
  4892. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4893. if idx != nil{
  4894. self.dataMessages[idx!]["is_stared"] = "0"
  4895. }
  4896. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4897. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  4898. })
  4899. }
  4900. let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left"), handler: {(_) in
  4901. if self.removed {
  4902. return
  4903. }
  4904. if self.isSearching {
  4905. self.cancelAction()
  4906. }
  4907. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  4908. self.handleReply(indexPath: indexPath!)
  4909. })
  4910. })
  4911. var pin: UIAction
  4912. if (dataMessages[indexPath!.row][TypeDataMessage.is_pinned] as? String ?? "0" == "0") {
  4913. pin = UIAction(title: "Pin".localized(), image: UIImage(systemName: "pin"), handler: {(_) in
  4914. if self.removed {
  4915. return
  4916. }
  4917. if self.isSearching {
  4918. self.cancelAction()
  4919. }
  4920. var checkDataPinned = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  4921. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  4922. if checkDataPinned.count == 3 {
  4923. let alert = UIAlertController(title: "Replace oldest pin?".localized(),
  4924. message: "Your pin will replace the oldest one.".localized(),
  4925. preferredStyle: .alert)
  4926. alert.addAction(UIAlertAction(title: "Continue", style: .default) { _ in
  4927. proceedPinned(replace: true)
  4928. })
  4929. alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
  4930. })
  4931. self.present(alert, animated: true, completion: nil)
  4932. } else {
  4933. proceedPinned()
  4934. }
  4935. })
  4936. func proceedPinned(replace: Bool = false) {
  4937. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  4938. DispatchQueue.main.async {
  4939. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  4940. imageView.tintColor = .white
  4941. 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)
  4942. banner.show()
  4943. }
  4944. return
  4945. }
  4946. if replace {
  4947. checkDataPinned.sort {
  4948. let firstPinned = Int64($0[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  4949. let secondPinned = Int64($1[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  4950. return firstPinned < secondPinned
  4951. }
  4952. self.proceedPinUnpinMessage(checkDataPinned: checkDataPinned[0], isPinned: false) { res1 in
  4953. if res1 {
  4954. self.proceedPinUnpinMessage(checkDataPinned: dataMessages[indexPath!.row], isPinned: true) { res2 in
  4955. if res2 {
  4956. let dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  4957. DispatchQueue.main.async {
  4958. self.pinAllMessages(dataMessages: dataMessagesPin)
  4959. }
  4960. }
  4961. }
  4962. }
  4963. }
  4964. } else {
  4965. self.proceedPinUnpinMessage(checkDataPinned: dataMessages[indexPath!.row], isPinned: true) { res in
  4966. if res {
  4967. let dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  4968. DispatchQueue.main.async {
  4969. self.pinAllMessages(dataMessages: dataMessagesPin)
  4970. }
  4971. }
  4972. }
  4973. }
  4974. }
  4975. })
  4976. } else {
  4977. pin = UIAction(title: "Unpin".localized(), image: UIImage(systemName: "pin.slash"), handler: {(_) in
  4978. if self.removed {
  4979. return
  4980. }
  4981. if self.isSearching {
  4982. self.cancelAction()
  4983. }
  4984. var checkDataPinned = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  4985. checkDataPinned.sort {
  4986. let firstPinned = Int64($0[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  4987. let secondPinned = Int64($1[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  4988. return firstPinned < secondPinned
  4989. }
  4990. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  4991. let indexUnpinned = checkDataPinned.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath!.row][TypeDataMessage.message_id] as? String })
  4992. self.proceedPinUnpinMessage(checkDataPinned: dataMessages[indexPath!.row], isPinned: false) { res in
  4993. if res {
  4994. let dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  4995. DispatchQueue.main.async {
  4996. self.pinAllMessages(dataMessages: dataMessagesPin, isPinned: indexUnpinned ?? 0)
  4997. }
  4998. }
  4999. }
  5000. })
  5001. })
  5002. }
  5003. let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right"), handler: {(_) in
  5004. if self.removed {
  5005. return
  5006. }
  5007. if self.isSearching {
  5008. self.cancelAction()
  5009. }
  5010. if self.reffId != nil {
  5011. self.deleteReplyView()
  5012. }
  5013. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  5014. self.forwardSession = true
  5015. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  5016. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  5017. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  5018. self.navigationItem.rightBarButtonItems = nil
  5019. }
  5020. self.navigationItem.rightBarButtonItem = cancelButton
  5021. if self.isContactCenter || self.fromNotification {
  5022. self.navigationItem.leftBarButtonItem = nil
  5023. }
  5024. self.changeAppBar()
  5025. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  5026. if idx != nil{
  5027. self.dataMessages[idx!]["isSelected"] = true
  5028. }
  5029. self.addMultipleSelectSession()
  5030. self.tableChatView.reloadData()
  5031. }
  5032. })
  5033. let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc"), handler: {(_) in
  5034. if self.removed {
  5035. return
  5036. }
  5037. if self.isSearching {
  5038. self.cancelAction()
  5039. }
  5040. if self.reffId != nil {
  5041. self.deleteReplyView()
  5042. }
  5043. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  5044. self.copySession = true
  5045. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  5046. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  5047. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  5048. self.navigationItem.rightBarButtonItems = nil
  5049. }
  5050. self.navigationItem.rightBarButtonItem = cancelButton
  5051. if self.isContactCenter || self.fromNotification {
  5052. self.navigationItem.leftBarButtonItem = nil
  5053. }
  5054. self.changeAppBar()
  5055. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  5056. if idx != nil{
  5057. self.dataMessages[idx!]["isSelected"] = true
  5058. }
  5059. self.addMultipleSelectSession()
  5060. self.tableChatView.reloadData()
  5061. }
  5062. })
  5063. let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil.tip.crop.circle"), handler: {(_) in
  5064. self.isEditingMessage = true
  5065. self.showEditMessageView(at: indexPath!)
  5066. })
  5067. let translate = UIAction(title: "Translate".localized(), image: UIImage(systemName: "t.bubble"), handler: {(_) in
  5068. self.view.makeToast("Translating...".localized(), duration: 3)
  5069. var translation: String = "English"
  5070. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  5071. if lang == "id" {
  5072. translation = "Indonesia"
  5073. }
  5074. let payload: [String : Any] = [
  5075. "role": "user",
  5076. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  5077. ]
  5078. let parameter: [String : Any] = [
  5079. "use_video": "0",
  5080. "translate": translation,
  5081. "payload": [payload]
  5082. ]
  5083. DispatchQueue.global().async {
  5084. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  5085. let response = response as? HTTPURLResponse
  5086. if response?.statusCode != 200 || error != nil {
  5087. DispatchQueue.main.async {
  5088. self.view.makeToast("There is an error occurred while translating your message. Please try again or check your network connection.".localized(), duration: 3)
  5089. }
  5090. return
  5091. }
  5092. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  5093. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
  5094. let dataContent = json["content"]!
  5095. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  5096. if idx != nil{
  5097. self.dataMessages[idx!][TypeDataMessage.message_text] = (dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "") + "\n\n" + "$\(dataContent)$"
  5098. }
  5099. DispatchQueue.main.async{
  5100. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  5101. }
  5102. }
  5103. }
  5104. })
  5105. }
  5106. })
  5107. let gcs = UIAction(title: "Get Chat Suggestion".localized(), image: UIImage(systemName: "exclamationmark.bubble"), handler: {(_) in
  5108. self.view.makeToast("Getting chat suggestion...".localized(), duration: 3)
  5109. let payload: [String : Any] = [
  5110. "role": "user",
  5111. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  5112. ]
  5113. let parameter: [String : Any] = [
  5114. "use_video": "0",
  5115. "suggest": "1",
  5116. "payload": [payload]
  5117. ]
  5118. DispatchQueue.global().async {
  5119. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  5120. let response = response as? HTTPURLResponse
  5121. if response?.statusCode != 200 || error != nil {
  5122. DispatchQueue.main.async {
  5123. 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)
  5124. }
  5125. return
  5126. }
  5127. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  5128. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
  5129. if let dataMessage = json["message"] as? [[String: Any]] {
  5130. if let dataContent = dataMessage[0]["content"] as? String {
  5131. DispatchQueue.main.async{
  5132. self.textFieldSend.text = dataContent
  5133. self.textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  5134. }
  5135. }
  5136. }
  5137. }
  5138. }
  5139. })
  5140. }
  5141. })
  5142. let more = UIMenu(title: "More...".localized(), children: [translate, gcs])
  5143. let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle"), handler: {(_) in
  5144. if self.removed {
  5145. return
  5146. }
  5147. let messageInfoVC = MessageInfo()
  5148. messageInfoVC.data = dataMessages[indexPath!.row]
  5149. messageInfoVC.dataPerson = self.dataPerson
  5150. self.navigationController?.pushViewController(messageInfoVC, animated: true)
  5151. })
  5152. let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash"), attributes: .destructive, handler: {(_) in
  5153. if self.removed {
  5154. return
  5155. }
  5156. if self.isSearching {
  5157. self.cancelAction()
  5158. }
  5159. if self.reffId != nil {
  5160. self.deleteReplyView()
  5161. }
  5162. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  5163. self.deleteSession = true
  5164. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  5165. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  5166. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  5167. self.navigationItem.rightBarButtonItems = nil
  5168. }
  5169. self.navigationItem.rightBarButtonItem = cancelButton
  5170. if self.isContactCenter || self.fromNotification {
  5171. self.navigationItem.leftBarButtonItem = nil
  5172. }
  5173. self.changeAppBar()
  5174. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  5175. if idx != nil{
  5176. self.dataMessages[idx!]["isSelected"] = true
  5177. }
  5178. self.addMultipleSelectSession()
  5179. self.tableChatView.reloadData()
  5180. }
  5181. })
  5182. let resend = UIAction(title: "Resend".localized(), image: UIImage(systemName: "arrow.clockwise"), handler: {(_) in
  5183. let messageId = dataMessages[indexPath!.row][TypeDataMessage.message_id] as? String ?? ""
  5184. let status = dataMessages[indexPath!.row][TypeDataMessage.status] as? String ?? ""
  5185. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  5186. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  5187. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  5188. self.groupImages[idxMessageIdParent].value[idxInImages].status = "1"
  5189. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage[TypeDataMessage.status] = "1"
  5190. }
  5191. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  5192. }
  5193. if (idx != nil) {
  5194. do {
  5195. self.dataMessages[idx!][TypeDataMessage.status] = "1"
  5196. self.dataMessages[idx!][TypeDataMessage.progress] = 0.0
  5197. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  5198. 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 })
  5199. if row != nil && section != nil {
  5200. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  5201. }
  5202. } catch {
  5203. }
  5204. }
  5205. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  5206. do {
  5207. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  5208. "status" : "1"
  5209. ], _where: "message_id = '\(messageId)'")
  5210. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
  5211. "status" : "1"
  5212. ], _where: "message_id = '\(messageId)'")
  5213. } catch {
  5214. rollback.pointee = true
  5215. print("Access database error: \(error.localizedDescription)")
  5216. }
  5217. })
  5218. let message = CoreMessage_TMessageBank.sendMessage(message_id: messageId,
  5219. l_pin: dataMessages[indexPath!.row][TypeDataMessage.l_pin] as? String ?? "",
  5220. message_scope_id: dataMessages[indexPath!.row][TypeDataMessage.message_scope_id] as? String ?? "",
  5221. status: "1",
  5222. message_text: dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "",
  5223. credential: dataMessages[indexPath!.row][TypeDataMessage.credential] as? String ?? "",
  5224. attachment_flag: dataMessages[indexPath!.row][TypeDataMessage.attachment_flag] as? String ?? "",
  5225. ex_blog_id: dataMessages[indexPath!.row][TypeDataMessage.blog_id] as? String ?? "",
  5226. message_large_text: "",
  5227. ex_format: "",
  5228. image_id: dataMessages[indexPath!.row][TypeDataMessage.image_id] as? String ?? "",
  5229. audio_id: dataMessages[indexPath!.row][TypeDataMessage.audio_id] as? String ?? "",
  5230. video_id: dataMessages[indexPath!.row][TypeDataMessage.video_id] as? String ?? "",
  5231. file_id: dataMessages[indexPath!.row][TypeDataMessage.file_id] as? String ?? "",
  5232. thumb_id: dataMessages[indexPath!.row][TypeDataMessage.thumb_id] as? String ?? "",
  5233. reff_id: dataMessages[indexPath!.row][TypeDataMessage.reff_id] as? String ?? "",
  5234. read_receipts: dataMessages[indexPath!.row][TypeDataMessage.read_receipts] as? String ?? "",
  5235. chat_id: dataMessages[indexPath!.row][TypeDataMessage.chat_id] as? String ?? "",
  5236. is_call_center: dataMessages[indexPath!.row][TypeDataMessage.is_call_center] as? String ?? "",
  5237. call_center_id: dataMessages[indexPath!.row][TypeDataMessage.call_center_id] as? String ?? "",
  5238. opposite_pin: dataMessages[indexPath!.row][TypeDataMessage.opposite_pin] as? String ?? "", specFile: "")
  5239. Nexilis.addQueueMessage(message: message)
  5240. })
  5241. var children: [UIMenuElement] = [star, reply, pin, copy, delete]
  5242. var isMore = false
  5243. let idMe = User.getMyPin() as String?
  5244. if !(dataMessages[indexPath!.row]["audio_id"] as? String ?? "").isEmpty {
  5245. children.remove(at: 3)
  5246. }
  5247. if dataMessages[indexPath!.row]["status"] as? String ?? "" == "0" {
  5248. children = [resend, delete]
  5249. } else if isContactCenter {
  5250. if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  5251. children = [reply]
  5252. }
  5253. else {
  5254. children = [reply, copy]
  5255. }
  5256. } else if (dataMessages[indexPath!.row]["lock"] != nil && dataMessages[indexPath!.row]["lock"] as? String == "1") || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.FORM || dataPerson["f_pin"] == "-999" || dataMessages[indexPath!.row]["credential"] as? String == "1" || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.CALL || dataMessages[indexPath!.row]["message_scope_id"] as? String == MessageScope.MISSED_CALL || blocking == "1" || blocking == "-1" {
  5257. children = [delete]
  5258. } else if (groupImages[dataMessages[indexPath!.row]["message_id"] as? String ?? ""] != nil) {
  5259. forward.title = "Forward All".localized()
  5260. delete.title = "Delete All".localized()
  5261. children = [delete]
  5262. if (Nexilis.checkingAccess(key: "secure_folder_forward") || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" {
  5263. children.insert(forward, at: 0)
  5264. }
  5265. } else {
  5266. 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 {
  5267. children = [star, reply , pin, delete]
  5268. } else if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  5269. children = [reply, delete]
  5270. }
  5271. if ((Nexilis.checkingAccess(key: "secure_folder_forward") && dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" != "11") || (!(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"] as? String ?? "").isEmpty) || (dataMessages[indexPath!.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward")) && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" && dataMessages[indexPath!.row]["read_receipts"] as? String != "8" && dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" != "11" {
  5272. children.insert(forward, at: 2)
  5273. }
  5274. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  5275. children.insert(info, at: children.count - 1)
  5276. }
  5277. if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty {
  5278. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe && ((dataMessages[indexPath!.row][TypeDataMessage.is_forwarded] as? Int) ?? 0) == 0 && (dataMessages[indexPath!.row][TypeDataMessage.attachment_flag] as? String ?? "") != "11" {
  5279. var textFile = dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? ""
  5280. if !(dataMessages[indexPath!.row][TypeDataMessage.file_id] as? String ?? "").isEmpty {
  5281. textFile = textFile.components(separatedBy: "|")[1]
  5282. }
  5283. if !textFile.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  5284. let valueDate = Date(milliseconds: Int64(dataMessages[indexPath!.row][TypeDataMessage.server_date] as? String ?? "") ?? 0)
  5285. let nowDate = Date()
  5286. let diffInSeconds = nowDate.timeIntervalSince(valueDate)
  5287. if diffInSeconds <= 15 * 60 {
  5288. children.insert(edit, at: children.count - 1)
  5289. }
  5290. }
  5291. }
  5292. if (dataMessages[indexPath!.row][TypeDataMessage.attachment_flag] as? String ?? "") != "11" && (dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["file_id"] as? String ?? "").isEmpty && (dataMessages[indexPath!.row]["audio_id"] as? String ?? "").isEmpty{
  5293. isMore = true
  5294. }
  5295. }
  5296. }
  5297. let mainMenu = UIMenu(title: "", options: [.displayInline],
  5298. children: children)
  5299. var menuForShow = UIMenu(title: "", children: [mainMenu])
  5300. if isMore {
  5301. menuForShow = UIMenu(title: "", children: [mainMenu, more])
  5302. }
  5303. return UIContextMenuConfiguration(identifier: nil,
  5304. previewProvider: nil) { _ in
  5305. return menuForShow
  5306. }
  5307. }
  5308. func proceedPinUnpinMessage(checkDataPinned: [String: Any?], isPinned: Bool, completion: @escaping (Bool)-> Void) {
  5309. DispatchQueue.global().async {
  5310. var jaData = [[String: Any]]()
  5311. var jsonObject = [String: Any]()
  5312. jsonObject[CoreMessage_TMessageKey.MESSAGE_ID] = checkDataPinned["message_id"] as? String ?? ""
  5313. jsonObject[CoreMessage_TMessageKey.IS_PINNED_MESSAGE] = isPinned ? "\(Date().currentTimeMillis())" : "0"
  5314. jaData.append(jsonObject)
  5315. if let jsonData = try? JSONSerialization.data(withJSONObject: jaData, options: []),
  5316. let jsonString = String(data: jsonData, encoding: .utf8) {
  5317. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getPinMessage(f_pin: User.getMyPin() ?? "", data: jsonString, oppositePin: self.unique_l_pin, chatId: "", scopeId: MessageScope.WHISPER)) {
  5318. if response.isOk() {
  5319. if isPinned {
  5320. let mId = Nexilis.saveMessageNotif(textMessage: "You".localized() + " " + "pinned a message".localized(), fPin: User.getMyPin() ?? "", lPin: self.unique_l_pin, chatId: "", scopeId: MessageScope.WHISPER)
  5321. self.appendNewMessage(messageId: mId)
  5322. }
  5323. DispatchQueue.global().async {
  5324. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  5325. do {
  5326. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  5327. "is_pinned" : isPinned ? "\(Date().currentTimeMillis())" : "0"
  5328. ], _where: "message_id = '\(checkDataPinned["message_id"] as? String ?? "")'")
  5329. } catch {
  5330. rollback.pointee = true
  5331. print("Access database error: \(error.localizedDescription)")
  5332. }
  5333. })
  5334. }
  5335. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == checkDataPinned["message_id"] as? String ?? ""})
  5336. if idx != nil{
  5337. self.dataMessages[idx!][TypeDataMessage.is_pinned] = isPinned ? "\(Date().currentTimeMillis())" : "0"
  5338. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  5339. 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 ?? "" })
  5340. if row != nil && section != nil {
  5341. DispatchQueue.main.async {
  5342. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  5343. }
  5344. }
  5345. }
  5346. completion(true)
  5347. } else {
  5348. DispatchQueue.main.async {
  5349. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  5350. imageView.tintColor = .white
  5351. let banner = FloatingNotificationBanner(title: "Failed to pin or unpin message, make sure you are connected to internet".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)
  5352. banner.show()
  5353. }
  5354. completion(false)
  5355. }
  5356. } else {
  5357. DispatchQueue.main.async {
  5358. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  5359. imageView.tintColor = .white
  5360. let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  5361. banner.show()
  5362. }
  5363. completion(false)
  5364. }
  5365. }
  5366. }
  5367. }
  5368. func showEditMessageView(at indexPath: IndexPath) {
  5369. tempListMentionWithText = listMentionWithText
  5370. tempListMentionInTextField = listMentionInTextField
  5371. listMentionWithText.removeAll()
  5372. listMentionInTextField.removeAll()
  5373. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  5374. var oldText = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  5375. if !(dataMessages[indexPath.row][TypeDataMessage.file_id] as? String ?? "").isEmpty {
  5376. oldText = oldText.components(separatedBy: "|")[1]
  5377. }
  5378. var oldTextForTextview = oldText
  5379. let pattern = "@[\\w]+"
  5380. do {
  5381. let regex = try NSRegularExpression(pattern: pattern)
  5382. let nsrange = NSRange(oldText.startIndex..., in: oldText)
  5383. let matches = regex.matches(in: oldText, range: nsrange)
  5384. let results = matches.map {
  5385. String(oldText[Range($0.range, in: oldText)!])
  5386. }
  5387. for result in results {
  5388. let pinRes = result.components(separatedBy: "@")[1]
  5389. Database.shared.database?.inTransaction({ fmdb, rollback in
  5390. do {
  5391. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where f_pin = '\(pinRes)'"), cursor.next() {
  5392. let user = User(pin: "")
  5393. user.pin = cursor.string(forColumnIndex: 0) ?? ""
  5394. user.firstName = cursor.string(forColumnIndex: 1) ?? ""
  5395. if !user.pin.isEmpty {
  5396. var fixUser = User.getDataCanNil(pin: user.pin, fmdb: fmdb)
  5397. if fixUser == nil {
  5398. fixUser = user
  5399. }
  5400. var indexAt = 0
  5401. if let range = oldTextForTextview.range(of: result) {
  5402. indexAt = oldTextForTextview.distance(from: oldTextForTextview.startIndex, to: range.lowerBound)
  5403. }
  5404. fixUser?.ex_block = "\(indexAt + fixUser!.fullName.count)"
  5405. listMentionWithText.append(fixUser!)
  5406. listMentionInTextField.append(fixUser!)
  5407. oldTextForTextview = oldTextForTextview.replacingOccurrences(of: result, with: "@\(fixUser!.fullName)")
  5408. lastTextLength = oldTextForTextview.count
  5409. }
  5410. cursor.close()
  5411. } else if pinRes == "-997" {
  5412. let gptUser = User(pin: "-997",
  5413. firstName: Utils.getGPTBotName(),
  5414. lastName: "",
  5415. thumb: "",
  5416. userType: "0",
  5417. official: "1")
  5418. var indexAt = 0
  5419. if let range = oldTextForTextview.range(of: result) {
  5420. indexAt = oldTextForTextview.distance(from: oldTextForTextview.startIndex, to: range.lowerBound)
  5421. }
  5422. gptUser.ex_block = "\(indexAt + gptUser.fullName.count)"
  5423. listMentionWithText.append(gptUser)
  5424. listMentionInTextField.append(gptUser)
  5425. oldTextForTextview = oldTextForTextview.replacingOccurrences(of: result, with: "@\(gptUser.fullName)")
  5426. lastTextLength = oldTextForTextview.count
  5427. }
  5428. } catch {
  5429. rollback.pointee = true
  5430. print("Access database error: \(error.localizedDescription)")
  5431. }
  5432. })
  5433. }
  5434. } catch {
  5435. print("Invalid regex pattern")
  5436. }
  5437. editVC = UIViewController()
  5438. if let view = editVC.view {
  5439. view.backgroundColor = .clear
  5440. let blurView = UIView()
  5441. let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
  5442. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  5443. blurEffectView.frame = blurView.bounds
  5444. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  5445. blurView.addSubview(blurEffectView)
  5446. blurView.sendSubviewToBack(blurEffectView)
  5447. view.addSubview(blurView)
  5448. blurView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  5449. let tapGesture = ObjectGesture(target: self, action: #selector(dismissEditVC))
  5450. tapGesture.message_id = oldText
  5451. blurView.addGestureRecognizer(tapGesture)
  5452. editTextView = CustomTextView()
  5453. editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
  5454. editTextView.layer.borderWidth = 1.0
  5455. editTextView.textColor = UIColor.black
  5456. editTextView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5457. editTextView.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  5458. editTextView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  5459. editTextView.font = UIFont.systemFont(ofSize: 12 + offset())
  5460. editTextView.delegate = self
  5461. editTextView.allowsEditingTextAttributes = true
  5462. editTextView.backgroundColor = .clear
  5463. view.addSubview(editTextView)
  5464. editTextView.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 15, paddingRight: 15)
  5465. constraintBottomeditTextView = editTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  5466. constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
  5467. constraintBottomeditTextView.isActive = true
  5468. constraintHeighteditTextView.isActive = true
  5469. editTextView.attributedText = oldText.richText(isEditing: true, listMentionInTextField: listMentionInTextField)
  5470. editTextView.becomeFirstResponder()
  5471. 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)
  5472. buttonSendEdit.circle()
  5473. buttonSendEdit.isEnabled = true
  5474. buttonSendEdit.actionHandle(controlEvents: .touchUpInside,
  5475. ForAction:{() -> Void in
  5476. var newText = self.editTextView.text ?? ""
  5477. if !(dataMessages[indexPath.row][TypeDataMessage.file_id] as? String ?? "").isEmpty {
  5478. let firstText = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  5479. newText = firstText.components(separatedBy: "|")[0] + "|" + newText
  5480. }
  5481. if newText.contains("@") && self.listMentionInTextField.count > 0 {
  5482. var diff: Int = 0
  5483. for i in 0..<self.listMentionInTextField.count {
  5484. let mention = self.listMentionInTextField[i]
  5485. guard let exBlockStr = mention.ex_block, let exBlock = Int(exBlockStr) else {
  5486. continue // skip if ex_block is nil or not an integer
  5487. }
  5488. let nameWithMention = ("@" + mention.firstName + " " + mention.lastName).trimmingCharacters(in: .whitespaces)
  5489. let pinString = "@\(mention.pin)"
  5490. let upperBound = exBlock + diff
  5491. let lowerBound = upperBound - nameWithMention.count + 1
  5492. guard lowerBound >= 0, upperBound < newText.count else {
  5493. continue // prevent index out-of-range
  5494. }
  5495. var afterMention = ""
  5496. let nextCharIndex = newText.index(newText.startIndex, offsetBy: upperBound + 1, limitedBy: newText.endIndex)
  5497. if let index = nextCharIndex, index < newText.endIndex {
  5498. let nextChar = newText[index]
  5499. if nextChar != "\n" && nextChar != " " {
  5500. afterMention = " "
  5501. }
  5502. }
  5503. let startIndex = newText.index(newText.startIndex, offsetBy: lowerBound)
  5504. let endIndex = newText.index(newText.startIndex, offsetBy: upperBound + 1)
  5505. let range = startIndex..<endIndex
  5506. if newText[range] == nameWithMention {
  5507. newText.replaceSubrange(range, with: pinString + afterMention)
  5508. diff += (pinString + afterMention).count - nameWithMention.count
  5509. }
  5510. }
  5511. }
  5512. if !newText.isEmpty && newText.trimmingCharacters(in: .whitespacesAndNewlines) != oldText {
  5513. if !(dataMessages[indexPath.row][TypeDataMessage.file_id] as? String ?? "").isEmpty {
  5514. let firstText = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  5515. if newText != firstText {
  5516. excEdit()
  5517. }
  5518. } else {
  5519. excEdit()
  5520. }
  5521. func excEdit() {
  5522. let lastEdited = Int64(Date().currentTimeMillis())
  5523. let message = CoreMessage_TMessageBank.editMessage(message_id: dataMessages[indexPath.row][TypeDataMessage.message_id] as? String ?? "", l_pin: dataMessages[indexPath.row][TypeDataMessage.l_pin] as? String ?? "", message_scope_id: dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String ?? "", status: "1", message_text: newText, credential: dataMessages[indexPath.row][TypeDataMessage.credential] as? String ?? "", attachment_flag: dataMessages[indexPath.row][TypeDataMessage.attachment_flag] as? String ?? "", ex_blog_id: dataMessages[indexPath.row][TypeDataMessage.blog_id] as? String ?? "", message_large_text: "", ex_format: "", image_id: dataMessages[indexPath.row][TypeDataMessage.image_id] as? String ?? "", audio_id: dataMessages[indexPath.row][TypeDataMessage.audio_id] as? String ?? "", video_id: dataMessages[indexPath.row][TypeDataMessage.video_id] as? String ?? "", file_id: dataMessages[indexPath.row][TypeDataMessage.file_id] as? String ?? "", thumb_id: dataMessages[indexPath.row][TypeDataMessage.thumb_id] as? String ?? "", reff_id: dataMessages[indexPath.row][TypeDataMessage.reff_id] as? String ?? "", read_receipts: dataMessages[indexPath.row][TypeDataMessage.read_receipts] as? String ?? "", chat_id: dataMessages[indexPath.row][TypeDataMessage.chat_id] as? String ?? "", is_call_center: dataMessages[indexPath.row][TypeDataMessage.is_call_center] as? String ?? "", call_center_id: dataMessages[indexPath.row][TypeDataMessage.call_center_id] as? String ?? "", opposite_pin: dataMessages[indexPath.row][TypeDataMessage.opposite_pin] as? String ?? "", server_date: dataMessages[indexPath.row][TypeDataMessage.server_date] as? String ?? "", local_time_stamp: dataMessages[indexPath.row][TypeDataMessage.server_date] as? String ?? "", last_edit: lastEdited)
  5524. Nexilis.addQueueMessage(message: message, isEditMessage: true)
  5525. DispatchQueue.global().async {
  5526. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  5527. do {
  5528. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  5529. "message_text" : newText,
  5530. "last_edited" : lastEdited
  5531. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  5532. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  5533. } catch {
  5534. rollback.pointee = true
  5535. print("Access database error: \(error.localizedDescription)")
  5536. }
  5537. })
  5538. }
  5539. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
  5540. if idx != nil{
  5541. self.dataMessages[idx!][TypeDataMessage.message_text] = newText
  5542. self.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
  5543. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  5544. }
  5545. }
  5546. }
  5547. self.isEditingMessage = false
  5548. self.listMentionWithText = self.tempListMentionWithText
  5549. self.listMentionInTextField = self.tempListMentionWithText
  5550. self.lastTextLength = self.textFieldSend.text?.count ?? 0
  5551. self.heightTableEditMention = nil
  5552. self.editVC.dismiss(animated: true)
  5553. })
  5554. buttonSendEdit.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  5555. view.addSubview(buttonSendEdit)
  5556. buttonSendEdit.anchor(right: view.rightAnchor, paddingRight: 15, width: 40, height: 40)
  5557. constraintBottomSendEditTV = buttonSendEdit.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  5558. constraintBottomSendEditTV.isActive = true
  5559. let viewMessage = UIView()
  5560. view.addSubview(viewMessage)
  5561. viewMessage.translatesAutoresizingMaskIntoConstraints = false
  5562. if (dataMessages[indexPath.row][TypeDataMessage.f_pin] as? String == User.getMyPin()) {
  5563. viewMessage.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 60).isActive = true
  5564. viewMessage.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15).isActive = true
  5565. viewMessage.backgroundColor = .blueBubbleColor
  5566. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  5567. } else {
  5568. viewMessage.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: 60).isActive = true
  5569. viewMessage.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
  5570. viewMessage.backgroundColor = .whiteBubbleColor
  5571. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  5572. }
  5573. viewMessage.bottomAnchor.constraint(equalTo: editTextView.topAnchor, constant: -15).isActive = true
  5574. viewMessage.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
  5575. viewMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5576. viewMessage.layer.cornerRadius = 10.0
  5577. viewMessage.clipsToBounds = true
  5578. let messageText = UILabel()
  5579. messageText.numberOfLines = 0
  5580. messageText.lineBreakMode = .byWordWrapping
  5581. viewMessage.addSubview(messageText)
  5582. messageText.translatesAutoresizingMaskIntoConstraints = false
  5583. messageText.topAnchor.constraint(equalTo: viewMessage.topAnchor, constant: 15).isActive = true
  5584. messageText.leadingAnchor.constraint(equalTo: viewMessage.leadingAnchor, constant: 15).isActive = true
  5585. messageText.bottomAnchor.constraint(equalTo: viewMessage.bottomAnchor, constant: -15).isActive = true
  5586. messageText.trailingAnchor.constraint(equalTo: viewMessage.trailingAnchor, constant: -15).isActive = true
  5587. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5588. messageText.font = .systemFont(ofSize: 12 + offset())
  5589. messageText.attributedText = oldText.richText()
  5590. tableMentionEdit = UITableView()
  5591. tableMentionEdit.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditMention")
  5592. tableMentionEdit.dataSource = self
  5593. tableMentionEdit.delegate = self
  5594. tableMentionEdit.contentInset = UIEdgeInsets(top: -25, left: 0, bottom: 0, right: 0)
  5595. tableMentionEdit.backgroundColor = .white
  5596. view.addSubview(tableMentionEdit)
  5597. tableMentionEdit.anchor(left: view.leftAnchor, bottom: editTextView.topAnchor, right: view.rightAnchor)
  5598. heightTableEditMention = tableMentionEdit.heightAnchor.constraint(equalToConstant: 0)
  5599. self.heightTableEditMention.isActive = true
  5600. }
  5601. editVC.modalTransitionStyle = .crossDissolve
  5602. editVC.modalPresentationStyle = .overFullScreen
  5603. self.present(editVC, animated: true, completion: {
  5604. self.constraintHeighteditTextView.constant = self.editTextView.contentSize.height
  5605. if self.constraintHeighteditTextView.constant > 95 {
  5606. self.constraintHeighteditTextView.constant = 95.0
  5607. }
  5608. })
  5609. }
  5610. @objc func dismissEditVC(_ sender: ObjectGesture) {
  5611. if editTextView.text == sender.message_id {
  5612. self.isEditingMessage = false
  5613. listMentionWithText = tempListMentionWithText
  5614. listMentionInTextField = tempListMentionWithText
  5615. lastTextLength = textFieldSend.text?.count ?? 0
  5616. heightTableEditMention = nil
  5617. editVC.dismiss(animated: true)
  5618. } else if self.isEditingMessage {
  5619. let alert = LibAlertController(title: "".localized(), message: "Discard edit?".localized(), preferredStyle: .alert)
  5620. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.cancel, handler: nil))
  5621. alert.addAction(UIAlertAction(title: "Discard".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  5622. self.isEditingMessage = false
  5623. self.listMentionWithText = self.tempListMentionWithText
  5624. self.listMentionInTextField = self.tempListMentionWithText
  5625. self.lastTextLength = self.textFieldSend.text?.count ?? 0
  5626. self.heightTableEditMention = nil
  5627. self.editVC.dismiss(animated: true)
  5628. }))
  5629. editVC.present(alert, animated: true, completion: nil)
  5630. } else {
  5631. lastTextLength = self.textFieldSend.text?.count ?? 0
  5632. editVC.dismiss(animated: true)
  5633. }
  5634. }
  5635. @objc func cancelAction() {
  5636. DispatchQueue.main.async {
  5637. if self.copySession {
  5638. self.copySession = false
  5639. } else if self.forwardSession {
  5640. self.forwardSession = false
  5641. } else if self.deleteSession {
  5642. self.deleteSession = false
  5643. } else if self.isSearching {
  5644. self.countMatchesSearch = 0
  5645. self.isSearching = false
  5646. }
  5647. if self.viewTextfield.isHidden {
  5648. self.viewTextfield.isHidden = false
  5649. }
  5650. if self.viewAttachment.isHidden {
  5651. self.viewAttachment.isHidden = false
  5652. }
  5653. if self.containerAction.isHidden {
  5654. self.containerAction.isHidden = false
  5655. }
  5656. if self.viewButton.isHidden {
  5657. self.viewButton.isHidden = false
  5658. }
  5659. if self.constraintBottomTableViewWithTextfield.constant == -60.0 {
  5660. self.constraintBottomTableViewWithTextfield.constant = self.constraintBottomTableViewWithTextfield.constant + 70
  5661. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  5662. if (self.currentIndexpath != nil) {
  5663. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: true)
  5664. } else {
  5665. self.tableChatView.scrollToBottom()
  5666. }
  5667. })
  5668. }
  5669. let data = self.dataMessages.filter({ $0["isSelected"] as? Bool == true })
  5670. for i in 0..<data.count {
  5671. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data[i]["message_id"] as? String})
  5672. if idx != nil{
  5673. self.dataMessages[idx!]["isSelected"] = false
  5674. }
  5675. }
  5676. self.tableChatView.reloadData()
  5677. self.setRightButtonItem()
  5678. self.changeAppBar()
  5679. if self.isContactCenter || self.fromNotification {
  5680. let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(self.didTapExit))
  5681. self.navigationItem.leftBarButtonItem = backButton
  5682. }
  5683. self.containerMultpileSelectSession.removeFromSuperview()
  5684. self.checkNewMessage(tableView: self.tableChatView)
  5685. }
  5686. }
  5687. private func addMultipleSelectSession() {
  5688. viewTextfield.isHidden = true
  5689. viewAttachment.isHidden = true
  5690. containerAction.isHidden = true
  5691. viewButton.isHidden = true
  5692. constraintBottomTableViewWithTextfield.constant = constraintBottomTableViewWithTextfield.constant - 70
  5693. view.addSubview(containerMultpileSelectSession)
  5694. containerMultpileSelectSession.translatesAutoresizingMaskIntoConstraints = false
  5695. constraintBottomContainerMultpileSelectSession = containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
  5696. NSLayoutConstraint.activate([
  5697. containerMultpileSelectSession.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  5698. containerMultpileSelectSession.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  5699. constraintBottomContainerMultpileSelectSession,
  5700. containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 50)
  5701. ])
  5702. containerMultpileSelectSession.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  5703. addSubviewMultipleSession()
  5704. }
  5705. private func addSubviewMultipleSession() {
  5706. let container = UIView()
  5707. containerMultpileSelectSession.addSubview(container)
  5708. container.translatesAutoresizingMaskIntoConstraints = false
  5709. NSLayoutConstraint.activate([
  5710. container.leadingAnchor.constraint(equalTo: containerMultpileSelectSession.leadingAnchor),
  5711. container.trailingAnchor.constraint(equalTo:containerMultpileSelectSession.trailingAnchor),
  5712. container.bottomAnchor.constraint(equalTo: containerMultpileSelectSession.bottomAnchor),
  5713. container.heightAnchor.constraint(equalToConstant: 50)
  5714. ])
  5715. container.layer.shadowOpacity = 0.7
  5716. container.layer.shadowOffset = CGSize(width: 3, height: 3)
  5717. container.layer.shadowRadius = 3.0
  5718. container.layer.shadowColor = UIColor.black.cgColor
  5719. container.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  5720. if !isSearching {
  5721. let title = UILabel()
  5722. container.addSubview(title)
  5723. title.translatesAutoresizingMaskIntoConstraints = false
  5724. NSLayoutConstraint.activate([
  5725. title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  5726. title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  5727. ])
  5728. let countSelected = dataMessages.filter({ $0["isSelected"] as? Bool == true }).count
  5729. title.text = "\(countSelected) " + "Selected".localized()
  5730. title.textColor = .mainColor
  5731. title.font = UIFont.systemFont(ofSize: 15.0).bold
  5732. let button = UIImageView()
  5733. container.addSubview(button)
  5734. button.translatesAutoresizingMaskIntoConstraints = false
  5735. NSLayoutConstraint.activate([
  5736. button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
  5737. button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  5738. button.widthAnchor.constraint(equalToConstant: 30),
  5739. button.heightAnchor.constraint(equalToConstant: 30),
  5740. ])
  5741. if copySession {
  5742. button.image = UIImage(systemName: "doc.on.doc")
  5743. if countSelected == 0 {
  5744. button.tintColor = .gray
  5745. } else {
  5746. button.tintColor = .mainColor
  5747. }
  5748. } else if forwardSession {
  5749. button.image = UIImage(systemName: "arrowshape.turn.up.right")
  5750. if countSelected == 0 {
  5751. button.tintColor = .gray
  5752. } else {
  5753. button.tintColor = .mainColor
  5754. }
  5755. } else if deleteSession {
  5756. button.image = UIImage(systemName: "trash")
  5757. if countSelected == 0 {
  5758. button.tintColor = .gray
  5759. } else {
  5760. button.tintColor = .red
  5761. }
  5762. }
  5763. let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
  5764. button.isUserInteractionEnabled = true
  5765. button.addGestureRecognizer(buttonGesture)
  5766. let selectedMessage = dataMessages.filter({ $0["isSelected"] as? Bool == true })
  5767. if selectedMessage.count > 0 {
  5768. for i in 0..<selectedMessage.count {
  5769. if let isGroupingImages = groupImages[selectedMessage[i]["message_id"] as? String ?? ""] {
  5770. title.text = "\(countSelected + (isGroupingImages.count - 1)) " + "Selected".localized()
  5771. }
  5772. }
  5773. }
  5774. } else {
  5775. buttonUp = UIButton()
  5776. container.addSubview(buttonUp)
  5777. buttonUp.translatesAutoresizingMaskIntoConstraints = false
  5778. NSLayoutConstraint.activate([
  5779. buttonUp.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10),
  5780. buttonUp.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  5781. buttonUp.widthAnchor.constraint(equalToConstant: 30),
  5782. buttonUp.heightAnchor.constraint(equalToConstant: 30),
  5783. ])
  5784. buttonUp.addTarget(self, action: #selector(upSearchText), for: .touchUpInside)
  5785. buttonDown = UIButton()
  5786. container.addSubview(buttonDown)
  5787. buttonDown.translatesAutoresizingMaskIntoConstraints = false
  5788. NSLayoutConstraint.activate([
  5789. buttonDown.leadingAnchor.constraint(equalTo: buttonUp.trailingAnchor, constant: 15),
  5790. buttonDown.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  5791. buttonDown.widthAnchor.constraint(equalToConstant: 30),
  5792. buttonDown.heightAnchor.constraint(equalToConstant: 30),
  5793. ])
  5794. buttonDown.addTarget(self, action: #selector(downSearchText), for: .touchUpInside)
  5795. buttonUp.setImage(UIImage(systemName: "chevron.up"), for: .normal)
  5796. buttonUp.tintColor = .gray
  5797. buttonDown.setImage(UIImage(systemName: "chevron.down"), for: .normal)
  5798. buttonDown.tintColor = .gray
  5799. titleSearchMatches = UILabel()
  5800. container.addSubview(titleSearchMatches)
  5801. titleSearchMatches.translatesAutoresizingMaskIntoConstraints = false
  5802. NSLayoutConstraint.activate([
  5803. titleSearchMatches.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  5804. titleSearchMatches.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  5805. ])
  5806. titleSearchMatches.textColor = .mainColor
  5807. titleSearchMatches.font = UIFont.systemFont(ofSize: 15.0).bold
  5808. titleSearchMatches.isHidden = true
  5809. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  5810. self.searchBar.becomeFirstResponder()
  5811. })
  5812. }
  5813. }
  5814. @objc func upSearchText() {
  5815. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch + 1)
  5816. }
  5817. @objc func downSearchText() {
  5818. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch - 1)
  5819. }
  5820. @objc func sessionAction() {
  5821. if copySession {
  5822. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as? Bool == true })
  5823. let countSelected = dataMessages.count
  5824. if countSelected == 0 {
  5825. return
  5826. }
  5827. var text = ""
  5828. for i in 0..<countSelected {
  5829. let stringDate = (dataMessages[i]["server_date"] as? String ?? "")
  5830. let date = Date(milliseconds: Int64(stringDate)!)
  5831. let formatterDate = DateFormatter()
  5832. let formatterTime = DateFormatter()
  5833. formatterDate.dateFormat = "dd/MM/yy"
  5834. formatterDate.locale = NSLocale(localeIdentifier: "id") as Locale?
  5835. formatterTime.dateFormat = "HH:mm"
  5836. formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
  5837. let dataProfile = getDataProfile(message_id: dataMessages[i]["message_id"] as? String ?? "")
  5838. let textCopied = (dataMessages[i]["message_text"] as? String ?? "").richText(isEditing: true)
  5839. if text.isEmpty {
  5840. text = "*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  5841. } else {
  5842. text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  5843. }
  5844. }
  5845. text = text + "\n\n\nchat " + "Powered by Nexilis".localized()
  5846. DispatchQueue.main.async {
  5847. UIPasteboard.general.string = text
  5848. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  5849. }
  5850. cancelAction()
  5851. } else if forwardSession {
  5852. var dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  5853. let countSelected = dataMessages.count
  5854. if countSelected == 0 {
  5855. return
  5856. }
  5857. for i in 0..<countSelected {
  5858. if groupImages[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  5859. var tempData = dataMessages
  5860. tempData.remove(at: 0)
  5861. let dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  5862. tempData.insert(contentsOf: dataMessageInGrouping, at: i)
  5863. dataMessages = tempData
  5864. }
  5865. }
  5866. contactChatNav.modalPresentationStyle = .custom
  5867. contactChatNav.navigationBar.tintColor = .white
  5868. contactChatNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5869. contactChatNav.navigationBar.isTranslucent = false
  5870. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  5871. contactChatNav.navigationBar.titleTextAttributes = textAttributes
  5872. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  5873. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  5874. contactChatNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5875. if let controller = contactChatNav.viewControllers.first as? ContactChatViewController {
  5876. controller.isChooser = { [weak self] scope, pin in
  5877. if scope == MessageScope.WHISPER || scope == MessageScope.CALL || scope == MessageScope.MISSED_CALL {
  5878. let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
  5879. editorPersonalVC.unique_l_pin = pin
  5880. editorPersonalVC.dataMessageForward = dataMessages
  5881. self?.navigationController?.replaceAllViewController(with: editorPersonalVC, animated: true)
  5882. } else {
  5883. let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
  5884. editorGroupVC.unique_l_pin = pin
  5885. editorGroupVC.dataMessageForward = dataMessages
  5886. self?.navigationController?.replaceAllViewController(with: editorGroupVC, animated: true)
  5887. }
  5888. }
  5889. }
  5890. self.present(contactChatNav, animated: true, completion: nil)
  5891. } else if deleteSession {
  5892. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  5893. var countSelected = dataMessages.count
  5894. if countSelected == 0 {
  5895. return
  5896. }
  5897. for i in 0..<countSelected {
  5898. if let isGroupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  5899. countSelected += (isGroupingImages.count - 1)
  5900. }
  5901. }
  5902. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  5903. if let action = self.actionDelete(for: "me", title: "Delete".localized() + " \(countSelected) " + "For Me".localized(), dataMessages: dataMessages) {
  5904. alertController.addAction(action)
  5905. }
  5906. let idMe = User.getMyPin() as String?
  5907. let dataFilterFpin = dataMessages.filter({ $0["l_pin"] as? String == idMe})
  5908. let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1" || $0["lock"] as? String == "2" })
  5909. let dataFilterCall = dataMessages.filter({ $0[TypeDataMessage.message_scope_id] as? String == MessageScope.CALL || $0[TypeDataMessage.message_scope_id] as? String == MessageScope.MISSED_CALL })
  5910. // let statusDataRead = dataMessages.filter({ Int($0["status"] as? String ?? "")! >= 4})
  5911. let statusFailed = dataMessages.filter({ Int($0["status"] as? String ?? "")! == 0})
  5912. if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusFailed.count == 0 && dataFilterCall.count == 0 {
  5913. if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
  5914. alertController.addAction(action)
  5915. }
  5916. }
  5917. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  5918. self.present(alertController, animated: true)
  5919. }
  5920. }
  5921. private func getDataProfile(message_id: String) -> [String: String]{
  5922. var data: [String: String] = [:]
  5923. Database.shared.database?.inTransaction({ fmdb, rollback in
  5924. if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
  5925. data["name"] = c.string(forColumnIndex: 0)!
  5926. c.close()
  5927. } else {
  5928. data["name"] = "Unknown".localized()
  5929. data["image_id"] = ""
  5930. }
  5931. })
  5932. return data
  5933. }
  5934. private func deleteMessage(l_pin: String, message_id: String, scope: String, type: String, chat: String) {
  5935. let tmessage = CoreMessage_TMessageBank.deleteMessage(l_pin: l_pin, messageId: message_id, scope: scope, type: type, chat: chat)
  5936. Nexilis.deleteQueueMessage(message: tmessage)
  5937. }
  5938. private func queryMessageReply(message_id: String) -> [String: Any?] {
  5939. var dataQuery: [String: Any] = [:]
  5940. Database.shared.database?.inTransaction({ fmdb, rollback in
  5941. 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() {
  5942. dataQuery["message_id"] = c.string(forColumnIndex: 0) ?? ""
  5943. dataQuery["f_pin"] = c.string(forColumnIndex: 1) ?? ""
  5944. dataQuery["message_text"] = c.string(forColumnIndex: 2) ?? ""
  5945. dataQuery["attachment_flag"] = c.string(forColumnIndex: 3) ?? ""
  5946. dataQuery["thumb_id"] = c.string(forColumnIndex: 4) ?? ""
  5947. dataQuery["image_id"] = c.string(forColumnIndex: 5) ?? ""
  5948. dataQuery["video_id"] = c.string(forColumnIndex: 6) ?? ""
  5949. dataQuery["file_id"] = c.string(forColumnIndex: 7) ?? ""
  5950. c.close()
  5951. }
  5952. })
  5953. return dataQuery
  5954. }
  5955. @objc func segmentedControlValueChanged(_ sender: segmentedControllerObject) {
  5956. switch sender.selectedSegmentIndex {
  5957. case 0:
  5958. sender.navigation.viewControllers[0].children[1].view.isHidden = true
  5959. break;
  5960. case 1:
  5961. sender.navigation.viewControllers[0].children[1].view.isHidden = false
  5962. break;
  5963. default:
  5964. break;
  5965. }
  5966. }
  5967. private func copyOption(indexPath: IndexPath) -> UIMenu {
  5968. var ratingButtonTitles = ["Text".localized(), "Image".localized()]
  5969. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  5970. ratingButtonTitles = ["Image".localized()]
  5971. }
  5972. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  5973. let copyActions = ratingButtonTitles
  5974. .enumerated()
  5975. .map { index, title in
  5976. return UIAction(
  5977. title: title,
  5978. identifier: nil,
  5979. handler: {(_) in
  5980. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  5981. DispatchQueue.main.async {
  5982. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5983. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5984. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5985. if let dirPath = paths.first {
  5986. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  5987. if FileManager.default.fileExists(atPath: imageURL.path) {
  5988. let image = UIImage(contentsOfFile: imageURL.path)
  5989. UIPasteboard.general.image = image
  5990. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  5991. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  5992. do {
  5993. if var imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  5994. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  5995. if dataDecrypt != nil {
  5996. imageData = dataDecrypt!
  5997. }
  5998. let image = UIImage(data: imageData)
  5999. UIPasteboard.general.image = image
  6000. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  6001. }
  6002. } catch {
  6003. }
  6004. }
  6005. }
  6006. }
  6007. return
  6008. }
  6009. if (index == 0) {
  6010. DispatchQueue.main.async {
  6011. UIPasteboard.general.string = dataMessages[indexPath.row]["message_text"] as? String
  6012. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  6013. }
  6014. } else {
  6015. DispatchQueue.main.async {
  6016. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6017. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6018. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6019. if let dirPath = paths.first {
  6020. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  6021. if FileManager.default.fileExists(atPath: imageURL.path) {
  6022. let image = UIImage(contentsOfFile: imageURL.path)
  6023. UIPasteboard.general.image = image
  6024. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  6025. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  6026. do {
  6027. if var imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  6028. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  6029. if dataDecrypt != nil {
  6030. imageData = dataDecrypt!
  6031. }
  6032. let image = UIImage(data: imageData)
  6033. UIPasteboard.general.image = image
  6034. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  6035. }
  6036. } catch {
  6037. }
  6038. }
  6039. }
  6040. }
  6041. }
  6042. self.dismissKeyboard()
  6043. })
  6044. }
  6045. return UIMenu(
  6046. title: "Copy".localized(),
  6047. image: UIImage(systemName: "doc.on.doc.fill"),
  6048. children: copyActions)
  6049. }
  6050. private func actionDelete(for type: String, title: String, dataMessages: [[String: Any?]]) -> UIAlertAction? {
  6051. return UIAlertAction(title: title, style: .destructive) { [unowned self] _ in
  6052. for i in 0..<dataMessages.count {
  6053. if (type == "me") {
  6054. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  6055. for i in 0..<groupingImages.count {
  6056. self.deleteMessage(l_pin: groupingImages[i].lPin, message_id: groupingImages[i].messageId, scope: MessageScope.WHISPER, type: "1", chat: "")
  6057. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId })
  6058. if idx != nil {
  6059. self.dataMessages.remove(at: idx!)
  6060. // if (idx == self.dataMessages.count - 1) {
  6061. // NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  6062. // }
  6063. for i in 0..<dataDates.count {
  6064. if i > dataDates.count - 1 {
  6065. continue
  6066. }
  6067. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  6068. dataDates.remove(at: i)
  6069. }
  6070. }
  6071. }
  6072. }
  6073. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  6074. } else {
  6075. self.deleteMessage(l_pin: dataMessages[i]["l_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: MessageScope.WHISPER, type: "1", chat: "")
  6076. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  6077. if idx != nil {
  6078. self.dataMessages.remove(at: idx!)
  6079. // if (idx == self.dataMessages.count - 1) {
  6080. // NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  6081. // }
  6082. for i in 0..<dataDates.count {
  6083. if i > dataDates.count - 1 {
  6084. continue
  6085. }
  6086. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  6087. dataDates.remove(at: i)
  6088. }
  6089. }
  6090. }
  6091. }
  6092. } else {
  6093. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  6094. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  6095. imageView.tintColor = .white
  6096. 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)
  6097. banner.show()
  6098. } else {
  6099. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  6100. for i in 0..<groupingImages.count {
  6101. self.deleteMessage(l_pin: groupingImages[i].lPin, message_id: groupingImages[i].messageId, scope: MessageScope.WHISPER, type: "2", chat: "")
  6102. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId})
  6103. if idx != nil {
  6104. self.dataMessages[idx!]["lock"] = "1"
  6105. self.dataMessages[idx!]["attachment_flag"] = "0"
  6106. self.dataMessages[idx!]["reff_id"] = ""
  6107. }
  6108. }
  6109. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[0].messageId}) {
  6110. var dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  6111. dataMessageInGrouping.remove(at: 0)
  6112. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx+1)
  6113. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  6114. }
  6115. } else {
  6116. self.deleteMessage(l_pin: dataMessages[i]["l_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: MessageScope.WHISPER, type: "2", chat: "")
  6117. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  6118. if idx != nil {
  6119. self.dataMessages[idx!]["lock"] = "1"
  6120. self.dataMessages[idx!]["attachment_flag"] = "0"
  6121. self.dataMessages[idx!]["reff_id"] = ""
  6122. }
  6123. }
  6124. }
  6125. }
  6126. if self.listTimerCredential[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  6127. self.listTimerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  6128. self.timerCredential[dataMessages[i]["message_id"] as? String ?? ""]?.invalidate()
  6129. self.timerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  6130. }
  6131. }
  6132. let dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  6133. self.pinAllMessages(dataMessages: dataMessagesPin)
  6134. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  6135. cancelAction()
  6136. }
  6137. }
  6138. private func updateProfile() {
  6139. let idMe = User.getMyPin() as String?
  6140. DispatchQueue.global().async {
  6141. let message = CoreMessage_TMessageBank.getBatchBuddiesInfos(p_f_pin: idMe!, last_update: 0)
  6142. let _ = Nexilis.write(message: message)
  6143. }
  6144. }
  6145. private func generateQRCode(from string: String) -> UIImage? {
  6146. let data = string.data(using: String.Encoding.ascii)
  6147. if let filter = CIFilter(name: "CIQRCodeGenerator") {
  6148. filter.setValue(data, forKey: "inputMessage")
  6149. let transform = CGAffineTransform(scaleX: 3, y: 3)
  6150. if let output = filter.outputImage?.transformed(by: transform) {
  6151. return UIImage(ciImage: output)
  6152. }
  6153. }
  6154. return nil
  6155. }
  6156. @objc func deleteReplyView() {
  6157. if self.containerPreviewReply.isDescendant(of: self.viewTextfield) {
  6158. self.containerPreviewReply.subviews.forEach { $0.removeFromSuperview() }
  6159. self.containerPreviewReply.removeConstraints(self.containerPreviewReply.constraints)
  6160. self.containerPreviewReply.removeFromSuperview()
  6161. self.reffId = nil
  6162. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  6163. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50 - (self.offset()*3)
  6164. if self.contraintBottomMention.constant > 0 {
  6165. self.contraintBottomMention.constant = self.contraintBottomMention.constant - 50
  6166. }
  6167. }, completion: nil)
  6168. }
  6169. }
  6170. @objc func removeLinkPreviewUntilEmptyTextView() {
  6171. isAlwaysHideLinkPreview = true
  6172. deleteLinkPreview()
  6173. }
  6174. @objc func deleteLinkPreview() {
  6175. if self.containerLink.isDescendant(of: self.viewTextfield) {
  6176. self.containerLink.subviews.forEach { $0.removeFromSuperview() }
  6177. self.containerLink.removeConstraints(self.containerLink.constraints)
  6178. self.containerLink.removeFromSuperview()
  6179. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  6180. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
  6181. if self.contraintBottomMention.constant > 0 {
  6182. self.contraintBottomMention.constant = self.contraintBottomMention.constant - 80
  6183. }
  6184. }, completion: nil)
  6185. self.showingLink = ""
  6186. }
  6187. if self.reffId != nil {
  6188. self.bottomAnchorPreviewReply.isActive = false
  6189. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  6190. self.bottomAnchorPreviewReply.isActive = true
  6191. }
  6192. }
  6193. }
  6194. //ECL
  6195. extension EditorPersonal: UICollectionViewDelegate, UICollectionViewDataSource {
  6196. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  6197. return 76
  6198. }
  6199. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  6200. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellSticker", for: indexPath)
  6201. if (cell.contentView.subviews.count > 0) {
  6202. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  6203. }
  6204. let imageSticker = UIImageView()
  6205. cell.contentView.addSubview(imageSticker)
  6206. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  6207. NSLayoutConstraint.activate([
  6208. imageSticker.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
  6209. imageSticker.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
  6210. imageSticker.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor),
  6211. imageSticker.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor)
  6212. ])
  6213. var imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  6214. if imageStickerBundle == nil {
  6215. imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  6216. }
  6217. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  6218. return cell
  6219. }
  6220. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  6221. sendChat(message_text: "sticker/\(stickers[indexPath.row])", attachment_flag: "11", viewController: self)
  6222. constraintBottomAttachment.constant = 0.0
  6223. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  6224. self.viewSticker.removeFromSuperview()
  6225. }
  6226. }
  6227. //ETB
  6228. extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPlayerDelegate {
  6229. // public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  6230. // checkNewMessage(tableView: tableView)
  6231. // }
  6232. public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  6233. if tableChatView.alpha != 1 {
  6234. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  6235. UIView.animate(withDuration: 0.5, animations: {
  6236. self.tableChatView.alpha = 1.0
  6237. })
  6238. })
  6239. }
  6240. }
  6241. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  6242. lastY = scrollView.contentOffset.y
  6243. DispatchQueue.main.async { [self] in
  6244. if tableChatView.alpha != 1 {
  6245. return
  6246. }
  6247. checkNewMessage(tableView: self.tableChatView)
  6248. }
  6249. }
  6250. public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  6251. if tableView == tableViewConfigFile {
  6252. tableView.deselectRow(at: indexPath, animated: true)
  6253. var type = ""
  6254. if indexPath.row == 0 {
  6255. type = "share,download"
  6256. } else {
  6257. type = "forward"
  6258. }
  6259. if !specFileString.contains(type) {
  6260. if !specFileString.isEmpty {
  6261. specFileString += ","
  6262. }
  6263. specFileString += type
  6264. } else {
  6265. specFileString = specFileString.replacingOccurrences(of: type, with: "")
  6266. if specFileString == "," {
  6267. specFileString = ""
  6268. }
  6269. }
  6270. if specFileString.isEmpty {
  6271. buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc_off", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
  6272. } else {
  6273. buttonSpec.setImage(UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal).resize(target: CGSize(width: 30, height: 30)), for: .normal)
  6274. }
  6275. tableView.reloadData()
  6276. return
  6277. }
  6278. if tableView == tableMention || tableView == tableMentionEdit {
  6279. tableView.deselectRow(at: indexPath, animated: true)
  6280. var nowTextField = textFieldSend!
  6281. if tableView == tableMentionEdit {
  6282. nowTextField = editTextView
  6283. }
  6284. let fulltextForMention = nowTextField.text.substring(from: 0, to: lastPositionCursorMention - 1)
  6285. let diff = nowTextField.text.count - fulltextForMention.count
  6286. let lines = fulltextForMention.split(separator: "\n")
  6287. if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
  6288. let words = lines[lastLineIndex].split(separator: " ")
  6289. if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
  6290. var lastWord = words[lastWordIndex]
  6291. if let textM = extractFromAtIfSymbolsBefore(String(lastWord)) {
  6292. lastWord = textM[textM.startIndex..<textM.endIndex]
  6293. }
  6294. if let rangeLastWord = fulltextForMention.range(of: lastWord, options: .backwards) {
  6295. listMentionInTextField.append(listMentionWithText[indexPath.row])
  6296. var addSpaceAfterReplacement = ""
  6297. if diff == 0 {
  6298. addSpaceAfterReplacement = " "
  6299. }
  6300. var text = nowTextField.text ?? ""
  6301. let nameMention = listMentionWithText[indexPath.row].fullName.trimmingCharacters(in: .whitespaces)
  6302. listMentionInTextField.last?.ex_block = "\(fulltextForMention.distance(from: fulltextForMention.startIndex, to: rangeLastWord.lowerBound) + nameMention.count)" //upperbound
  6303. let replacementText = "@\(nameMention)"
  6304. // Replace the old text with the new text using the replaceSubrange(_:with:) method
  6305. text.replaceSubrange(rangeLastWord, with: replacementText + addSpaceAfterReplacement)
  6306. nowTextField.attributedText = text.richText(isEditing: true, listMentionInTextField: listMentionInTextField)
  6307. let newPosition = nowTextField.position(from: nowTextField.beginningOfDocument, offset: nowTextField.text.count - diff)
  6308. nowTextField.selectedTextRange = nowTextField.textRange(from: newPosition!, to: newPosition!)
  6309. hideMention()
  6310. lastTextLength = nowTextField.text.count
  6311. return
  6312. }
  6313. }
  6314. }
  6315. }
  6316. if isContactCenter && indexPath.row == 0 && isRequestContactCenter {
  6317. return
  6318. }
  6319. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section] })
  6320. if copySession || forwardSession || deleteSession {
  6321. guard indexPath.row < dataMessages.count else {
  6322. return
  6323. }
  6324. if (dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" != "0" || dataMessages[indexPath.row]["lock"] as? String == "1") && !forwardSession && !deleteSession {
  6325. return
  6326. }
  6327. if !(dataMessages[indexPath.row]["image_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["video_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["file_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath.row]["audio_id"] as? String ?? "").isEmpty {
  6328. if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
  6329. return
  6330. } else {
  6331. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  6332. if file.isEmpty {
  6333. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  6334. if file.isEmpty {
  6335. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  6336. if file.isEmpty {
  6337. file = dataMessages[indexPath.row]["audio_id"] as? String ?? ""
  6338. }
  6339. }
  6340. }
  6341. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6342. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6343. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6344. if let dirPath = paths.first {
  6345. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  6346. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  6347. return
  6348. }
  6349. }
  6350. }
  6351. }
  6352. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String})
  6353. if idx != nil {
  6354. self.dataMessages[idx!]["isSelected"] = !(self.dataMessages[idx!]["isSelected"] as! Bool)
  6355. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  6356. }
  6357. containerMultpileSelectSession.subviews.forEach({ $0.removeFromSuperview() })
  6358. addSubviewMultipleSession()
  6359. return
  6360. }
  6361. let message = dataMessages[indexPath.row]
  6362. if let attachmentFlag = message["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  6363. if attachmentFlag == "27" || attachmentFlag == "26" {
  6364. let streamingController = (attachmentFlag == "27") ? QmeraCreateStreamingViewController() : CreateSeminarViewController()
  6365. if let messageText = message["message_text"],
  6366. let messageText = messageText as? String,
  6367. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  6368. if json["blog"] == nil {
  6369. json["blog"] = message["blog_id"] ?? nil
  6370. }
  6371. switch(attachmentFlag){
  6372. case "27":
  6373. (streamingController as! QmeraCreateStreamingViewController).data = json
  6374. default:
  6375. (streamingController as! CreateSeminarViewController).data = json
  6376. }
  6377. if json["by"] as? String != User.getMyPin() as String? {
  6378. switch(attachmentFlag){
  6379. case "27":
  6380. (streamingController as! QmeraCreateStreamingViewController).isJoin = true
  6381. default:
  6382. (streamingController as! CreateSeminarViewController).isJoin = true
  6383. }
  6384. }
  6385. }
  6386. let streamingNav = CustomNavigationController(rootViewController: streamingController)
  6387. streamingNav.modalPresentationStyle = .custom
  6388. streamingNav.navigationBar.tintColor = .white
  6389. streamingNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  6390. streamingNav.navigationBar.isTranslucent = false
  6391. streamingNav.navigationBar.overrideUserInterfaceStyle = .dark
  6392. streamingNav.navigationBar.barStyle = .black
  6393. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  6394. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  6395. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  6396. streamingNav.navigationBar.titleTextAttributes = textAttributes
  6397. streamingNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  6398. streamingNav.navigationBar.isTranslucent = false
  6399. navigationController?.present(streamingNav, animated: true, completion: nil)
  6400. } else if attachmentFlag == "25" {
  6401. let conferenceController = CreateSeminarViewController()
  6402. if let messageText = message["message_text"],
  6403. let messageText = messageText as? String,
  6404. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  6405. if json["blog"] == nil {
  6406. json["blog"] = message["blog"] ?? nil
  6407. }
  6408. json["participant"] = message["members"]
  6409. let start = json["time"] as? Int64 ?? 0
  6410. json["start"] = String(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))
  6411. conferenceController.data = json
  6412. conferenceController.isJoin = true
  6413. }
  6414. let conferenceNav = CustomNavigationController(rootViewController: conferenceController)
  6415. conferenceNav.modalPresentationStyle = .custom
  6416. conferenceNav.navigationBar.tintColor = .white
  6417. conferenceNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  6418. conferenceNav.navigationBar.isTranslucent = false
  6419. conferenceNav.navigationBar.overrideUserInterfaceStyle = .dark
  6420. conferenceNav.navigationBar.barStyle = .black
  6421. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  6422. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  6423. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  6424. conferenceNav.navigationBar.titleTextAttributes = textAttributes
  6425. conferenceNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  6426. conferenceNav.navigationBar.isTranslucent = false
  6427. navigationController?.present(conferenceNav, animated: true, completion: nil)
  6428. } else if message["message_scope_id"] as? String == MessageScope.FORM {
  6429. let formView = FormEditor()
  6430. let messageText = message["message_text"] as? String ?? ""
  6431. formView.jsonData = messageText
  6432. formView.dataMessage = message
  6433. formView.dataPerson = self.dataPerson
  6434. formView.modalPresentationStyle = .custom
  6435. formView.modalTransitionStyle = .crossDissolve
  6436. formView.view.backgroundColor = .black.withAlphaComponent(0.2)
  6437. self.present(formView, animated: true, completion: nil)
  6438. }
  6439. }
  6440. }
  6441. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  6442. if tableView == tableViewConfigFile {
  6443. return nil
  6444. }
  6445. if tableView == tableMention || tableView == tableMentionEdit {
  6446. return .none
  6447. }
  6448. let containerView = UIView()
  6449. containerView.backgroundColor = .clear
  6450. let dateView = UIView()
  6451. containerView.addSubview(dateView)
  6452. dateView.translatesAutoresizingMaskIntoConstraints = false
  6453. var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
  6454. topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10)
  6455. NSLayoutConstraint.activate([
  6456. topAnchor,
  6457. dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
  6458. dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
  6459. dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  6460. ])
  6461. dateView.backgroundColor = .orangeColor
  6462. dateView.layer.cornerRadius = 8.0
  6463. dateView.clipsToBounds = true
  6464. let labelDate = UILabel()
  6465. dateView.addSubview(labelDate)
  6466. labelDate.translatesAutoresizingMaskIntoConstraints = false
  6467. NSLayoutConstraint.activate([
  6468. labelDate.centerYAnchor.constraint(equalTo: dateView.centerYAnchor),
  6469. labelDate.centerXAnchor.constraint(equalTo: dateView.centerXAnchor),
  6470. labelDate.leadingAnchor.constraint(equalTo: dateView.leadingAnchor, constant: 10),
  6471. labelDate.trailingAnchor.constraint(equalTo: dateView.trailingAnchor, constant: -10),
  6472. ])
  6473. labelDate.textAlignment = .center
  6474. labelDate.textColor = .secondaryColor
  6475. labelDate.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  6476. labelDate.text = dataDates[section]
  6477. if listViewOnSection.count == 0 || listViewOnSection.count - 1 < section {
  6478. listViewOnSection.append(containerView)
  6479. } else {
  6480. listViewOnSection.remove(at: section)
  6481. listViewOnSection.insert(containerView, at: section)
  6482. }
  6483. return containerView
  6484. }
  6485. public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  6486. if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
  6487. return 0
  6488. }
  6489. return 30
  6490. }
  6491. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  6492. if tableView == tableViewConfigFile {
  6493. let cell = tableView.dequeueReusableCell(withIdentifier: "cellConfigFile", for: indexPath as IndexPath)
  6494. var content = cell.defaultContentConfiguration()
  6495. content.textProperties.font = .systemFont(ofSize: 16, weight: .medium)
  6496. content.textProperties.color = .label
  6497. content.secondaryTextProperties.font = .systemFont(ofSize: 14)
  6498. content.secondaryTextProperties.color = .gray
  6499. if indexPath.row == 0 {
  6500. content.text = "Can Share and Download".localized()
  6501. content.secondaryText = "The user, as the receiver, can share and download the attachment.".localized()
  6502. cell.accessoryType = specFileString.contains("share,download") ? .checkmark : .none
  6503. } else {
  6504. content.text = "Can Forward".localized()
  6505. content.secondaryText = "The user, as the receiver, can forward the attachment.".localized()
  6506. cell.accessoryType = specFileString.contains("forward") ? .checkmark : .none
  6507. }
  6508. cell.contentConfiguration = content
  6509. cell.tintColor = .black
  6510. return cell
  6511. }
  6512. if tableView == tableMention || tableView == tableMentionEdit {
  6513. let cellMention = tableView.dequeueReusableCell(withIdentifier: tableView == tableMention ? "cellMention" : "cellEditMention", for: indexPath as IndexPath)
  6514. var content = cellMention.defaultContentConfiguration()
  6515. content.textProperties.font = UIFont.systemFont(ofSize: 11 + offset())
  6516. content.imageProperties.tintColor = .black
  6517. content.imageProperties.maximumSize = CGSize(width: 24, height: 24)
  6518. if indexPath.row < listMentionWithText.count {
  6519. if listMentionWithText[indexPath.row].pin == "-997" {
  6520. if let urlGif = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif"), let data = try? Data(contentsOf: urlGif), let source = CGImageSourceCreateWithData(data as CFData, nil), let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) {
  6521. let staticImage = UIImage(cgImage: cgImage)
  6522. content.image = staticImage.circleMasked
  6523. } else if let urlGif = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif"), let data = try? Data(contentsOf: urlGif), let source = CGImageSourceCreateWithData(data as CFData, nil), let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) {
  6524. let staticImage = UIImage(cgImage: cgImage)
  6525. content.image = staticImage.circleMasked
  6526. }
  6527. } else {
  6528. getImage(name: listMentionWithText[indexPath.row].thumb, placeholderImage: UIImage(systemName: "person"), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
  6529. content.image = image
  6530. })
  6531. }
  6532. content.text = listMentionWithText[indexPath.row].firstName + " " + listMentionWithText[indexPath.row].lastName
  6533. }
  6534. cellMention.contentConfiguration = content
  6535. return cellMention
  6536. }
  6537. let idMe = User.getMyPin() as String?
  6538. let dateKey = dataDates[indexPath.section]
  6539. let dataMessages = messagesByDate[dateKey]!
  6540. let profileMessage = UIImageView()
  6541. let cell = tableView.dequeueReusableCell(withIdentifier: "cellEditorPersonal", for: indexPath as IndexPath)
  6542. cell.contentView.subviews.forEach({ $0.removeConstraints($0.constraints) })
  6543. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  6544. if isContactCenter && isRequestContactCenter && dataMessages[indexPath.row]["category_cc"] != nil {
  6545. cell.backgroundColor = .clear
  6546. cell.selectionStyle = .none
  6547. if dataMessages[indexPath.row]["category_cc"] is [CategoryCC] {
  6548. let category_cc = dataMessages[indexPath.row]["category_cc"] as! [CategoryCC]
  6549. profileMessage.frame.size = CGSize(width: 35, height: 35)
  6550. cell.contentView.addSubview(profileMessage)
  6551. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  6552. profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  6553. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  6554. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  6555. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  6556. profileMessage.circle()
  6557. profileMessage.clipsToBounds = true
  6558. profileMessage.backgroundColor = .lightGray
  6559. profileMessage.image = UIImage(systemName: "person")
  6560. profileMessage.tintColor = .white
  6561. profileMessage.contentMode = .scaleAspectFit
  6562. getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  6563. profileMessage.image = image
  6564. }
  6565. profileMessage.contentMode = .scaleAspectFill
  6566. let containerMessage = UIView()
  6567. cell.contentView.addSubview(containerMessage)
  6568. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  6569. containerMessage.topAnchor.constraint(equalTo: profileMessage.bottomAnchor).isActive = true
  6570. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 5).isActive = true
  6571. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
  6572. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  6573. // containerMessage.backgroundColor = .grayColor
  6574. // containerMessage.layer.cornerRadius = 10.0
  6575. // containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  6576. // containerMessage.clipsToBounds = true
  6577. // let timeMessage = UILabel()
  6578. // cell.contentView.addSubview(timeMessage)
  6579. // timeMessage.translatesAutoresizingMaskIntoConstraints = false
  6580. // timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  6581. let messageText = UILabel()
  6582. containerMessage.addSubview(messageText)
  6583. messageText.translatesAutoresizingMaskIntoConstraints = false
  6584. messageText.numberOfLines = 0
  6585. messageText.lineBreakMode = .byWordWrapping
  6586. containerMessage.addSubview(messageText)
  6587. messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 5).isActive = true
  6588. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6589. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6590. if !contextCC.isEmpty {
  6591. let dataSplit = contextCC.components(separatedBy: "~")
  6592. let contentErr = dataSplit[0]
  6593. var activity = ""
  6594. var titleErr = ""
  6595. if dataSplit.count > 1 {
  6596. activity = dataSplit[1]
  6597. }
  6598. if dataSplit.count > 2 {
  6599. titleErr = dataSplit[2]
  6600. }
  6601. var welcome = ""
  6602. let time = Utils.getGreetingsTimeDefaultWelcome()
  6603. if time == "1" {
  6604. welcome = "Selamat pagi"
  6605. } else if time == "2" {
  6606. welcome = "Selamat siang"
  6607. } else {
  6608. welcome = "Selamat malam"
  6609. }
  6610. var myName = ""
  6611. let myData = User.getDataCanNil(pin: User.getMyPin())
  6612. if myData != nil {
  6613. myName = myData!.fullName
  6614. }
  6615. messageText.attributedText = "_\(welcome) Pak/Bu *\(myName)*. Kami mendeteksi anda mengalami hambatan pada waktu *\(activity)*, dikarenakan *\(titleErr)*. Kami siap membantu anda untuk mensolusikan masalah yang dihadapi. Silahkan memilih cara interaksi yang diinginkan melalui tombol di bawah ini._".richText(fontSize: 14)
  6616. } else if category_cc[0].id.contains("level0_") || dataMessages[indexPath.row]["attachment_flag"] != nil && dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "503" {
  6617. messageText.text = "Welcome to".localized() + " " + dataPerson["name"]!! + " " + "Contact Center".localized()
  6618. + "\n" + "Please choose your desired communication method...".localized()
  6619. } else if category_cc[0].id.contains("level1_") {
  6620. messageText.text = "Please select your Consultation Topic:".localized()
  6621. } else if !category_cc[0].id.contains("level1_") && dataMessages[indexPath.row]["attachment_flag"] == nil {
  6622. messageText.text = "Please select the type of topic that you chosen".localized()
  6623. } else if dataMessages[indexPath.row]["attachment_flag"] != nil && dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "502" {
  6624. messageText.text = "Please select the information option:".localized()
  6625. } else {
  6626. messageText.text = "Sorry, currently all our representatives are busy helping other customers. Do you want us to get back to you as soon as one of them is available?".localized()
  6627. }
  6628. if contextCC.isEmpty {
  6629. messageText.font = UIFont.systemFont(ofSize: 14 + offset(), weight: .medium)
  6630. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  6631. }
  6632. // let date = Date()
  6633. // let formatter = DateFormatter()
  6634. // formatter.dateFormat = "HH:mm"
  6635. // formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  6636. // timeMessage.text = formatter.string(from: date as Date)
  6637. // timeMessage.font = UIFont.systemFont(ofSize: 10, weight: .medium)
  6638. // timeMessage.textColor = .lightGray
  6639. let containerButton = UIView()
  6640. cell.contentView.addSubview(containerButton)
  6641. containerButton.translatesAutoresizingMaskIntoConstraints = false
  6642. containerButton.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  6643. containerButton.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  6644. containerButton.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  6645. containerButton.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width * 0.9).isActive = true
  6646. containerButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 55).isActive = true
  6647. containerButton.backgroundColor = .clear
  6648. for i in 0..<category_cc.count {
  6649. let buttonChat = UIButton(type: .custom)
  6650. containerButton.addSubview(buttonChat)
  6651. buttonChat.translatesAutoresizingMaskIntoConstraints = false
  6652. buttonChat.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width * 0.9 / 2 - 5).isActive = true
  6653. buttonChat.heightAnchor.constraint(greaterThanOrEqualToConstant: 55).isActive = true
  6654. if i % 2 == 0 {
  6655. if i / 2 + 1 == 1 {
  6656. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: 5).isActive = true
  6657. } else {
  6658. var constantTop = (i / 2 - 1) * 50
  6659. if constantTop == 0 {
  6660. constantTop = 55
  6661. } else {
  6662. constantTop = constantTop + 55
  6663. }
  6664. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: CGFloat(constantTop)).isActive = true
  6665. }
  6666. if i == category_cc.count - 1 {
  6667. buttonChat.bottomAnchor.constraint(equalTo: containerButton.bottomAnchor, constant: -5).isActive = true
  6668. }
  6669. buttonChat.leadingAnchor.constraint(equalTo: containerButton.leadingAnchor, constant: 5).isActive = true
  6670. } else {
  6671. let newi = i - 1
  6672. if newi / 2 + 1 == 1 {
  6673. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: 5).isActive = true
  6674. } else {
  6675. var constantTop = (newi / 2 - 1) * 50
  6676. if constantTop == 0 {
  6677. constantTop = 55
  6678. } else {
  6679. constantTop = constantTop + 55
  6680. }
  6681. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: CGFloat(constantTop)).isActive = true
  6682. }
  6683. if i == category_cc.count - 1 {
  6684. buttonChat.bottomAnchor.constraint(equalTo: containerButton.bottomAnchor, constant: -5).isActive = true
  6685. }
  6686. buttonChat.trailingAnchor.constraint(equalTo: containerButton.trailingAnchor, constant: -5).isActive = true
  6687. }
  6688. if category_cc[i].isActive {
  6689. buttonChat.backgroundColor = .orangeBNI
  6690. }
  6691. var nameImage = "pb_cc_bg_messaging"
  6692. if i == 1 {
  6693. nameImage = "pb_cc_bg_sms"
  6694. } else if i == 2 {
  6695. nameImage = "pb_cc_bg_voip"
  6696. } else if i == 3 {
  6697. nameImage = "pb_cc_bg_email"
  6698. } else if i == 4 {
  6699. nameImage = "pb_cc_bg_videocall"
  6700. } else if i == 5 {
  6701. nameImage = "pb_cc_bg_gsmcall"
  6702. } else if i == 6 {
  6703. nameImage = "pb_cc_bg_gptchatbot"
  6704. } else if i == 7 {
  6705. nameImage = "pb_cc_bg_whatsapp"
  6706. }
  6707. buttonChat.setImage(resizeImage(image: UIImage(named: nameImage, in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: self.view!.frame.size.width * 0.9 / 2 - 5, height: 55)), for: .normal)
  6708. // buttonChat.setTitle(category_cc[i].service_name.localized(), for: .normal)
  6709. // buttonChat.setTitleColor(.black, for: .normal)
  6710. // buttonChat.setImage(resizeImage(image: UIImage(named: "pb_gpt_bot", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)), for: .normal)
  6711. // buttonChat.contentHorizontalAlignment = .left
  6712. // buttonChat.imageEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) // Adjust left inset for the image
  6713. // buttonChat.titleEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) // Adjust left inset for the title
  6714. // buttonChat.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
  6715. // buttonChat.titleLabel?.numberOfLines = 0
  6716. // buttonChat.layer.borderWidth = 2
  6717. // buttonChat.layer.borderColor = UIColor.white.cgColor
  6718. // buttonChat.backgroundColor = .grayColor
  6719. // buttonChat.layer.cornerRadius = 8.0
  6720. // buttonChat.clipsToBounds = true
  6721. buttonChat.restorationIdentifier = "\(category_cc[i].id),\(category_cc[i].service_id)"
  6722. if dataMessages[indexPath.row]["attachment_flag"] != nil {
  6723. buttonChat.tag = Int(dataMessages[indexPath.row]["attachment_flag"] as? String ?? "")!
  6724. }
  6725. buttonChat.addTarget(self, action: #selector(ccAction(sender:)), for: .touchUpInside)
  6726. }
  6727. } else {
  6728. let messageWait = UILabel()
  6729. cell.contentView.addSubview(messageWait)
  6730. messageWait.translatesAutoresizingMaskIntoConstraints = false
  6731. messageWait.topAnchor.constraint(equalTo: cell.contentView.topAnchor).isActive = true
  6732. messageWait.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true
  6733. messageWait.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor, constant: 10).isActive = true
  6734. messageWait.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor, constant: -10).isActive = true
  6735. messageWait.text = dataMessages[indexPath.row]["category_cc"] as? String ?? dataMessages[indexPath.row]["message_text"] as? String ?? ""
  6736. messageWait.numberOfLines = 0
  6737. messageWait.font = UIFont.systemFont(ofSize: 12 + offset())
  6738. messageWait.textColor = .gray
  6739. messageWait.textAlignment = .center
  6740. }
  6741. return cell
  6742. }
  6743. let messageIdChat = (dataMessages[indexPath.row]["message_id"] as? String) ?? ""
  6744. let thumbChat = (dataMessages[indexPath.row]["thumb_id"] as? String) ?? ""
  6745. let imageChat = (dataMessages[indexPath.row]["image_id"] as? String) ?? ""
  6746. let videoChat = (dataMessages[indexPath.row]["video_id"] as? String) ?? ""
  6747. let fileChat = (dataMessages[indexPath.row]["file_id"] as? String) ?? ""
  6748. let reffChat = (dataMessages[indexPath.row]["reff_id"] as? String) ?? ""
  6749. let audioChat = (dataMessages[indexPath.row]["audio_id"] as? String) ?? ""
  6750. let gifChat = (dataMessages[indexPath.row]["gif_id"] as? String) ?? ""
  6751. let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as? String ?? "")]
  6752. let is_bot = (dataMessages[indexPath.row][TypeDataMessage.is_bot] as? Int) ?? 0
  6753. cell.backgroundColor = .clear
  6754. cell.selectionStyle = .none
  6755. let nameSender = UILabel()
  6756. if isContactCenter || is_bot == 1 {
  6757. profileMessage.frame.size = CGSize(width: 35, height: 35)
  6758. cell.contentView.addSubview(profileMessage)
  6759. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  6760. profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  6761. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6762. profileMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
  6763. } else {
  6764. if copySession || forwardSession || deleteSession {
  6765. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
  6766. } else {
  6767. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  6768. }
  6769. }
  6770. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  6771. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  6772. profileMessage.circle()
  6773. profileMessage.clipsToBounds = true
  6774. profileMessage.backgroundColor = .lightGray
  6775. profileMessage.image = UIImage(systemName: "person")
  6776. profileMessage.tintColor = .white
  6777. profileMessage.contentMode = .scaleAspectFit
  6778. if is_bot == 1 {
  6779. if let urlGif = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") {
  6780. profileMessage.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
  6781. if error == nil {
  6782. profileMessage.animationImages = image?.images
  6783. profileMessage.animationDuration = image?.duration ?? 0.0
  6784. profileMessage.animationRepeatCount = 0
  6785. profileMessage.startAnimating()
  6786. }
  6787. }
  6788. } else if let urlGif = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") {
  6789. profileMessage.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in
  6790. if error == nil {
  6791. profileMessage.animationImages = image?.images
  6792. profileMessage.animationDuration = image?.duration ?? 0.0
  6793. profileMessage.animationRepeatCount = 0
  6794. profileMessage.startAnimating()
  6795. }
  6796. }
  6797. }
  6798. nameSender.text = Utils.getGPTBotName()
  6799. } else {
  6800. let user = User.getData(pin: dataMessages[indexPath.row]["f_pin"] as? String)
  6801. getImage(name: user?.thumb ?? "", placeholderImage: UIImage(systemName: "person.circle.fill")!, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in
  6802. profileMessage.image = image
  6803. }
  6804. nameSender.text = user?.fullName ?? ""
  6805. }
  6806. profileMessage.contentMode = .scaleAspectFill
  6807. cell.contentView.addSubview(nameSender)
  6808. nameSender.translatesAutoresizingMaskIntoConstraints = false
  6809. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  6810. nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
  6811. } else {
  6812. nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  6813. }
  6814. nameSender.font = UIFont.systemFont(ofSize: 12 + offset(), weight: UIFont.Weight(800))
  6815. nameSender.textAlignment = .right
  6816. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6817. nameSender.trailingAnchor.constraint(equalTo:profileMessage.leadingAnchor, constant: -5).isActive = true
  6818. nameSender.textColor = .systemBlue
  6819. } else {
  6820. nameSender.leadingAnchor.constraint(equalTo:profileMessage.trailingAnchor, constant: 5).isActive = true
  6821. nameSender.textColor = .orangeColor
  6822. }
  6823. }
  6824. let containerMessage = UIView()
  6825. cell.contentView.addSubview(containerMessage)
  6826. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  6827. if messageIdChat.contains("NTFPIN_") {
  6828. containerMessage.backgroundColor = .orangeColor
  6829. containerMessage.anchor(top: cell.contentView.topAnchor, bottom: cell.contentView.bottomAnchor, paddingTop: 5, paddingBottom: 5, centerX: cell.contentView.centerXAnchor, minWidth: 40, maxWidth: UIScreen.main.bounds.width - 40)
  6830. containerMessage.layer.cornerRadius = 8
  6831. containerMessage.clipsToBounds = true
  6832. let textMessage = UILabel()
  6833. containerMessage.addSubview(textMessage)
  6834. textMessage.textAlignment = .center
  6835. textMessage.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingBottom: 5, paddingRight: 10)
  6836. textMessage.font = .systemFont(ofSize: 14)
  6837. textMessage.text = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  6838. textMessage.textColor = .white
  6839. return cell
  6840. }
  6841. let timeMessage = UILabel()
  6842. timeMessage.numberOfLines = 0
  6843. cell.contentView.addSubview(timeMessage)
  6844. timeMessage.translatesAutoresizingMaskIntoConstraints = false
  6845. if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
  6846. (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
  6847. !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
  6848. (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
  6849. (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  6850. timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
  6851. } else {
  6852. timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  6853. }
  6854. let statusMessage = UIImageView()
  6855. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "0" && dataMessages[indexPath.row]["lock"] as? String != "1") || forwardSession || deleteSession {
  6856. var showSelectedImage = true
  6857. if (!imageChat.isEmpty || !videoChat.isEmpty || !fileChat.isEmpty) && forwardSession {
  6858. if !Nexilis.checkingAccess(key: "secure_folder_forward") && !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").contains("forward") {
  6859. showSelectedImage = false
  6860. } else {
  6861. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  6862. if file.isEmpty {
  6863. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  6864. if file.isEmpty {
  6865. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  6866. if file.isEmpty {
  6867. file = dataMessages[indexPath.row]["audio_id"] as? String ?? ""
  6868. }
  6869. }
  6870. }
  6871. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6872. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6873. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6874. if let dirPath = paths.first {
  6875. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  6876. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  6877. showSelectedImage = false
  6878. }
  6879. }
  6880. }
  6881. }
  6882. if showSelectedImage {
  6883. let selectedImage = UIImageView()
  6884. cell.contentView.addSubview(selectedImage)
  6885. selectedImage.translatesAutoresizingMaskIntoConstraints = false
  6886. selectedImage.frame.size = CGSize(width: 20, height: 20)
  6887. var leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: -20)
  6888. selectedImage.isHidden = true
  6889. if copySession || forwardSession || deleteSession {
  6890. leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15)
  6891. selectedImage.isHidden = false
  6892. }
  6893. NSLayoutConstraint.activate([
  6894. leading,
  6895. selectedImage.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor),
  6896. selectedImage.widthAnchor.constraint(equalToConstant: 20),
  6897. selectedImage.heightAnchor.constraint(equalToConstant: 20)
  6898. ])
  6899. selectedImage.circle()
  6900. selectedImage.layer.borderWidth = 2
  6901. selectedImage.layer.borderColor = UIColor.mainColor.cgColor
  6902. if dataMessages[indexPath.row]["isSelected"] as! Bool {
  6903. selectedImage.image = UIImage(systemName: "checkmark.circle.fill")
  6904. }
  6905. selectedImage.tintColor = .mainColor
  6906. }
  6907. }
  6908. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6909. containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cell.contentView.leadingAnchor, constant: 60).isActive = true
  6910. if isContactCenter || is_bot == 1 {
  6911. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  6912. containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
  6913. } else {
  6914. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  6915. containerMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
  6916. }
  6917. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  6918. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  6919. containerMessage.backgroundColor = .clear
  6920. } else {
  6921. containerMessage.backgroundColor = .blueBubbleColor
  6922. }
  6923. containerMessage.layer.cornerRadius = 10.0
  6924. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  6925. containerMessage.clipsToBounds = true
  6926. timeMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  6927. if (dataMessages[indexPath.row]["lock"] as? String == "0" || (dataMessages[indexPath.row]["lock"] as? String ?? "").isEmpty) && dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String != MessageScope.CALL && dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String != MessageScope.MISSED_CALL {
  6928. cell.contentView.addSubview(statusMessage)
  6929. statusMessage.translatesAutoresizingMaskIntoConstraints = false
  6930. statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  6931. statusMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  6932. statusMessage.widthAnchor.constraint(equalToConstant: 15).isActive = true
  6933. statusMessage.heightAnchor.constraint(equalToConstant: 15).isActive = true
  6934. if dataMessages[indexPath.row]["status"]! as? String ?? "" == "0" {
  6935. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  6936. } else if dataMessages[indexPath.row]["status"]! as? String ?? "" == "1" {
  6937. statusMessage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
  6938. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "2" ) {
  6939. statusMessage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  6940. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "3") {
  6941. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  6942. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "8") {
  6943. statusMessage.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  6944. } else {
  6945. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  6946. }
  6947. }
  6948. } else {
  6949. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  6950. if isContactCenter || is_bot == 1 {
  6951. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  6952. } else {
  6953. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
  6954. }
  6955. let newMessagesView = UIView()
  6956. cell.contentView.addSubview(newMessagesView)
  6957. newMessagesView.translatesAutoresizingMaskIntoConstraints = false
  6958. NSLayoutConstraint.activate([
  6959. newMessagesView.topAnchor.constraint(equalTo: newMessagesView.topAnchor),
  6960. newMessagesView.bottomAnchor.constraint(equalTo: containerMessage.topAnchor),
  6961. newMessagesView.centerXAnchor.constraint(equalTo: cell.contentView.centerXAnchor),
  6962. newMessagesView.heightAnchor.constraint(equalToConstant: 30),
  6963. newMessagesView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  6964. ])
  6965. newMessagesView.backgroundColor = .greenColor
  6966. newMessagesView.layer.cornerRadius = 15.0
  6967. newMessagesView.clipsToBounds = true
  6968. let labelNewMessages = UILabel()
  6969. newMessagesView.addSubview(labelNewMessages)
  6970. labelNewMessages.translatesAutoresizingMaskIntoConstraints = false
  6971. NSLayoutConstraint.activate([
  6972. labelNewMessages.centerYAnchor.constraint(equalTo: newMessagesView.centerYAnchor),
  6973. labelNewMessages.centerXAnchor.constraint(equalTo: newMessagesView.centerXAnchor),
  6974. labelNewMessages.leadingAnchor.constraint(equalTo: newMessagesView.leadingAnchor, constant: 10),
  6975. labelNewMessages.trailingAnchor.constraint(equalTo: newMessagesView.trailingAnchor, constant: -10),
  6976. ])
  6977. labelNewMessages.textAlignment = .center
  6978. labelNewMessages.textColor = .secondaryColor
  6979. labelNewMessages.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  6980. labelNewMessages.text = "Unread Messages".localized()
  6981. } else {
  6982. if isContactCenter || is_bot == 1 {
  6983. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  6984. } else {
  6985. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  6986. }
  6987. }
  6988. if isContactCenter || is_bot == 1 {
  6989. containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
  6990. } else {
  6991. if copySession || forwardSession || deleteSession {
  6992. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
  6993. } else {
  6994. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  6995. }
  6996. }
  6997. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
  6998. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  6999. if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  7000. containerMessage.backgroundColor = .clear
  7001. } else {
  7002. containerMessage.backgroundColor = .whiteBubbleColor
  7003. }
  7004. containerMessage.layer.cornerRadius = 10.0
  7005. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  7006. containerMessage.clipsToBounds = true
  7007. timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  7008. }
  7009. if ((dataMessages[indexPath.row]["read_receipts"] as? String) == "8" ||
  7010. (dataMessages[indexPath.row]["credential"] as? String) == "1" ||
  7011. !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty) &&
  7012. (dataMessages[indexPath.row]["lock"] as? String) != "2" &&
  7013. (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  7014. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
  7015. } else {
  7016. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  7017. }
  7018. let imageStared = UIImageView()
  7019. if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" == "0") {
  7020. cell.contentView.addSubview(imageStared)
  7021. imageStared.translatesAutoresizingMaskIntoConstraints = false
  7022. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7023. imageStared.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
  7024. imageStared.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  7025. } else {
  7026. imageStared.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  7027. imageStared.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  7028. }
  7029. imageStared.widthAnchor.constraint(equalToConstant: 15).isActive = true
  7030. imageStared.heightAnchor.constraint(equalToConstant: 15).isActive = true
  7031. imageStared.image = UIImage(systemName: "star.fill")
  7032. imageStared.backgroundColor = .clear
  7033. imageStared.tintColor = .systemYellow
  7034. }
  7035. let imageAckView = UIImageView()
  7036. let imageCredentialView = UIImageView()
  7037. let imagePinView = UIImageView()
  7038. if dataMessages[indexPath.row][TypeDataMessage.is_pinned] as? String != nil && dataMessages[indexPath.row][TypeDataMessage.is_pinned] as? String != "0" {
  7039. cell.contentView.addSubview(imagePinView)
  7040. imagePinView.translatesAutoresizingMaskIntoConstraints = false
  7041. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7042. if imageStared.isDescendant(of: cell.contentView){
  7043. imagePinView.bottomAnchor.constraint(equalTo: imageStared.topAnchor).isActive = true
  7044. } else {
  7045. imagePinView.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
  7046. }
  7047. imagePinView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  7048. } else {
  7049. if imageStared.isDescendant(of: cell.contentView){
  7050. imagePinView.bottomAnchor.constraint(equalTo: imageStared.topAnchor).isActive = true
  7051. } else {
  7052. imagePinView.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  7053. }
  7054. imagePinView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  7055. }
  7056. imagePinView.widthAnchor.constraint(equalToConstant: 15).isActive = true
  7057. imagePinView.heightAnchor.constraint(equalToConstant: 15).isActive = true
  7058. imagePinView.image = UIImage(systemName: "pin.fill")
  7059. imagePinView.backgroundColor = .clear
  7060. imagePinView.tintColor = .lightGray
  7061. }
  7062. if dataMessages[indexPath.row]["read_receipts"] as? String == "8" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  7063. var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  7064. if dataMessages[indexPath.row]["status"] as? String == "8" {
  7065. imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  7066. }
  7067. imageAckView.image = imageAck
  7068. cell.contentView.addSubview(imageAckView)
  7069. imageAckView.translatesAutoresizingMaskIntoConstraints = false
  7070. imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7071. imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7072. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  7073. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7074. imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  7075. } else {
  7076. imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  7077. let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
  7078. tap.indexPath = indexPath
  7079. imageAckView.addGestureRecognizer(tap)
  7080. imageAckView.isUserInteractionEnabled = true
  7081. }
  7082. }
  7083. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  7084. let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  7085. imageCredentialView.image = imageCredential
  7086. cell.contentView.addSubview(imageCredentialView)
  7087. imageCredentialView.translatesAutoresizingMaskIntoConstraints = false
  7088. imageCredentialView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7089. imageCredentialView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7090. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  7091. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7092. imageCredentialView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  7093. } else {
  7094. imageCredentialView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  7095. }
  7096. }
  7097. if !(dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["lock"] as? String) != "2" && (dataMessages[indexPath.row]["lock"] as? String) != "1" {
  7098. let imageSpecFileView = UIImageView()
  7099. let imageSpecFile = UIImage(named: "pb_ic_attach_spc", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  7100. imageSpecFileView.image = imageSpecFile
  7101. cell.contentView.addSubview(imageSpecFileView)
  7102. imageSpecFileView.translatesAutoresizingMaskIntoConstraints = false
  7103. imageSpecFileView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7104. imageSpecFileView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7105. imageSpecFileView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  7106. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7107. if imageAckView.isDescendant(of: cell.contentView) {
  7108. imageSpecFileView.leadingAnchor.constraint(equalTo: imageAckView.trailingAnchor, constant: 5).isActive = true
  7109. } else if imageCredentialView.isDescendant(of: cell.contentView) {
  7110. imageSpecFileView.leadingAnchor.constraint(equalTo: imageCredentialView.trailingAnchor, constant: 5).isActive = true
  7111. } else {
  7112. imageSpecFileView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  7113. }
  7114. } else {
  7115. if imageAckView.isDescendant(of: cell.contentView) {
  7116. imageSpecFileView.trailingAnchor.constraint(equalTo: imageAckView.leadingAnchor, constant: -5).isActive = true
  7117. } else if imageCredentialView.isDescendant(of: cell.contentView) {
  7118. imageSpecFileView.trailingAnchor.constraint(equalTo: imageCredentialView.leadingAnchor, constant: -5).isActive = true
  7119. } else {
  7120. imageSpecFileView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  7121. }
  7122. }
  7123. }
  7124. let messageText = UITextView()
  7125. messageText.isEditable = false
  7126. messageText.isSelectable = true
  7127. messageText.dataDetectorTypes = [.link]
  7128. messageText.backgroundColor = .clear
  7129. messageText.isScrollEnabled = false
  7130. messageText.textContainerInset = UIEdgeInsets.zero
  7131. messageText.contentInset = UIEdgeInsets.zero
  7132. messageText.textDragInteraction?.isEnabled = false
  7133. containerMessage.addSubview(messageText)
  7134. messageText.translatesAutoresizingMaskIntoConstraints = false
  7135. var topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15)
  7136. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  7137. messageText.font = .systemFont(ofSize: 12 + offset())
  7138. var textChat = (dataMessages[indexPath.row]["message_text"] as? String) ?? ""
  7139. var messageRequestFriend: String!
  7140. if dataMessages[indexPath.row]["attachment_flag"] as? String == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String == "26" ||
  7141. dataMessages[indexPath.row]["attachment_flag"] as? String == "25" || dataMessages[indexPath.row]["message_scope_id"] as? String == MessageScope.FORM {
  7142. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
  7143. let imageLS = UIImageView()
  7144. containerMessage.addSubview(imageLS)
  7145. imageLS.translatesAutoresizingMaskIntoConstraints = false
  7146. NSLayoutConstraint.activate([
  7147. imageLS.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15.0),
  7148. imageLS.trailingAnchor.constraint(equalTo: messageText.leadingAnchor, constant: -10.0),
  7149. imageLS.centerYAnchor.constraint(equalTo: containerMessage.centerYAnchor),
  7150. imageLS.heightAnchor.constraint(equalToConstant: 60.0)
  7151. ])
  7152. if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "26" {
  7153. imageLS.image = UIImage(named: "pb_seminar_wpr", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  7154. } else if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "27" {
  7155. imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  7156. } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "25" {
  7157. imageLS.image = UIImage(named: "pb_vroom", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  7158. } else if dataMessages[indexPath.row]["message_scope_id"] as? String == MessageScope.FORM {
  7159. imageLS.image = UIImage(systemName: "doc.richtext.fill")
  7160. imageLS.tintColor = .mainColor
  7161. }
  7162. } else if !audioChat.isEmpty {
  7163. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 60).isActive = true
  7164. } else {
  7165. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  7166. }
  7167. if dataMessages[indexPath.row]["f_pin"] as? String == "-999" && (dataMessages[indexPath.row]["blog_id"] as? String) != nil && !(dataMessages[indexPath.row]["blog_id"] as? String ?? "").isEmpty && (dataMessages[indexPath.row]["message_text"] as? String ?? "").contains("Berikut QR Code dan detil booking Anda") {
  7168. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -115).isActive = true
  7169. let imageQR = UIImageView()
  7170. containerMessage.addSubview(imageQR)
  7171. imageQR.translatesAutoresizingMaskIntoConstraints = false
  7172. NSLayoutConstraint.activate([
  7173. imageQR.centerXAnchor.constraint(equalTo: containerMessage.centerXAnchor),
  7174. imageQR.topAnchor.constraint(equalTo: messageText.bottomAnchor),
  7175. imageQR.widthAnchor.constraint(equalToConstant: 100.0),
  7176. imageQR.heightAnchor.constraint(equalToConstant: 100.0)
  7177. ])
  7178. imageQR.image = generateQRCode(from: dataMessages[indexPath.row]["blog_id"] as? String ?? "")
  7179. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "61" {
  7180. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -50).isActive = true
  7181. let fPinFriend = dataMessages[indexPath.row]["blog_id"] as? String ?? ""
  7182. let buttonAccept = UIButton(type: .custom)
  7183. buttonAccept.setTitle("Accept".localized(), for: .normal)
  7184. buttonAccept.setBackgroundImage(UIImage(color: UIColor.clear), for: .normal)
  7185. buttonAccept.setBackgroundImage(UIImage(color: UIColor.blueBubbleColor), for: .highlighted)
  7186. buttonAccept.setTitleColor(.black, for: .normal)
  7187. buttonAccept.titleLabel?.font = UIFont.systemFont(ofSize: 12)
  7188. buttonAccept.layer.borderWidth = 2.0
  7189. buttonAccept.layer.borderColor = UIColor.blueBubbleColor.cgColor
  7190. buttonAccept.layer.cornerRadius = 8.0
  7191. buttonAccept.tag = 0
  7192. buttonAccept.restorationIdentifier = "\(fPinFriend),\(messageIdChat)"
  7193. buttonAccept.clipsToBounds = true
  7194. containerMessage.addSubview(buttonAccept)
  7195. buttonAccept.translatesAutoresizingMaskIntoConstraints = false
  7196. buttonAccept.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  7197. buttonAccept.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  7198. buttonAccept.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width/5).isActive = true
  7199. buttonAccept.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7200. buttonAccept.addTarget(self, action: #selector(addFriendReqAction), for: .touchUpInside)
  7201. let buttonDecline = UIButton(type: .custom)
  7202. buttonDecline.setTitle("Decline".localized(), for: .normal)
  7203. buttonDecline.setBackgroundImage(UIImage(color: UIColor.clear), for: .normal)
  7204. buttonDecline.setBackgroundImage(UIImage(color: UIColor.blueBubbleColor), for: .highlighted)
  7205. buttonDecline.setTitleColor(.black, for: .normal)
  7206. buttonDecline.titleLabel?.font = UIFont.systemFont(ofSize: 12)
  7207. buttonDecline.layer.borderWidth = 2.0
  7208. buttonDecline.tag = 1
  7209. buttonDecline.restorationIdentifier = "\(fPinFriend),\(messageIdChat)"
  7210. buttonDecline.layer.borderColor = UIColor.blueBubbleColor.cgColor
  7211. buttonDecline.layer.cornerRadius = 8.0
  7212. buttonDecline.clipsToBounds = true
  7213. containerMessage.addSubview(buttonDecline)
  7214. buttonDecline.translatesAutoresizingMaskIntoConstraints = false
  7215. buttonDecline.leadingAnchor.constraint(equalTo: buttonAccept.trailingAnchor, constant: 10).isActive = true
  7216. buttonDecline.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  7217. buttonDecline.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width/5).isActive = true
  7218. buttonDecline.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7219. buttonDecline.addTarget(self, action: #selector(addFriendReqAction), for: .touchUpInside)
  7220. let textName = textChat.components(separatedBy: "~")[0]
  7221. let textAfterName = textChat.components(separatedBy: "~")[1]
  7222. messageRequestFriend = textName + " " + textAfterName.localized()
  7223. } else {
  7224. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -15).isActive = true
  7225. }
  7226. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  7227. let originalMessageText = textChat
  7228. if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
  7229. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7230. textChat = "🚫 _"+"You were deleted this message".localized()+"_"
  7231. } else {
  7232. textChat = "🚫 _"+"This message was deleted".localized()+"_"
  7233. }
  7234. }
  7235. if dataMessages[indexPath.row]["lock"] as? String == "2" {
  7236. textChat = "🚫 _"+"Message has expired".localized()+"_"
  7237. }
  7238. if !audioChat.isEmpty {
  7239. textChat = textChat.components(separatedBy: "|")[0]
  7240. }
  7241. let imageSticker = UIImageView()
  7242. var stringLS = ""
  7243. if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  7244. if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
  7245. let data = textChat
  7246. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  7247. let title = json["title"] as? String ?? ""
  7248. let description = json["description"] as? String ?? ""
  7249. let start = json["time"] as? Int64 ?? 0
  7250. let by = json["by"] as? String ?? ""
  7251. let textLS = "Live Streaming".localized()
  7252. var type = "*\(textLS)*"
  7253. if attachmentFlag == "26" {
  7254. let textSeminar = "Seminar".localized()
  7255. type = "*\(textSeminar)*"
  7256. }
  7257. if let c = User.getData(pin: by) {
  7258. let name = c.fullName
  7259. stringLS = "\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: \(name)"
  7260. } else {
  7261. stringLS = ("\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))")
  7262. }
  7263. messageText.attributedText = stringLS.richText()
  7264. messageText.isUserInteractionEnabled = false
  7265. }
  7266. }
  7267. else if attachmentFlag == "25" {
  7268. let data = textChat
  7269. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  7270. let title = json["title"] as? String ?? ""
  7271. let blog = json["blog"] as? String ?? ""
  7272. let by = json["by"] as? String ?? ""
  7273. let start = json["time"] as? Int64 ?? 0
  7274. let textVCR = "Video Conference Room".localized()
  7275. var type = "*\(textVCR)*"
  7276. if let c = User.getData(pin: by) {
  7277. let name = c.fullName
  7278. stringLS = "\(type) \nTitle: \(title) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nInitiator: \(name) \n\n*^Room ID: ^*\n*^\(blog)^*"
  7279. }
  7280. messageText.attributedText = stringLS.richText()
  7281. messageText.isUserInteractionEnabled = false
  7282. }
  7283. }
  7284. else if attachmentFlag == "61" {
  7285. messageText.attributedText = messageRequestFriend.richText()
  7286. messageText.isUserInteractionEnabled = false
  7287. }
  7288. else if attachmentFlag == "11" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  7289. messageText.text = ""
  7290. topMarginText.constant = topMarginText.constant + 100
  7291. containerMessage.addSubview(imageSticker)
  7292. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  7293. let data = queryMessageReply(message_id: reffChat)
  7294. if reffChat.isEmpty || data.count == 0 {
  7295. imageSticker.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  7296. imageSticker.widthAnchor.constraint(equalToConstant: 80).isActive = true
  7297. } else {
  7298. imageSticker.widthAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true
  7299. }
  7300. imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  7301. imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  7302. imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  7303. var imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  7304. if imageStickerBundle == nil {
  7305. imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  7306. }
  7307. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  7308. imageSticker.contentMode = .scaleAspectFit
  7309. } else if dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" == MessageScope.FORM {
  7310. let data = textChat
  7311. if let jsonForm = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  7312. let form_title = jsonForm["form_title"] as? String ?? ""
  7313. let club_type = jsonForm["club_type"] as? String ?? ""
  7314. let province = jsonForm["province"] as? String ?? ""
  7315. let club = jsonForm["club"] as? String ?? ""
  7316. messageText.attributedText = "*\(form_title.replacingOccurrences(of: "+", with: " "))* \nClub Type: \(club_type) \nProvince: \(province) \nClub Name: \(club) ".richText()
  7317. messageText.isUserInteractionEnabled = false
  7318. }
  7319. }
  7320. else {
  7321. messageText.attributedText = textChat.richText()
  7322. modifyText()
  7323. }
  7324. } else {
  7325. messageText.attributedText = textChat.richText()
  7326. modifyText()
  7327. }
  7328. func modifyText() {
  7329. if !textChat.isEmpty {
  7330. if textChat.contains("■"){
  7331. textChat = textChat.components(separatedBy: "■")[0]
  7332. textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
  7333. }
  7334. if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  7335. textChat = textChat.components(separatedBy: "|")[1]
  7336. }
  7337. let finalAtribute = textChat.richText()
  7338. textChat = finalAtribute.string
  7339. let urlPattern = "(https?://|www\\.)\\S+"
  7340. if let regex = try? NSRegularExpression(pattern: urlPattern, options: []) {
  7341. let matches = regex.matches(in: textChat, options: [], range: NSRange(textChat.startIndex..., in: textChat))
  7342. for match in matches {
  7343. if let range = Range(match.range, in: textChat) {
  7344. let linkText = String(textChat[range])
  7345. let nsRange = NSRange(range, in: textChat)
  7346. finalAtribute.addAttribute(.link, value: linkText, range: nsRange)
  7347. finalAtribute.addAttribute(.foregroundColor, value: UIColor.blue, range: nsRange)
  7348. finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
  7349. }
  7350. }
  7351. }
  7352. messageText.attributedText = finalAtribute
  7353. messageText.delegate = self
  7354. }
  7355. }
  7356. if dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String == MessageScope.CALL || dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String == MessageScope.MISSED_CALL{
  7357. messageText.removeFromSuperview()
  7358. let containerCall = UIButton(type: .custom)
  7359. containerCall.backgroundColor = .white.withAlphaComponent(0.3)
  7360. containerMessage.addSubview(containerCall)
  7361. containerCall.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, height: 60)
  7362. containerCall.layer.cornerRadius = 5
  7363. containerCall.clipsToBounds = true
  7364. var imageCall = "phone.fill.arrow.up.right"
  7365. var textCall = "Audio call".localized()
  7366. let isVideo = textChat.lowercased().contains("video")
  7367. let isMissedCall = textChat.lowercased().contains("missed")
  7368. let isImageLeft = textChat.lowercased().contains("incoming") || isMissedCall
  7369. let longCall = textChat.components(separatedBy: " at ")[1]
  7370. var subTextCall = longCall
  7371. let contIconCall = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
  7372. containerCall.addSubview(contIconCall)
  7373. contIconCall.anchor(top: containerCall.topAnchor, left: containerCall.leftAnchor, bottom: containerCall.bottomAnchor, paddingTop: 10, paddingLeft: 10, paddingBottom: 10, width: 40, height: 40)
  7374. contIconCall.circle()
  7375. if isImageLeft {
  7376. contIconCall.backgroundColor = .white
  7377. } else {
  7378. contIconCall.backgroundColor = .black.withAlphaComponent(0.6)
  7379. }
  7380. if isVideo && isImageLeft {
  7381. imageCall = "arrow.down.left.video.fill"
  7382. if isMissedCall {
  7383. textCall = "Missed video call".localized()
  7384. subTextCall = "Tap to call back".localized()
  7385. } else {
  7386. textCall = "Video call".localized()
  7387. }
  7388. } else if isVideo {
  7389. imageCall = "arrow.up.right.video.fill"
  7390. textCall = "Video call".localized()
  7391. if longCall.trimmingCharacters(in: .whitespaces) == "0" {
  7392. subTextCall = "No answer".localized()
  7393. }
  7394. } else if isImageLeft {
  7395. imageCall = "phone.fill.arrow.down.left"
  7396. if isMissedCall {
  7397. textCall = "Missed audio call".localized()
  7398. subTextCall = "Tap to call back".localized()
  7399. }
  7400. } else if longCall.trimmingCharacters(in: .whitespaces) == "0" {
  7401. subTextCall = "No answer".localized()
  7402. }
  7403. let iconCall = UIImageView()
  7404. iconCall.image = UIImage(systemName: imageCall, withConfiguration: UIImage.SymbolConfiguration(pointSize: 18))
  7405. contIconCall.addSubview(iconCall)
  7406. if isMissedCall {
  7407. iconCall.tintColor = .red
  7408. }else if isImageLeft {
  7409. iconCall.tintColor = .black
  7410. } else {
  7411. iconCall.tintColor = .white
  7412. }
  7413. iconCall.anchor(centerX: contIconCall.centerXAnchor, centerY: contIconCall.centerYAnchor)
  7414. let titleCall = UILabel()
  7415. containerCall.addSubview(titleCall)
  7416. titleCall.anchor(top: containerCall.topAnchor, left: contIconCall.rightAnchor, right: containerCall.rightAnchor, paddingTop: 10, paddingLeft: 10, paddingRight: 10)
  7417. titleCall.text = textCall
  7418. titleCall.font = .systemFont(ofSize: 14)
  7419. let subtitleCall = UILabel()
  7420. containerCall.addSubview(subtitleCall)
  7421. subtitleCall.anchor(top: titleCall.bottomAnchor, left: contIconCall.rightAnchor, right: containerCall.rightAnchor, paddingLeft: 10, paddingRight: 10)
  7422. subtitleCall.text = subTextCall
  7423. subtitleCall.font = .systemFont(ofSize: 13)
  7424. subtitleCall.textColor = .gray
  7425. }
  7426. if !copySession && !forwardSession && !deleteSession && !self.removed {
  7427. let interaction = UIContextMenuInteraction(delegate: self)
  7428. containerMessage.addInteraction(interaction)
  7429. containerMessage.isUserInteractionEnabled = true
  7430. }
  7431. if isSearching && textSearch.count > 1 && dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String != MessageScope.CALL && dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String != MessageScope.MISSED_CALL {
  7432. messageText.attributedText = messageRequestFriend != nil ? messageRequestFriend.richText(isSearching: true, textSearch: textSearch) : stringLS.isEmpty ? textChat.richText(isSearching: true, textSearch: textSearch) : stringLS.richText(isSearching: true, textSearch: textSearch)
  7433. }
  7434. let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
  7435. if !stringDate.isEmpty {
  7436. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" && dataMessages[indexPath.row]["lock"] as? String != "1" {
  7437. if dataTimer != nil {
  7438. if dataTimer! >= 10 {
  7439. timeMessage.text = "00:\(dataTimer!)"
  7440. } else {
  7441. timeMessage.text = "00:0\(dataTimer!)"
  7442. }
  7443. timeMessage.textColor = .systemRed
  7444. }
  7445. } else {
  7446. let date = Date(milliseconds: Int64(stringDate) ?? 100)
  7447. let formatter = DateFormatter()
  7448. formatter.dateFormat = "HH:mm"
  7449. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  7450. timeMessage.text = formatter.string(from: date as Date)
  7451. timeMessage.textColor = .lightGray
  7452. }
  7453. timeMessage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  7454. if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
  7455. timeMessage.text = (timeMessage.text ?? "") + "\n" + "Edited".localized()
  7456. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7457. timeMessage.textAlignment = .right
  7458. }
  7459. }
  7460. }
  7461. let imageThumb = UIImageView()
  7462. let containerViewFile = UIView()
  7463. let imageGif = SDAnimatedImageView()
  7464. if !audioChat.isEmpty {
  7465. messageText.isHidden = true
  7466. let imageAudio = UIImageView()
  7467. imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
  7468. containerMessage.addSubview(imageAudio)
  7469. imageAudio.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 15, centerY: containerMessage.centerYAnchor)
  7470. imageAudio.tintColor = .mainColor
  7471. let playButtonAudio = UIButton(type: .system)
  7472. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  7473. playButtonAudio.tintColor = .gray
  7474. containerMessage.addSubview(playButtonAudio)
  7475. playButtonAudio.anchor(left: containerMessage.leftAnchor, paddingLeft: 60, centerY: containerMessage.centerYAnchor, width: 20, height: 20)
  7476. let progressSliderAudio = UISlider()
  7477. progressSliderAudio.minimumValue = 0
  7478. progressSliderAudio.maximumValue = 1
  7479. let thumbImage = UIImage(systemName: "circle.fill")?.withTintColor(UIColor.mainColor)
  7480. .resize(target: CGSize(width: 15, height: 15))
  7481. progressSliderAudio.setThumbImage(thumbImage, for: .normal)
  7482. containerMessage.addSubview(progressSliderAudio)
  7483. progressSliderAudio.anchor(left: playButtonAudio.rightAnchor, right: containerMessage.rightAnchor, paddingLeft: 10, paddingRight: 15, centerY: containerMessage.centerYAnchor, height: 15)
  7484. let timeLabelAudio = UILabel()
  7485. timeLabelAudio.text = "0:00"
  7486. timeLabelAudio.font = .systemFont(ofSize: 10 + offset())
  7487. timeLabelAudio.textColor = .gray
  7488. containerMessage.addSubview(timeLabelAudio)
  7489. timeLabelAudio.anchor(top: playButtonAudio.bottomAnchor, left: playButtonAudio.rightAnchor, paddingLeft: 10, width: 100, height: 12)
  7490. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7491. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7492. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7493. if let dirPath = paths.first {
  7494. let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(audioChat)
  7495. var url = audioURL
  7496. if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: audioChat) {
  7497. let activityIndicator = UIActivityIndicatorView(style: .medium)
  7498. activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  7499. activityIndicator.startAnimating()
  7500. playButtonAudio.setImage(nil, for: .normal)
  7501. playButtonAudio.addSubview(activityIndicator)
  7502. NSLayoutConstraint.activate([
  7503. activityIndicator.centerXAnchor.constraint(equalTo: playButtonAudio.centerXAnchor),
  7504. activityIndicator.centerYAnchor.constraint(equalTo: playButtonAudio.centerYAnchor)
  7505. ])
  7506. Download().startHTTP(forKey: audioChat) { (name, progress) in
  7507. guard progress == 100 else {
  7508. return
  7509. }
  7510. tableView.reloadRows(at: [indexPath], with: .none)
  7511. }
  7512. } else {
  7513. if !FileManager.default.fileExists(atPath: audioURL.path) {
  7514. do {
  7515. if var audioData = try FileEncryption.shared.readSecure(filename: audioChat) {
  7516. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: audioData)
  7517. if dataDecrypt != nil {
  7518. audioData = dataDecrypt!
  7519. }
  7520. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7521. let tempPath = cachesDirectory.appendingPathComponent(audioChat.contains(".aac") ? "\(audioChat.components(separatedBy: ".")[0]).m4a" : audioChat)
  7522. try audioData.write(to: tempPath)
  7523. url = tempPath
  7524. }
  7525. } catch {
  7526. }
  7527. }
  7528. if audioPlayers[indexPath] == nil {
  7529. do {
  7530. let audioPlayer = try AVAudioPlayer(contentsOf: url)
  7531. audioPlayers[indexPath] = audioPlayer
  7532. audioPlayer.delegate = self
  7533. progressSliderAudio.maximumValue = Float(audioPlayer.duration)
  7534. timeLabelAudio.text = formatTime(audioPlayer.duration)
  7535. } catch {
  7536. print("Error loading audio: \(error)")
  7537. }
  7538. }
  7539. let audioPlayer = audioPlayers[indexPath]
  7540. if playingIndexPath == indexPath, let player = audioPlayer, player.isPlaying {
  7541. playButtonAudio.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  7542. } else {
  7543. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  7544. }
  7545. // Play/Pause Button Action
  7546. playButtonAudio.addAction(UIAction { _ in
  7547. self.playPauseAudio(indexPath: indexPath, playButton: playButtonAudio, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  7548. }, for: .touchUpInside)
  7549. progressSliderAudio.addAction(UIAction { _ in
  7550. self.sliderChanged(indexPath: indexPath, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  7551. }, for: .valueChanged)
  7552. }
  7553. }
  7554. }
  7555. if (!thumbChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  7556. if let listImages = groupImages[messageIdChat] {
  7557. timeMessage.isHidden = true
  7558. statusMessage.isHidden = true
  7559. imageStared.isHidden = true
  7560. topMarginText.constant = topMarginText.constant + 205
  7561. var constTop = 5.0
  7562. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
  7563. topMarginText.constant = topMarginText.constant + 10
  7564. constTop = 35.0
  7565. }
  7566. let listImageThumb: [UIImageView] = [UIImageView(), UIImageView(), UIImageView(), UIImageView()]
  7567. for i in 0..<4 {
  7568. containerMessage.addSubview(listImageThumb[i])
  7569. listImageThumb[i].layer.cornerRadius = 5.0
  7570. listImageThumb[i].clipsToBounds = true
  7571. listImageThumb[i].contentMode = .scaleAspectFill
  7572. let widthHeightImage: CGFloat = 120
  7573. switch i {
  7574. case 0:
  7575. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: constTop, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
  7576. case 1:
  7577. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: constTop, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  7578. case 2:
  7579. listImageThumb[i].anchor(left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingLeft: 5, paddingBottom: 5, width: widthHeightImage, height: widthHeightImage)
  7580. default:
  7581. listImageThumb[i].anchor(left: listImageThumb[2].rightAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  7582. }
  7583. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7584. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7585. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7586. if let dirPath = paths.first {
  7587. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].thumbId)
  7588. if FileManager.default.fileExists(atPath: thumbURL.path) {
  7589. DispatchQueue.main.async {
  7590. let image : UIImage? = {
  7591. if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
  7592. return img
  7593. }
  7594. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  7595. Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
  7596. return img
  7597. }
  7598. return nil
  7599. }()
  7600. listImageThumb[i].image = image
  7601. }
  7602. } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
  7603. do {
  7604. if var data = try FileEncryption.shared.readSecure(filename: listImages[i].thumbId) {
  7605. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  7606. if dataDecrypt != nil {
  7607. data = dataDecrypt!
  7608. }
  7609. DispatchQueue.main.async {
  7610. let image : UIImage? = {
  7611. if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
  7612. return img
  7613. }
  7614. else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
  7615. Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
  7616. return img
  7617. }
  7618. return nil
  7619. }()
  7620. listImageThumb[i].image = image
  7621. }
  7622. }
  7623. } catch {
  7624. }
  7625. } else {
  7626. Download().startHTTP(forKey: listImages[i].thumbId) { (name, progress) in
  7627. guard progress == 100 else {
  7628. return
  7629. }
  7630. tableView.reloadRows(at: [indexPath], with: .none)
  7631. }
  7632. }
  7633. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
  7634. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  7635. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  7636. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  7637. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[i].frame.size.width, height: listImageThumb[i].frame.size.height)
  7638. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  7639. listImageThumb[i].addSubview(blurEffectView)
  7640. }
  7641. }
  7642. let containerTimeStatus = UIView()
  7643. listImageThumb[i].addSubview(containerTimeStatus)
  7644. containerTimeStatus.anchor(bottom: listImageThumb[i].bottomAnchor, right: listImageThumb[i].rightAnchor, height: 15)
  7645. let widthcontainerTimeStatus = containerTimeStatus.widthAnchor.constraint(equalToConstant: 50)
  7646. widthcontainerTimeStatus.isActive = true
  7647. containerTimeStatus.layer.cornerRadius = 5.0
  7648. containerTimeStatus.layer.masksToBounds = true
  7649. containerTimeStatus.backgroundColor = .black.withAlphaComponent(0.15)
  7650. let timeInImage = UILabel()
  7651. containerTimeStatus.addSubview(timeInImage)
  7652. let date = Date(milliseconds: Int64(listImages[i].time) ?? 100)
  7653. let formatter = DateFormatter()
  7654. formatter.dateFormat = "HH:mm"
  7655. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  7656. timeInImage.text = formatter.string(from: date as Date)
  7657. timeInImage.textColor = .white
  7658. timeInImage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  7659. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe && dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String != MessageScope.CALL && dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String != MessageScope.MISSED_CALL) {
  7660. let statusInImage = UIImageView()
  7661. containerTimeStatus.addSubview(statusInImage)
  7662. statusInImage.anchor(right: containerTimeStatus.rightAnchor, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  7663. if listImages[i].status == "0" {
  7664. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  7665. } else if listImages[i].status == "1" {
  7666. statusInImage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
  7667. } else if listImages[i].status == "2" {
  7668. statusInImage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  7669. } else if listImages[i].status == "3" {
  7670. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  7671. } else {
  7672. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  7673. }
  7674. timeInImage.anchor(right: statusInImage.leftAnchor, centerY: containerTimeStatus.centerYAnchor, height: 15)
  7675. } else {
  7676. timeInImage.anchor(right: containerTimeStatus.rightAnchor, paddingRight: 5, centerY: containerTimeStatus.centerYAnchor, height: 15)
  7677. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant - 10
  7678. }
  7679. if listImages[i].dataMessage["is_stared"] as? String == "1" {
  7680. let iconStar = UIImageView()
  7681. containerTimeStatus.addSubview(iconStar)
  7682. iconStar.anchor(right: timeInImage.leftAnchor, paddingRight: 2, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  7683. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant + 15
  7684. iconStar.image = UIImage(systemName: "star.fill")
  7685. iconStar.tintColor = .white
  7686. }
  7687. if !copySession && !forwardSession && !deleteSession {
  7688. let objectTap = ObjectGesture(target: self, action: #selector(imageGroupingTapped(_:)))
  7689. listImageThumb[i].isUserInteractionEnabled = true
  7690. listImageThumb[i].addGestureRecognizer(objectTap)
  7691. objectTap.indexImageTapped = i
  7692. objectTap.listImageFromGrouping = listImages
  7693. objectTap.isInitiator = dataMessages[indexPath.row]["f_pin"] as? String == idMe
  7694. }
  7695. }
  7696. if listImages.count > 4 {
  7697. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
  7698. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  7699. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[3].frame.size.width, height: listImageThumb[3].frame.size.height)
  7700. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  7701. listImageThumb[3].addSubview(blurEffectView)
  7702. let countRestImages = UILabel()
  7703. listImageThumb[3].addSubview(countRestImages)
  7704. countRestImages.anchor(centerX: listImageThumb[3].centerXAnchor, centerY: listImageThumb[3].centerYAnchor)
  7705. countRestImages.font = UIFont.systemFont(ofSize: 30, weight: .medium)
  7706. countRestImages.text = "+\(listImages.count - 3)"
  7707. countRestImages.textColor = .white
  7708. }
  7709. } else {
  7710. let getHeightImage: CGFloat = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.height
  7711. let getWidthImage: CGFloat = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.width
  7712. topMarginText.constant = topMarginText.constant + (getHeightImage < 40 ? 45 : getHeightImage + 5)
  7713. containerMessage.addSubview(imageThumb)
  7714. imageThumb.frame = CGRect(x: 0, y: 0, width: getWidthImage, height: getHeightImage)
  7715. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  7716. let data = queryMessageReply(message_id: reffChat)
  7717. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  7718. imageThumb.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  7719. }
  7720. imageThumb.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  7721. imageThumb.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  7722. imageThumb.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  7723. imageThumb.widthAnchor.constraint(equalToConstant: getWidthImage).isActive = true
  7724. imageThumb.layer.cornerRadius = 5.0
  7725. imageThumb.clipsToBounds = true
  7726. imageThumb.contentMode = .scaleAspectFill
  7727. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7728. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7729. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7730. if let dirPath = paths.first {
  7731. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
  7732. if FileManager.default.fileExists(atPath: thumbURL.path) {
  7733. DispatchQueue.main.async {
  7734. let image : UIImage? = {
  7735. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  7736. return img
  7737. }
  7738. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  7739. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  7740. return img
  7741. }
  7742. return nil
  7743. }()
  7744. imageThumb.image = image
  7745. }
  7746. } else if FileEncryption.shared.isSecureExists(filename: thumbChat) {
  7747. do {
  7748. if var data = try FileEncryption.shared.readSecure(filename: thumbChat) {
  7749. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  7750. if dataDecrypt != nil {
  7751. data = dataDecrypt!
  7752. }
  7753. DispatchQueue.main.async {
  7754. let image : UIImage? = {
  7755. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  7756. return img
  7757. }
  7758. else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
  7759. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  7760. return img
  7761. }
  7762. return nil
  7763. }()
  7764. imageThumb.image = image
  7765. }
  7766. }
  7767. } catch {
  7768. }
  7769. } else {
  7770. Download().startHTTP(forKey: thumbChat) { (name, progress) in
  7771. guard progress == 100 else {
  7772. return
  7773. }
  7774. tableView.reloadRows(at: [indexPath], with: .none)
  7775. }
  7776. }
  7777. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
  7778. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  7779. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  7780. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  7781. blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
  7782. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  7783. imageThumb.addSubview(blurEffectView)
  7784. if !imageChat.isEmpty {
  7785. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
  7786. imageThumb.addSubview(imageDownload)
  7787. imageDownload.tintColor = .black.withAlphaComponent(0.3)
  7788. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  7789. imageDownload.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  7790. imageDownload.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  7791. }
  7792. }
  7793. }
  7794. if (videoChat != "" && gifChat.isEmpty) {
  7795. 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))
  7796. imagePlay.circle()
  7797. imageThumb.addSubview(imagePlay)
  7798. imagePlay.backgroundColor = .black.withAlphaComponent(0.3)
  7799. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  7800. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  7801. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  7802. } else if !gifChat.isEmpty {
  7803. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7804. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7805. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7806. if let dirPath = paths.first {
  7807. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifChat)
  7808. if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifChat) {
  7809. Download().startHTTP(forKey: gifChat) { (name, progress) in
  7810. guard progress == 100 else {
  7811. return
  7812. }
  7813. tableView.reloadRows(at: [indexPath], with: .none)
  7814. }
  7815. } else {
  7816. imageThumb.addSubview(imageGif)
  7817. imageGif.translatesAutoresizingMaskIntoConstraints = false
  7818. imageGif.anchor(top: imageThumb.topAnchor, left: imageThumb.leftAnchor, bottom: imageThumb.bottomAnchor, right: imageThumb.rightAnchor)
  7819. if FileManager.default.fileExists(atPath: gifURL.path) {
  7820. imageGif.image = SDAnimatedImage(contentsOfFile: gifURL.path)
  7821. // imageGif.shouldCustomLoopCount = true
  7822. // imageGif.animationRepeatCount = 4
  7823. } else if FileEncryption.shared.isSecureExists(filename: gifChat){
  7824. do {
  7825. if var data = try FileEncryption.shared.readSecure(filename: gifChat) {
  7826. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  7827. if dataDecrypt != nil {
  7828. data = dataDecrypt!
  7829. }
  7830. if let imageData = SDAnimatedImage(data: data) {
  7831. imageGif.image = imageData
  7832. // imageGif.shouldCustomLoopCount = true
  7833. // imageGif.animationRepeatCount = 4
  7834. }
  7835. }
  7836. }
  7837. catch {
  7838. print("Error reading secure file")
  7839. }
  7840. }
  7841. }
  7842. }
  7843. }
  7844. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0 && dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7845. let container = UIView()
  7846. imageThumb.addSubview(container)
  7847. container.translatesAutoresizingMaskIntoConstraints = false
  7848. container.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  7849. container.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  7850. container.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7851. container.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7852. container.backgroundColor = .white.withAlphaComponent(0.1)
  7853. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 10, y: 20), radius: 15, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  7854. let trackShape = CAShapeLayer()
  7855. trackShape.path = circlePath.cgPath
  7856. trackShape.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
  7857. trackShape.lineWidth = 3
  7858. trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
  7859. container.backgroundColor = .clear
  7860. container.layer.addSublayer(trackShape)
  7861. let shapeLoading = CAShapeLayer()
  7862. shapeLoading.path = circlePath.cgPath
  7863. shapeLoading.fillColor = UIColor.clear.cgColor
  7864. shapeLoading.lineWidth = 3
  7865. shapeLoading.strokeEnd = 0
  7866. shapeLoading.strokeColor = UIColor.mentionColor.cgColor
  7867. container.layer.addSublayer(shapeLoading)
  7868. let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  7869. imageupload.tintColor = .white
  7870. container.addSubview(imageupload)
  7871. imageupload.translatesAutoresizingMaskIntoConstraints = false
  7872. imageupload.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  7873. imageupload.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  7874. imageupload.widthAnchor.constraint(equalToConstant: 20).isActive = true
  7875. imageupload.heightAnchor.constraint(equalToConstant: 20).isActive = true
  7876. }
  7877. if !copySession && !forwardSession && !deleteSession {
  7878. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  7879. let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
  7880. imageThumb.isUserInteractionEnabled = true
  7881. imageThumb.addGestureRecognizer(objectTap)
  7882. objectTap.image_id = imageChat
  7883. objectTap.video_id = videoChat
  7884. objectTap.gif_id = gifChat
  7885. objectTap.imageView = imageThumb
  7886. objectTap.specFile = sfs
  7887. objectTap.indexPath = indexPath
  7888. }
  7889. }
  7890. }
  7891. if (!fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  7892. topMarginText.constant = topMarginText.constant + 55
  7893. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7894. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7895. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7896. let arrExtFile = (originalMessageText.components(separatedBy: "|")[0]).split(separator: ".")
  7897. let finalExtFile = arrExtFile[arrExtFile.count - 1]
  7898. if let dirPath = paths.first {
  7899. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
  7900. if FileManager.default.fileExists(atPath: fileURL.path) {
  7901. if let dataFile = try? Data(contentsOf: fileURL), textChat.isEmpty {
  7902. var sizeOfFile = Int(dataFile.count / 1000000)
  7903. if (sizeOfFile < 1) {
  7904. sizeOfFile = Int(dataFile.count / 1000)
  7905. if (finalExtFile.count > 4) {
  7906. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  7907. }else {
  7908. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  7909. }
  7910. } else {
  7911. if (finalExtFile.count > 4) {
  7912. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  7913. }else {
  7914. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  7915. }
  7916. }
  7917. }
  7918. }
  7919. else if FileEncryption.shared.isSecureExists(filename: fileChat) {
  7920. if var dataFile = try? FileEncryption.shared.readSecure(filename: fileChat), textChat.isEmpty {
  7921. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: dataFile)
  7922. if dataDecrypt != nil {
  7923. dataFile = dataDecrypt!
  7924. }
  7925. var sizeOfFile = Int(dataFile.count / 1000000)
  7926. if (sizeOfFile < 1) {
  7927. sizeOfFile = Int(dataFile.count / 1000)
  7928. if (finalExtFile.count > 4) {
  7929. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  7930. }else {
  7931. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  7932. }
  7933. } else {
  7934. if (finalExtFile.count > 4) {
  7935. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  7936. }else {
  7937. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  7938. }
  7939. }
  7940. }
  7941. }
  7942. }
  7943. containerMessage.addSubview(containerViewFile)
  7944. containerViewFile.translatesAutoresizingMaskIntoConstraints = false
  7945. let data = queryMessageReply(message_id: reffChat)
  7946. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  7947. containerViewFile.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  7948. } else {
  7949. containerViewFile.heightAnchor.constraint(equalToConstant: 50).isActive = true
  7950. }
  7951. containerViewFile.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  7952. containerViewFile.bottomAnchor.constraint(equalTo:messageText.topAnchor, constant: -5).isActive = true
  7953. containerViewFile.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  7954. // containerViewFile.heightAnchor.constraint(equalToConstant: 50).isActive = true
  7955. containerViewFile.backgroundColor = .black.withAlphaComponent(0.2)
  7956. containerViewFile.layer.cornerRadius = 5.0
  7957. containerViewFile.clipsToBounds = true
  7958. let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .bold, scale: .default)))
  7959. containerViewFile.addSubview(imageFile)
  7960. let nameFile = UILabel()
  7961. containerViewFile.addSubview(nameFile)
  7962. imageFile.translatesAutoresizingMaskIntoConstraints = false
  7963. imageFile.leadingAnchor.constraint(equalTo: containerViewFile.leadingAnchor, constant: 5).isActive = true
  7964. imageFile.trailingAnchor.constraint(equalTo: nameFile.leadingAnchor, constant: -5).isActive = true
  7965. imageFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  7966. imageFile.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7967. imageFile.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7968. imageFile.tintColor = .docColor
  7969. nameFile.translatesAutoresizingMaskIntoConstraints = false
  7970. nameFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  7971. nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
  7972. nameFile.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  7973. nameFile.textColor = .white
  7974. nameFile.text = originalMessageText.components(separatedBy: "|")[0]
  7975. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
  7976. let containerLoading = UIView()
  7977. containerViewFile.addSubview(containerLoading)
  7978. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  7979. containerLoading.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  7980. containerLoading.leadingAnchor.constraint(equalTo: nameFile.trailingAnchor, constant: 5).isActive = true
  7981. containerLoading.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  7982. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7983. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7984. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  7985. let trackShape = CAShapeLayer()
  7986. trackShape.path = circlePath.cgPath
  7987. trackShape.fillColor = UIColor.clear.cgColor
  7988. trackShape.lineWidth = 5
  7989. trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
  7990. containerLoading.layer.addSublayer(trackShape)
  7991. let shapeLoading = CAShapeLayer()
  7992. shapeLoading.path = circlePath.cgPath
  7993. shapeLoading.fillColor = UIColor.clear.cgColor
  7994. shapeLoading.lineWidth = 3
  7995. shapeLoading.strokeEnd = 0
  7996. shapeLoading.strokeColor = UIColor.mentionColor.cgColor
  7997. containerLoading.layer.addSublayer(shapeLoading)
  7998. var imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  7999. if dataMessages[indexPath.row]["f_pin"] as? String != idMe {
  8000. imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  8001. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  8002. }
  8003. imageupload.tintColor = .white
  8004. containerLoading.addSubview(imageupload)
  8005. imageupload.translatesAutoresizingMaskIntoConstraints = false
  8006. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  8007. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  8008. } else {
  8009. nameFile.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  8010. }
  8011. if !copySession && !forwardSession && !deleteSession {
  8012. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  8013. let sfs = (dataMessages[indexPath.row][TypeDataMessage.spec_file] as? String) ?? ""
  8014. containerViewFile.addGestureRecognizer(objectTap)
  8015. objectTap.containerFile = containerViewFile
  8016. objectTap.labelFile = nameFile
  8017. objectTap.file_id = fileChat
  8018. objectTap.specFile = sfs
  8019. objectTap.indexPath = indexPath
  8020. }
  8021. }
  8022. let containerLinkMessage = UIView()
  8023. var isLoadingShowLink = false
  8024. if thumbChat.isEmpty && fileChat.isEmpty && !textChat.isEmpty {
  8025. var text = ""
  8026. let listTextSplitBreak = textChat.components(separatedBy: "\n")
  8027. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  8028. if indexFirstLinkSplitBreak != nil {
  8029. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  8030. 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) })
  8031. if indexFirstLinkSplitSpace != nil {
  8032. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  8033. }
  8034. }
  8035. if !text.isEmpty {
  8036. isLoadingShowLink = true
  8037. var dataURL = ""
  8038. func showLink() {
  8039. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  8040. let title = data["title"] as? String ?? ""
  8041. let description = data["description"] as? String ?? ""
  8042. let imageUrl = data["imageUrl"] as? String
  8043. let link = data["link"] as? String ?? ""
  8044. topMarginText.constant = topMarginText.constant + 85
  8045. containerMessage.addSubview(containerLinkMessage)
  8046. containerLinkMessage.translatesAutoresizingMaskIntoConstraints = false
  8047. containerLinkMessage.leadingAnchor.constraint(equalTo:containerMessage.leadingAnchor, constant: 15).isActive = true
  8048. containerLinkMessage.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  8049. containerLinkMessage.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  8050. containerLinkMessage.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  8051. containerLinkMessage.backgroundColor = .gray.withAlphaComponent(0.2)
  8052. let imagePreview = UIImageView()
  8053. if imageUrl != nil {
  8054. containerLinkMessage.addSubview(imagePreview)
  8055. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  8056. imagePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor).isActive = true
  8057. imagePreview.bottomAnchor.constraint(equalTo: containerLinkMessage.bottomAnchor).isActive = true
  8058. imagePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor).isActive = true
  8059. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  8060. imagePreview.loadImageAsync(with: imageUrl)
  8061. imagePreview.contentMode = .scaleAspectFill
  8062. imagePreview.clipsToBounds = true
  8063. }
  8064. let titlePreview = UILabel()
  8065. containerLinkMessage.addSubview(titlePreview)
  8066. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  8067. if imageUrl != nil {
  8068. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  8069. } else {
  8070. titlePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  8071. }
  8072. titlePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: 10.0).isActive = true
  8073. titlePreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  8074. titlePreview.text = title
  8075. titlePreview.font = UIFont.systemFont(ofSize: 12.0 + offset(), weight: .bold)
  8076. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  8077. let descPreview = UILabel()
  8078. containerLinkMessage.addSubview(descPreview)
  8079. descPreview.translatesAutoresizingMaskIntoConstraints = false
  8080. if imageUrl != nil {
  8081. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  8082. } else {
  8083. descPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  8084. }
  8085. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  8086. descPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  8087. descPreview.text = description
  8088. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  8089. descPreview.textColor = .gray
  8090. descPreview.numberOfLines = 1
  8091. let linkPreview = UILabel()
  8092. containerLinkMessage.addSubview(linkPreview)
  8093. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  8094. if imageUrl != nil {
  8095. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  8096. } else {
  8097. linkPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  8098. }
  8099. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  8100. linkPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  8101. linkPreview.text = link
  8102. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  8103. linkPreview.textColor = .gray
  8104. linkPreview.numberOfLines = 1
  8105. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
  8106. showForwardedSign()
  8107. }
  8108. if !copySession && !forwardSession && !deleteSession {
  8109. let objectTap = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
  8110. objectTap.message_id = text
  8111. containerLinkMessage.addGestureRecognizer(objectTap)
  8112. }
  8113. }
  8114. }
  8115. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  8116. do {
  8117. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
  8118. if let data = cursor.string(forColumnIndex: 0) {
  8119. dataURL = data
  8120. }
  8121. cursor.close()
  8122. }
  8123. } catch {
  8124. rollback.pointee = true
  8125. print("Access database error: \(error.localizedDescription)")
  8126. }
  8127. })
  8128. if !dataURL.isEmpty {
  8129. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  8130. let imageUrl = data["imageUrl"] as? String
  8131. let link = data["link"] as? String ?? ""
  8132. if imageUrl == nil || (link.contains("youtube.com") && link.contains("watch?v=") && !imageUrl!.contains("img.youtube.com/vi/")) {
  8133. dataURL = ""
  8134. }
  8135. }
  8136. }
  8137. if dataURL.isEmpty {
  8138. let urlConfig = URLSessionConfiguration.default
  8139. let sessionDelegate = SelfSignedURLSessionDelegate()
  8140. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  8141. let slp = SwiftLinkPreview(session: session,
  8142. workQueue: SwiftLinkPreview.defaultWorkQueue,
  8143. responseQueue: DispatchQueue.main,
  8144. cache: DisabledCache.instance)
  8145. let preview = slp.preview(text,
  8146. onSuccess: { result in
  8147. let title = result.title?.trimmingCharacters(in: .whitespacesAndNewlines)
  8148. .nilIfEmpty ?? URL(string: text)?.host ?? "Untitled"
  8149. let description: String
  8150. if text.contains("google.com") {
  8151. description = "" // special rule for google
  8152. } else {
  8153. description = result.description?.trimmingCharacters(in: .whitespacesAndNewlines)
  8154. .nilIfEmpty ?? ""
  8155. }
  8156. let imageUrl = self.youtubeThumbnail(from: text)
  8157. ?? result.image
  8158. ?? result.icon
  8159. ?? ""
  8160. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  8161. do {
  8162. var dataJson: [String: Any] = [:]
  8163. dataJson["title"] = title
  8164. dataJson["description"] = description
  8165. dataJson["imageUrl"] = imageUrl
  8166. dataJson["link"] = text
  8167. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  8168. return
  8169. }
  8170. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  8171. "id" : "\(Date().currentTimeMillis().toHex())",
  8172. "link" : text,
  8173. "data_link" : json,
  8174. "retry": 0
  8175. ], replace: true)
  8176. dataURL = json
  8177. showLink()
  8178. DispatchQueue.main.async {
  8179. tableView.reloadRows(at: [indexPath], with: .none)
  8180. }
  8181. } catch {
  8182. rollback.pointee = true
  8183. print("Access database error: \(error.localizedDescription)")
  8184. }
  8185. })
  8186. }, onError: { error in
  8187. })
  8188. } else {
  8189. showLink()
  8190. }
  8191. }
  8192. }
  8193. if (!reffChat.isEmpty && dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" != MessageScope.FORM) {
  8194. let chatGroup = Chat.getMessageFromId(message_id: reffChat)
  8195. if chatGroup.count != 0 {
  8196. let containerReply = UIView()
  8197. containerMessage.addSubview(containerReply)
  8198. containerReply.translatesAutoresizingMaskIntoConstraints = false
  8199. containerReply.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  8200. containerReply.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  8201. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8202. containerReply.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  8203. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8204. containerReply.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  8205. } else if containerMessage.subviews.contains(containerLinkMessage) {
  8206. containerReply.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  8207. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8208. containerReply.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  8209. } else {
  8210. containerReply.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  8211. }
  8212. containerReply.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  8213. let minHeightConstraint = containerReply.heightAnchor.constraint(greaterThanOrEqualToConstant: 50 + (self.offset()*3))
  8214. minHeightConstraint.priority = .defaultHigh
  8215. minHeightConstraint.isActive = true
  8216. containerReply.backgroundColor = .black.withAlphaComponent(0.2)
  8217. containerReply.layer.cornerRadius = 5
  8218. containerReply.clipsToBounds = true
  8219. if (thumbChat != "" || fileChat != "") && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8220. topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: topMarginText.constant + 50 + (self.offset()*3))
  8221. }
  8222. let leftReply = UIView()
  8223. containerReply.addSubview(leftReply)
  8224. leftReply.translatesAutoresizingMaskIntoConstraints = false
  8225. leftReply.leadingAnchor.constraint(equalTo: containerReply.leadingAnchor).isActive = true
  8226. leftReply.topAnchor.constraint(equalTo: containerReply.topAnchor).isActive = true
  8227. leftReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor).isActive = true
  8228. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  8229. leftReply.layer.cornerRadius = 5
  8230. leftReply.clipsToBounds = true
  8231. leftReply.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
  8232. let titleReply = UILabel()
  8233. containerReply.addSubview(titleReply)
  8234. titleReply.translatesAutoresizingMaskIntoConstraints = false
  8235. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  8236. titleReply.topAnchor.constraint(equalTo: containerReply.topAnchor, constant: 10).isActive = true
  8237. titleReply.trailingAnchor.constraint(lessThanOrEqualTo: containerReply.trailingAnchor, constant: -20).isActive = true
  8238. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  8239. let f_pin = chatGroup[0].fpin
  8240. if f_pin == idMe {
  8241. if chatGroup[0].messageScope != MessageScope.GROUP {
  8242. titleReply.text = "You".localized()
  8243. } else {
  8244. titleReply.text = "You".localized() + " ● " + "\(chatGroup[0].groupName)(\(chatGroup[0].name))"
  8245. }
  8246. } else {
  8247. if isContactCenter {
  8248. let user: [User] = users.filter({$0.pin == chatGroup[0].fpin})
  8249. titleReply.text = user.first!.fullName
  8250. } else {
  8251. if chatGroup[0].messageScope != MessageScope.GROUP {
  8252. titleReply.text = self.dataPerson["name"]!!
  8253. } else {
  8254. if let dataPerson = User.getData(pin: f_pin) {
  8255. let namePerson = dataPerson.fullName
  8256. titleReply.text = namePerson + " ● " + "\(chatGroup[0].groupName)(\(chatGroup[0].name))"
  8257. }
  8258. }
  8259. }
  8260. }
  8261. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  8262. titleReply.textColor = .white
  8263. leftReply.backgroundColor = .white
  8264. } else {
  8265. titleReply.textColor = .mainColor
  8266. leftReply.backgroundColor = .mainColor
  8267. }
  8268. let contentReply = UILabel()
  8269. contentReply.numberOfLines = 2
  8270. containerReply.addSubview(contentReply)
  8271. contentReply.translatesAutoresizingMaskIntoConstraints = false
  8272. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  8273. contentReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor, constant: -10).isActive = true
  8274. let topConstraintContent = contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor)
  8275. topConstraintContent.priority = .defaultHigh
  8276. topConstraintContent.isActive = true
  8277. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  8278. let message_text = chatGroup[0].messageText
  8279. let attachment_flag = chatGroup[0].attachmentFlag
  8280. let thumb_chat = chatGroup[0].thumb
  8281. let image_chat = chatGroup[0].image
  8282. let video_chat = chatGroup[0].video
  8283. let file_chat = chatGroup[0].file
  8284. if (attachment_flag == "0" && thumb_chat == "") {
  8285. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  8286. contentReply.attributedText = message_text.richText()
  8287. } else if (attachment_flag == "1" || image_chat != "") {
  8288. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  8289. contentReply.text = "📷 Photo".localized()
  8290. } else {
  8291. contentReply.attributedText = message_text.richText()
  8292. }
  8293. } else if (attachment_flag == "2" || video_chat != "") {
  8294. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  8295. contentReply.text = "📹 Video".localized()
  8296. } else {
  8297. contentReply.attributedText = message_text.richText()
  8298. }
  8299. } else if (attachment_flag == "6" || file_chat != ""){
  8300. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  8301. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  8302. } else if (attachment_flag == "11") {
  8303. contentReply.text = "❤️ Sticker"
  8304. }
  8305. contentReply.textColor = .white.withAlphaComponent(0.8)
  8306. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  8307. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  8308. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  8309. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  8310. if let dirPath = paths.first {
  8311. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  8312. DispatchQueue.main.async {
  8313. let image : UIImage? = {
  8314. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  8315. return img
  8316. }
  8317. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  8318. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  8319. return img
  8320. }
  8321. return nil
  8322. }()
  8323. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  8324. let imageThumb = UIImageView(image: image)
  8325. containerReply.addSubview(imageThumb)
  8326. imageThumb.layer.cornerRadius = 2.0
  8327. imageThumb.clipsToBounds = true
  8328. imageThumb.contentMode = .scaleAspectFill
  8329. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  8330. imageThumb.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  8331. imageThumb.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  8332. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  8333. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  8334. if (attachment_flag == "2") {
  8335. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  8336. imageThumb.addSubview(imagePlay)
  8337. imagePlay.clipsToBounds = true
  8338. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  8339. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  8340. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  8341. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  8342. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  8343. imagePlay.tintColor = .white
  8344. }
  8345. titleReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  8346. contentReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  8347. }
  8348. }
  8349. }
  8350. if (attachment_flag == "11" && message_text.components(separatedBy: "/").count > 1) {
  8351. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  8352. containerReply.addSubview(imageSticker)
  8353. imageSticker.layer.cornerRadius = 2.0
  8354. imageSticker.clipsToBounds = true
  8355. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  8356. imageSticker.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  8357. imageSticker.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  8358. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  8359. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  8360. titleReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  8361. contentReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  8362. }
  8363. if !copySession && !forwardSession && !deleteSession {
  8364. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  8365. containerReply.addGestureRecognizer(objectTap)
  8366. objectTap.indexPath = indexPath
  8367. objectTap.message_id = chatGroup[0].messageId
  8368. }
  8369. }
  8370. }
  8371. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 && !isLoadingShowLink {
  8372. showForwardedSign()
  8373. }
  8374. func showForwardedSign() {
  8375. topMarginText.constant = topMarginText.constant + 20
  8376. let containerForwarded = UIView()
  8377. containerMessage.addSubview(containerForwarded)
  8378. containerForwarded.translatesAutoresizingMaskIntoConstraints = false
  8379. containerForwarded.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  8380. containerForwarded.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  8381. containerForwarded.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  8382. containerForwarded.heightAnchor.constraint(equalToConstant: 20).isActive = true
  8383. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8384. if groupImages[messageIdChat] == nil {
  8385. containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  8386. }
  8387. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8388. containerForwarded.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  8389. } else if containerMessage.subviews.contains(containerLinkMessage) {
  8390. containerForwarded.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  8391. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  8392. containerForwarded.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  8393. }
  8394. let imageForwarded = UIImageView()
  8395. containerForwarded.addSubview(imageForwarded)
  8396. imageForwarded.anchor(top: containerForwarded.topAnchor, left: containerForwarded.leftAnchor, width: 15, height: 15)
  8397. imageForwarded.image = UIImage(systemName: "arrowshape.turn.up.right.fill")
  8398. imageForwarded.tintColor = .gray
  8399. let titleForwarded = UILabel()
  8400. containerForwarded.addSubview(titleForwarded)
  8401. titleForwarded.anchor(top: containerForwarded.topAnchor, left: imageForwarded.rightAnchor, right: containerForwarded.rightAnchor, height: 15)
  8402. titleForwarded.font = .systemFont(ofSize: 15)
  8403. let textForwarded = "Forwarded".localized()
  8404. titleForwarded.attributedText = " $\(textForwarded)$".richText()
  8405. }
  8406. if messageText.isDescendant(of: containerMessage) {
  8407. var addTopMargin = true
  8408. if !reffChat.isEmpty && dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" != MessageScope.FORM {
  8409. let data = queryMessageReply(message_id: reffChat)
  8410. if data.count != 0 && (topMarginText.constant == 15.0 || topMarginText.constant == 100.0) {
  8411. addTopMargin = false
  8412. }
  8413. }
  8414. if addTopMargin{
  8415. topMarginText.isActive = true
  8416. }
  8417. }
  8418. // let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureCellAction))
  8419. // panGestureRecognizer.delegate = self
  8420. // cellMessage.addGestureRecognizer(panGestureRecognizer)
  8421. return cell
  8422. }
  8423. func playPauseAudio(indexPath: IndexPath, playButton: UIButton, progressSlider: UISlider, timeLabel: UILabel) {
  8424. guard let audioPlayer = audioPlayers[indexPath] else { return }
  8425. if audioPlayer.isPlaying {
  8426. // Pause Audio
  8427. audioPlayer.pause()
  8428. playButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
  8429. timers[indexPath]?.invalidate()
  8430. } else {
  8431. // Stop other players if one is already playing
  8432. if let currentPlayingIndexPath = playingIndexPath, let currentAudioPlayer = audioPlayers[currentPlayingIndexPath] {
  8433. if currentPlayingIndexPath != indexPath {
  8434. currentAudioPlayer.pause()
  8435. timers[currentPlayingIndexPath]?.invalidate()
  8436. timers[currentPlayingIndexPath] = nil
  8437. audioPlayers[currentPlayingIndexPath] = nil
  8438. tableChatView.reloadRows(at: [currentPlayingIndexPath], with: .none)
  8439. }
  8440. }
  8441. do {
  8442. try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
  8443. try AVAudioSession.sharedInstance().setActive(true)
  8444. } catch {
  8445. }
  8446. // Play new audio
  8447. audioPlayer.play()
  8448. playButton.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  8449. playingIndexPath = indexPath
  8450. // Start timer to update progress
  8451. timers[indexPath] = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
  8452. progressSlider.value = Float(audioPlayer.currentTime)
  8453. timeLabel.text = self.formatTime(audioPlayer.currentTime)
  8454. }
  8455. }
  8456. }
  8457. func sliderChanged(indexPath: IndexPath, progressSlider: UISlider, timeLabel: UILabel) {
  8458. guard let audioPlayer = audioPlayers[indexPath] else { return }
  8459. audioPlayer.currentTime = TimeInterval(progressSlider.value)
  8460. timeLabel.text = formatTime(audioPlayer.currentTime)
  8461. }
  8462. func formatTime(_ time: TimeInterval) -> String {
  8463. let roundedTime = time.rounded(.up)
  8464. let minutes = Int(roundedTime) / 60
  8465. let seconds = Int(roundedTime) % 60
  8466. return String(format: "%d:%02d", minutes, seconds)
  8467. }
  8468. public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  8469. if let finishedIndexPath = audioPlayers.first(where: { $0.value == player })?.key {
  8470. DispatchQueue.main.async {
  8471. self.timers[finishedIndexPath]?.invalidate()
  8472. self.timers[finishedIndexPath] = nil
  8473. self.playingIndexPath = nil
  8474. self.audioPlayers[finishedIndexPath] = nil
  8475. self.tableChatView.reloadRows(at: [finishedIndexPath], with: .none)
  8476. }
  8477. }
  8478. }
  8479. @objc func imageGroupingTapped(_ sender: ObjectGesture) {
  8480. let listGroupingImages = ListGroupImages()
  8481. listGroupingImages.imageTapped = sender.indexImageTapped
  8482. listGroupingImages.listGroupingImages = sender.listImageFromGrouping
  8483. listGroupingImages.titleName = titleText
  8484. listGroupingImages.isInitiator = sender.isInitiator
  8485. listGroupingImages.updateEditor = { [self] updatedData, replyData, isUpdateDelete in
  8486. if replyData.count == 0 {
  8487. if updatedData.count != 0 && !isUpdateDelete {
  8488. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  8489. } else if updatedData.count > 0 {
  8490. let deletedForEveryoneData = updatedData.filter({ $0.dataMessage["lock"] as? String == "1" })
  8491. if deletedForEveryoneData.count != 0 {
  8492. if groupImages[sender.listImageFromGrouping[0].messageId] != nil {
  8493. var dataWillEmpty = updatedData
  8494. while dataWillEmpty.count > 0 {
  8495. if let lastIdx = dataWillEmpty.lastIndex(where: { $0.dataMessage["lock"] as? String == "1" }) {
  8496. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  8497. if dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId {
  8498. self.dataMessages.remove(at: idx)
  8499. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx)
  8500. } else {
  8501. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx + 1)
  8502. }
  8503. let subData = Array(updatedData[lastIdx+1..<dataWillEmpty.count])
  8504. if subData.count >= 4 {
  8505. groupImages[subData[0].messageId] = subData
  8506. self.dataMessages.insert(subData[0].dataMessage, at: lastIdx + 1)
  8507. } else {
  8508. if subData.count > 0 {
  8509. self.dataMessages.insert(contentsOf: subData.map({ $0.dataMessage }), at: idx + (dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId ? 1 : 2))
  8510. }
  8511. }
  8512. }
  8513. dataWillEmpty.removeSubrange(lastIdx..<dataWillEmpty.count)
  8514. } else if dataWillEmpty.count >= 4 {
  8515. groupImages[dataWillEmpty[0].messageId] = dataWillEmpty
  8516. dataWillEmpty.removeAll()
  8517. } else {
  8518. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  8519. self.dataMessages.remove(at: idx)
  8520. self.dataMessages.insert(contentsOf: dataWillEmpty.map({ $0.dataMessage }), at: idx)
  8521. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  8522. }
  8523. dataWillEmpty.removeAll()
  8524. }
  8525. }
  8526. } else {
  8527. }
  8528. } else {
  8529. if updatedData.count >= 4 {
  8530. if updatedData[0].messageId == sender.listImageFromGrouping[0].messageId {
  8531. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  8532. } else {
  8533. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  8534. self.dataMessages.remove(at: idx)
  8535. self.dataMessages.insert(updatedData[0].dataMessage, at: idx)
  8536. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  8537. groupImages[updatedData[0].messageId] = updatedData
  8538. }
  8539. }
  8540. } else {
  8541. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  8542. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  8543. self.dataMessages.remove(at: idx)
  8544. let dataMessageInGrouping = updatedData.map({ $0.dataMessage })
  8545. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx)
  8546. }
  8547. }
  8548. }
  8549. } else {
  8550. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  8551. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  8552. self.dataMessages.remove(at: idx)
  8553. }
  8554. }
  8555. DispatchQueue.main.async { [self] in
  8556. tableChatView.reloadData()
  8557. }
  8558. } else if replyData.count != 0 {
  8559. handleReply(indexPath: IndexPath(row: 0, section: 0), dataMessagesImage: replyData)
  8560. }
  8561. }
  8562. self.navigationController?.pushViewController(listGroupingImages, animated: true)
  8563. }
  8564. @objc func tapAck(_ sender: ObjectGesture) {
  8565. if blocking == "1" {
  8566. self.view.makeToast("You blocked this user".localized(), duration: 3)
  8567. return
  8568. }
  8569. if blocking == "-1" {
  8570. self.view.makeToast("You have been blocked by this user".localized(), duration: 3)
  8571. return
  8572. }
  8573. let indexPath = sender.indexPath
  8574. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  8575. if dataMessages[indexPath.row]["status"] as? String ?? "" == "8" {
  8576. return
  8577. }
  8578. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  8579. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  8580. imageView.tintColor = .white
  8581. 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)
  8582. banner.show()
  8583. return
  8584. }
  8585. DispatchQueue.global().async {
  8586. let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as? String ?? "", message_id: dataMessages[indexPath.row]["message_id"] as? String ?? "", l_pin: dataMessages[indexPath.row]["l_pin"] as? String ?? "", server_date: "\(Date().currentTimeMillis())", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as? String ?? "", longitude: self.longitude, latitude: self.latitude, description: ""))
  8587. if result != nil {
  8588. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  8589. do {
  8590. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  8591. "status" : "8"
  8592. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  8593. } catch {
  8594. rollback.pointee = true
  8595. print("Access database error: \(error.localizedDescription)")
  8596. }
  8597. })
  8598. DispatchQueue.main.async {
  8599. if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
  8600. self.dataMessages[index]["status"] = "8"
  8601. let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"] as? String ?? "")
  8602. 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 ?? ""})
  8603. if row != nil && section != nil {
  8604. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  8605. }
  8606. self.view.makeToast("Confirmation Success.".localized(), duration: 3)
  8607. }
  8608. }
  8609. }
  8610. }
  8611. }
  8612. // public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  8613. // let velocity : CGPoint = gestureRecognizer.location(in: tableChatView)
  8614. // if velocity.x < 0 {
  8615. // return false
  8616. // }
  8617. // return abs(Float(velocity.x)) > abs(Float(velocity.y))
  8618. // }
  8619. //
  8620. // @objc func panGestureCellAction(recognizer: UIPanGestureRecognizer) {
  8621. // let translation = recognizer.translation(in: tableChatView)
  8622. // let x = recognizer.view?.frame.origin.x ?? 0
  8623. // if x >= -(recognizer.view?.frame.size.width ?? 0) * 0.05 {
  8624. // recognizer.view?.center = CGPoint(
  8625. // x: (recognizer.view?.center.x ?? 0) + translation.x,
  8626. // y: (recognizer.view?.center.y ?? 0))
  8627. // recognizer.setTranslation(CGPoint(x: 0, y: 0), in: view)
  8628. // if (recognizer.view?.frame.origin.x ?? 0) > UIScreen.main.bounds.size.width * 0.9 {
  8629. // UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
  8630. // recognizer.view?.frame = CGRect(x: 0, y: recognizer.view?.frame.origin.y ?? 0, width: recognizer.view?.frame.size.width ?? 0, height: recognizer.view?.frame.size.height ?? 0)
  8631. // })
  8632. // }
  8633. // }
  8634. // if x <= -(recognizer.view?.frame.size.width ?? 0) * 0.05 {
  8635. // let idMe = User.getMyPin() as String?
  8636. // let indexPath = self.tableChatView.indexPath(for: recognizer.view! as! UITableViewCell)
  8637. // let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  8638. // if (dataMessages[indexPath!.row]["f_pin"] as? String == idMe) {
  8639. // let messageInfoVC = MessageInfo()
  8640. // messageInfoVC.data = dataMessages[indexPath!.row]
  8641. // self.navigationController?.pushViewController(messageInfoVC, animated: true)
  8642. // return
  8643. // }
  8644. // }
  8645. // if x >= ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  8646. // if !hapticSwipeLeft {
  8647. // UINotificationFeedbackGenerator().notificationOccurred(.success)
  8648. // }
  8649. // hapticSwipeLeft = true
  8650. // } else if x < ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  8651. // hapticSwipeLeft = false
  8652. // }
  8653. // if recognizer.state == .ended {
  8654. // UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
  8655. // recognizer.view?.frame = CGRect(x: 0, y: recognizer.view?.frame.origin.y ?? 0, width: recognizer.view?.frame.size.width ?? 0, height: recognizer.view?.frame.size.height ?? 0)
  8656. // } completion: { (finished) in
  8657. // if x > ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  8658. // self.hapticSwipeLeft = false
  8659. //
  8660. // }
  8661. // }
  8662. // }
  8663. // }
  8664. public func numberOfSections(in tableView: UITableView) -> Int {
  8665. if tableView == tableMention || tableView == tableMentionEdit || tableView == tableViewConfigFile {
  8666. return 1
  8667. }
  8668. return dataDates.count
  8669. }
  8670. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  8671. if tableView == tableMention || tableView == tableMentionEdit {
  8672. return listMentionWithText.count
  8673. }
  8674. if tableView == tableViewConfigFile {
  8675. return 2
  8676. }
  8677. let dateKey = dataDates[section]
  8678. return messagesByDate[dateKey]?.count ?? 0
  8679. }
  8680. @objc func contentMessageTapped(_ sender: ObjectGesture) {
  8681. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  8682. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  8683. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  8684. let indexPath = sender.indexPath
  8685. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  8686. func showMedia(data: Data? = nil, url: URL? = nil, type: Int = 0) {
  8687. let image = UIImage(data: data ?? Data())
  8688. let imageViewer = MediaViewerViewController()
  8689. if type == 0 {
  8690. imageViewer.media = .image(image ?? UIImage())
  8691. } else if type == 1 {
  8692. imageViewer.media = .video(url ?? URL(string: "")!)
  8693. } else if type == 2 {
  8694. imageViewer.media = .gif(UIImage.gifImageWithData(data ?? Data()) ?? UIImage())
  8695. }
  8696. let navigationController = UINavigationController(rootViewController: imageViewer)
  8697. navigationController.defaultStyle()
  8698. navigationController.view.backgroundColor = .clear
  8699. navigationController.modalPresentationCapturesStatusBarAppearance = true
  8700. navigationController.modalPresentationStyle = .overFullScreen
  8701. let backAction = UIAction { _ in
  8702. navigationController.dismiss(animated: true)
  8703. }
  8704. let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
  8705. imageViewer.navigationItem.leftBarButtonItem = backButton
  8706. if (Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share")) && dataMessages[indexPath.row]["credential"] as? String != "1" {
  8707. let shareAction = UIAction { _ in
  8708. var activityViewController = UIActivityViewController(activityItems: [""], applicationActivities: nil)
  8709. if type == 1 {
  8710. activityViewController = UIActivityViewController(activityItems: [url ?? URL(string: "")!], applicationActivities: nil)
  8711. } else {
  8712. let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("ImageSharedNexilis-\(Date().currentTimeMillis())" + ".jpeg")
  8713. try? data!.write(to: tempURL)
  8714. activityViewController = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
  8715. }
  8716. activityViewController.popoverPresentationController?.sourceView = imageViewer.view
  8717. imageViewer.present(activityViewController, animated: true, completion: nil)
  8718. }
  8719. let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
  8720. imageViewer.navigationItem.rightBarButtonItem = shareButton
  8721. }
  8722. var name = ""
  8723. if !isContactCenter {
  8724. name = dataPerson["name"] as? String ?? ""
  8725. } else {
  8726. if users.count == 1 {
  8727. name = users[0].fullName
  8728. } else {
  8729. var stringName = ""
  8730. for user in users {
  8731. if stringName.isEmpty {
  8732. stringName = user.fullName
  8733. } else {
  8734. stringName += ", \(user.fullName)"
  8735. }
  8736. }
  8737. name = stringName
  8738. }
  8739. }
  8740. imageViewer.title = name
  8741. let transitionDelegate = ZoomTransitioningDelegate()
  8742. transitionDelegate.originImageView = sender.imageView
  8743. navigationController.transitioningDelegate = transitionDelegate
  8744. self.transitioningDelegateRef = transitionDelegate
  8745. present(navigationController, animated: true) {
  8746. imageViewer.animateBackgroundIn()
  8747. }
  8748. }
  8749. if (sender.image_id != "") {
  8750. if let dirPath = paths.first {
  8751. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  8752. if FileManager.default.fileExists(atPath: imageURL.path) {
  8753. self.previewItem = imageURL as NSURL
  8754. do {
  8755. showMedia(data: try Data(contentsOf: imageURL))
  8756. } catch {
  8757. }
  8758. } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
  8759. do {
  8760. if var data = try FileEncryption.shared.readSecure(filename: sender.image_id) {
  8761. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  8762. if dataDecrypt != nil {
  8763. data = dataDecrypt!
  8764. }
  8765. showMedia(data: data)
  8766. }
  8767. }
  8768. catch {
  8769. print("Error reading secure file")
  8770. }
  8771. } else {
  8772. for view in sender.imageView.subviews {
  8773. if view is UIImageView {
  8774. view.removeFromSuperview()
  8775. }
  8776. }
  8777. let activityIndicator = UIActivityIndicatorView(style: .large)
  8778. activityIndicator.color = .mainColor
  8779. activityIndicator.hidesWhenStopped = true
  8780. activityIndicator.center = CGPoint(x:sender.imageView.frame.width/2,
  8781. y: sender.imageView.frame.height/2)
  8782. activityIndicator.startAnimating()
  8783. sender.imageView.addSubview(activityIndicator)
  8784. Download().startHTTP(forKey: sender.image_id) { (name, progress) in
  8785. guard progress == 100 else {
  8786. return
  8787. }
  8788. do {
  8789. DispatchQueue.main.async {
  8790. activityIndicator.stopAnimating()
  8791. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  8792. }
  8793. } catch {
  8794. }
  8795. }
  8796. }
  8797. }
  8798. } else if (sender.gif_id != "") {
  8799. if let dirPath = paths.first {
  8800. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.gif_id)
  8801. if FileManager.default.fileExists(atPath: gifURL.path) {
  8802. do {
  8803. let data = try Data(contentsOf: gifURL)
  8804. showMedia(data: data, type: 2)
  8805. } catch {
  8806. }
  8807. } else if FileEncryption.shared.isSecureExists(filename: sender.gif_id) {
  8808. do {
  8809. if var secureData = try FileEncryption.shared.readSecure(filename: sender.gif_id) {
  8810. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  8811. if dataDecrypt != nil {
  8812. secureData = dataDecrypt!
  8813. }
  8814. showMedia(data: secureData, type: 2)
  8815. }
  8816. } catch {
  8817. }
  8818. }
  8819. }
  8820. } else if (sender.video_id != "") {
  8821. if let dirPath = paths.first {
  8822. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  8823. if FileManager.default.fileExists(atPath: videoURL.path) {
  8824. showMedia(url: videoURL, type: 1)
  8825. } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
  8826. do {
  8827. if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
  8828. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  8829. if dataDecrypt != nil {
  8830. secureData = dataDecrypt!
  8831. }
  8832. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  8833. let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
  8834. try secureData.write(to: tempPath)
  8835. showMedia(url: tempPath, type: 1)
  8836. }
  8837. } catch {
  8838. }
  8839. } else {
  8840. if downloadList[sender.video_id] != nil && downloadList[sender.video_id] == sender.indexPath {
  8841. return
  8842. }
  8843. downloadList[sender.video_id] = sender.indexPath
  8844. for view in sender.imageView.subviews {
  8845. if view is UIImageView {
  8846. view.removeFromSuperview()
  8847. }
  8848. }
  8849. let container = UIView()
  8850. sender.imageView.addSubview(container)
  8851. container.translatesAutoresizingMaskIntoConstraints = false
  8852. container.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  8853. container.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  8854. container.widthAnchor.constraint(equalToConstant: 50).isActive = true
  8855. container.heightAnchor.constraint(equalToConstant: 50).isActive = true
  8856. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 20, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  8857. let trackShape = CAShapeLayer()
  8858. trackShape.path = circlePath.cgPath
  8859. trackShape.fillColor = UIColor.clear.cgColor
  8860. trackShape.lineWidth = 10
  8861. trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
  8862. container.backgroundColor = .clear
  8863. container.layer.addSublayer(trackShape)
  8864. let shapeLoading = CAShapeLayer()
  8865. shapeLoading.path = circlePath.cgPath
  8866. shapeLoading.fillColor = UIColor.clear.cgColor
  8867. shapeLoading.lineWidth = 10
  8868. shapeLoading.strokeEnd = 0
  8869. shapeLoading.strokeColor = UIColor.mentionColor.cgColor
  8870. container.layer.addSublayer(shapeLoading)
  8871. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  8872. imageDownload.tintColor = .white
  8873. container.addSubview(imageDownload)
  8874. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  8875. imageDownload.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  8876. imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  8877. imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
  8878. imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
  8879. Download().startHTTP(forKey: sender.video_id) { (name, progress) in
  8880. DispatchQueue.main.async {
  8881. guard progress == 100 else {
  8882. shapeLoading.strokeEnd = CGFloat(progress / 100)
  8883. return
  8884. }
  8885. let idx = self.dataMessages.firstIndex(where: { $0["video_id"] as? String ?? "" == sender.video_id})
  8886. if idx != nil {
  8887. self.dataMessages[idx!]["progress"] = progress
  8888. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  8889. }
  8890. }
  8891. }
  8892. }
  8893. }
  8894. } else if (sender.file_id != "") {
  8895. func showFile(urlFile: URL, isFile: Bool = true) {
  8896. let previewController = QLPreviewController()
  8897. previewController.dataSource = self
  8898. let vcHandleFile = UIViewController()
  8899. let nc = UINavigationController(rootViewController: vcHandleFile)
  8900. let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
  8901. let navBarAppearance = UINavigationBarAppearance()
  8902. nc.defaultStyle()
  8903. navBarAppearance.configureWithOpaqueBackground()
  8904. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : UIColor.mainColor
  8905. navBarAppearance.titleTextAttributes = attributes
  8906. nc.navigationBar.standardAppearance = navBarAppearance
  8907. nc.navigationBar.scrollEdgeAppearance = navBarAppearance
  8908. let backAction = UIAction { _ in
  8909. nc.dismiss(animated: true)
  8910. }
  8911. let backButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "chevron.backward"), primaryAction: backAction, menu: nil)
  8912. vcHandleFile.navigationItem.leftBarButtonItem = backButton
  8913. if (Nexilis.checkingAccess(key: "secure_folder_share") || sender.specFile.contains("download") || sender.specFile.contains("share")) && dataMessages[indexPath.row]["credential"] as? String != "1" {
  8914. let shareAction = UIAction { _ in
  8915. let fileManager = FileManager.default
  8916. let tempURL = fileManager.temporaryDirectory.appendingPathComponent(urlFile.lastPathComponent)
  8917. do {
  8918. if !fileManager.fileExists(atPath: tempURL.path) {
  8919. try fileManager.copyItem(at: urlFile, to: tempURL)
  8920. }
  8921. let activityViewController = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil)
  8922. activityViewController.popoverPresentationController?.sourceView = vcHandleFile.view
  8923. vcHandleFile.present(activityViewController, animated: true, completion: nil)
  8924. } catch {
  8925. }
  8926. }
  8927. let shareButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "square.and.arrow.up"), primaryAction: shareAction, menu: nil)
  8928. vcHandleFile.navigationItem.rightBarButtonItem = shareButton
  8929. }
  8930. if let viewVc = vcHandleFile.view {
  8931. if isFile {
  8932. vcHandleFile.title = sender.labelFile.text
  8933. }
  8934. vcHandleFile.addChild(previewController)
  8935. previewController.dataSource = self
  8936. previewController.view.frame = CGRect(x: 0, y: 0, width: viewVc.bounds.size.width, height: viewVc.bounds.size.height)
  8937. previewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  8938. viewVc.addSubview(previewController.view)
  8939. previewController.didMove(toParent: vcHandleFile)
  8940. self.present(nc, animated: true)
  8941. }
  8942. }
  8943. if let dirPath = paths.first {
  8944. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id)
  8945. if FileManager.default.fileExists(atPath: fileURL.path) {
  8946. self.previewItem = fileURL as NSURL
  8947. showFile(urlFile: fileURL)
  8948. } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
  8949. do {
  8950. if var docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
  8951. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: docData)
  8952. if dataDecrypt != nil {
  8953. docData = dataDecrypt!
  8954. }
  8955. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  8956. let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
  8957. try docData.write(to: tempPath)
  8958. self.previewItem = tempPath as NSURL
  8959. showFile(urlFile: tempPath)
  8960. }
  8961. }
  8962. catch {
  8963. }
  8964. } else {
  8965. if downloadList[sender.file_id] != nil && downloadList[sender.file_id] == sender.indexPath {
  8966. return
  8967. }
  8968. downloadList[sender.file_id] = sender.indexPath
  8969. for view in sender.containerFile.subviews {
  8970. if !(view is UIImageView) && !(view is UILabel) {
  8971. view.removeFromSuperview()
  8972. }
  8973. }
  8974. let containerLoading = UIView()
  8975. sender.containerFile.addSubview(containerLoading)
  8976. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  8977. containerLoading.centerYAnchor.constraint(equalTo: sender.containerFile.centerYAnchor).isActive = true
  8978. containerLoading.leadingAnchor.constraint(equalTo: sender.labelFile.trailingAnchor, constant: 5).isActive = true
  8979. containerLoading.trailingAnchor.constraint(equalTo: sender.containerFile.trailingAnchor, constant: -5).isActive = true
  8980. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  8981. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  8982. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  8983. let trackShape = CAShapeLayer()
  8984. trackShape.path = circlePath.cgPath
  8985. trackShape.fillColor = UIColor.clear.cgColor
  8986. trackShape.lineWidth = 5
  8987. trackShape.strokeColor = UIColor.mentionColor.withAlphaComponent(0.3).cgColor
  8988. containerLoading.layer.addSublayer(trackShape)
  8989. let shapeLoading = CAShapeLayer()
  8990. shapeLoading.path = circlePath.cgPath
  8991. shapeLoading.fillColor = UIColor.clear.cgColor
  8992. shapeLoading.lineWidth = 3
  8993. shapeLoading.strokeEnd = 0
  8994. shapeLoading.strokeColor = UIColor.mentionColor.cgColor
  8995. containerLoading.layer.addSublayer(shapeLoading)
  8996. let imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  8997. imageupload.tintColor = .white
  8998. containerLoading.addSubview(imageupload)
  8999. imageupload.translatesAutoresizingMaskIntoConstraints = false
  9000. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  9001. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  9002. Download().startHTTP(forKey: sender.file_id) { (name, progress) in
  9003. DispatchQueue.main.async {
  9004. guard progress == 100 else {
  9005. shapeLoading.strokeEnd = CGFloat(progress / 100)
  9006. return
  9007. }
  9008. let idx = self.dataMessages.firstIndex(where: { $0["file_id"] as? String ?? "" == sender.file_id})
  9009. if idx != nil {
  9010. self.dataMessages[idx!]["progress"] = progress
  9011. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  9012. }
  9013. }
  9014. }
  9015. }
  9016. }
  9017. } else {
  9018. let chatGroup = Chat.getMessageFromId(message_id: sender.message_id)
  9019. if chatGroup.count > 0 && chatGroup[0].messageScope == "4" {
  9020. let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
  9021. editorGroupVC.hidesBottomBarWhenPushed = true
  9022. editorGroupVC.unique_l_pin = chatGroup[0].pin == chatGroup[0].groupId ? chatGroup[0].groupId : chatGroup[0].pin
  9023. editorGroupVC.referenceMessageId = sender.message_id
  9024. editorGroupVC.referenceChatDate = chatDate(stringDate: chatGroup[0].serverDate)
  9025. navigationController?.show(editorGroupVC, sender: nil)
  9026. return
  9027. }
  9028. DispatchQueue.main.async {
  9029. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == sender.message_id})
  9030. if idx == nil {
  9031. return
  9032. }
  9033. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  9034. if section == nil {
  9035. return
  9036. }
  9037. 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})
  9038. if row == nil {
  9039. return
  9040. }
  9041. let indexPath = IndexPath(row: row!, section: section!)
  9042. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  9043. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  9044. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  9045. let containerMessage = cell.contentView.subviews[0]
  9046. let idMe = User.getMyPin() as String?
  9047. if (self.dataMessages[idx!]["f_pin"] as? String == idMe) {
  9048. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  9049. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  9050. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  9051. containerMessage.backgroundColor = .clear
  9052. } else {
  9053. containerMessage.backgroundColor = .blueBubbleColor
  9054. }
  9055. }
  9056. } else {
  9057. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  9058. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  9059. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  9060. containerMessage.backgroundColor = .clear
  9061. } else {
  9062. containerMessage.backgroundColor = .whiteBubbleColor
  9063. }
  9064. }
  9065. }
  9066. }
  9067. }
  9068. }
  9069. }
  9070. }
  9071. func highlightedText(for text: String, textView: UITextView) -> NSAttributedString {
  9072. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  9073. if let range = textView.attributedText.string.range(of: text) {
  9074. let nsRange = NSRange(range, in: textView.attributedText.string)
  9075. mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.lightGray.withAlphaComponent(0.5), range: NSRange(range, in: text))
  9076. }
  9077. return mutableAttributedString
  9078. }
  9079. func removeHighlightedText(for text: String, textView: UITextView) -> NSAttributedString {
  9080. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  9081. if let range = textView.attributedText.string.range(of: text) {
  9082. let nsRange = NSRange(range, in: textView.attributedText.string)
  9083. mutableAttributedString.removeAttribute(.backgroundColor, range: NSRange(range, in: text))
  9084. }
  9085. return mutableAttributedString
  9086. }
  9087. @objc func tapMessageText(_ sender: ObjectGesture) {
  9088. var stringURl = sender.message_id
  9089. if stringURl.lowercased().starts(with: "www.") {
  9090. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  9091. }
  9092. let app: UIApplication = UIApplication.shared
  9093. var appURL: URL? = nil
  9094. if let url = URL(string: stringURl) {
  9095. if url.host?.contains("instagram.com") == true {
  9096. // Convert to Instagram deep link
  9097. if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
  9098. let path = components.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
  9099. appURL = URL(string: "instagram://\(path)")
  9100. }
  9101. } else if url.host?.contains("x.com") == true || url.host?.contains("twitter.com") == true {
  9102. if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
  9103. let path = components.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
  9104. appURL = URL(string: "twitter://\(path)")
  9105. }
  9106. } else if url.host?.contains("youtube.com") == true || url.host?.contains("youtu.be") == true {
  9107. appURL = URL(string: "youtube://\(url.absoluteString)")
  9108. }
  9109. if let appURL = appURL, app.canOpenURL(appURL) {
  9110. app.open(appURL, options: [:]) { success in
  9111. if !success {
  9112. if Nexilis.checkingAccess(key: "secure_browser") {
  9113. APIS.openUrl(url: stringURl)
  9114. } else {
  9115. app.open(url)
  9116. }
  9117. }
  9118. }
  9119. } else {
  9120. if Nexilis.checkingAccess(key: "secure_browser") {
  9121. APIS.openUrl(url: stringURl)
  9122. } else {
  9123. app.open(url)
  9124. }
  9125. }
  9126. }
  9127. }
  9128. // public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  9129. // if copySession || forwardSession || deleteSession {
  9130. // return nil
  9131. // }
  9132. // let idMe = User.getMyPin() as String?
  9133. // if (dataMessages[indexPath.row]["f_pin"] as? String != idMe) {
  9134. // return nil
  9135. // }
  9136. // let messageInfoVC = MessageInfo()
  9137. // messageInfoVC.data = dataMessages[indexPath.row]
  9138. // self.navigationController?.show(messageInfoVC, sender: nil)
  9139. // return UISwipeActionsConfiguration()
  9140. // }
  9141. //
  9142. // public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  9143. // if copySession || forwardSession || deleteSession {
  9144. // return nil
  9145. // }
  9146. // let action = UIContextualAction(style: .normal, title: "Reply") { [weak self] (action, view, completionHandler) in
  9147. // let feedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
  9148. // feedbackGenerator.impactOccurred()
  9149. //
  9150. // self?.handleReply(indexPath: indexPath)
  9151. // completionHandler(true)
  9152. // }
  9153. // action.title = nil
  9154. // action.backgroundColor = .white
  9155. // action.image = UIImage(systemName: "arrowshape.turn.up.left.circle.fill")?.withTintColor(.gray, renderingMode: .alwaysOriginal)
  9156. // let config = UISwipeActionsConfiguration(actions: [action])
  9157. // config.performsFirstActionWithFullSwipe = false
  9158. // return config
  9159. // }
  9160. private func pinAllMessages(dataMessages: [[String: Any?]], isPinned: Int = -1) {
  9161. var dataMessages = dataMessages
  9162. dataMessages.sort {
  9163. let firstPinned = Int64($0[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  9164. let secondPinned = Int64($1[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  9165. return firstPinned < secondPinned
  9166. }
  9167. if dataMessages.count != 0 {
  9168. if !self.containerPin.isDescendant(of: self.view) && dataMessages.count != 0 {
  9169. self.tableChatView.contentInset.top = 50
  9170. self.view.addSubview(self.containerPin)
  9171. self.containerPin.isUserInteractionEnabled = true
  9172. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(viewPinTapped))
  9173. self.containerPin.addGestureRecognizer(tapGesture)
  9174. self.containerPin.anchor(top: self.view.safeAreaLayoutGuide.topAnchor, left: self.view.leftAnchor, right: self.view.rightAnchor, height: 50)
  9175. self.containerPin.backgroundColor = .mainColor
  9176. if dataMessages.count > 1 {
  9177. self.containerPin.addSubview(self.signSelectedPin)
  9178. self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
  9179. self.signSelectedPin.layer.cornerRadius = 1
  9180. self.signSelectedPin.clipsToBounds = true
  9181. self.signSelectedPin.alignment = .fill
  9182. self.signSelectedPin.axis = .vertical
  9183. self.signSelectedPin.distribution = .fill
  9184. self.signSelectedPin.spacing = dataMessages.count == 3 ? 1.5 : 2
  9185. let heightSign: CGFloat = CGFloat((30 / dataMessages.count) - 1)
  9186. let widthSign: CGFloat = 2
  9187. for i in 0..<dataMessages.count {
  9188. let viewSign = UIView()
  9189. viewSign.backgroundColor = (i == (dataMessages.count - 1)) ? .white : .gray
  9190. viewSign.anchor(width: widthSign, height: heightSign)
  9191. viewSign.layer.cornerRadius = 1
  9192. viewSign.clipsToBounds = true
  9193. self.signSelectedPin.addArrangedSubview(viewSign)
  9194. }
  9195. self.nextPinShowed = dataMessages.count - 1
  9196. }
  9197. let contIconPin = UIImageView()
  9198. self.containerPin.addSubview(contIconPin)
  9199. contIconPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 15, centerY: self.containerPin.centerYAnchor, width: 30, height: 30)
  9200. contIconPin.layer.cornerRadius = 8
  9201. contIconPin.clipsToBounds = true
  9202. contIconPin.backgroundColor = .gray
  9203. contIconPin.image = UIImage(systemName: "pin.fill")?.imageWithInsets(insets: UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5))?.withTintColor(.waGrayLight)
  9204. self.containerPin.addSubview(textPin)
  9205. self.textPin.anchor(left: contIconPin.rightAnchor, right: self.containerPin.rightAnchor, paddingLeft: 10, paddingRight: 10, centerY: self.containerPin.centerYAnchor)
  9206. let chat = Chat.getMessageFromId(message_id: dataMessages[dataMessages.count - 1][TypeDataMessage.message_id] as? String ?? "")
  9207. if chat.count > 0 {
  9208. let text = Utils.previewMessageText(chat: chat[0])
  9209. if let attributeText = text as? NSMutableAttributedString {
  9210. self.textPin.attributedText = attributeText
  9211. }
  9212. }
  9213. self.textPin.numberOfLines = 1
  9214. self.textPin.textColor = .white
  9215. } else {
  9216. self.signSelectedPin.subviews.forEach({ $0.removeFromSuperview() })
  9217. self.signSelectedPin.removeFromSuperview()
  9218. var same = false
  9219. if dataMessages.count > 1 {
  9220. self.containerPin.addSubview(self.signSelectedPin)
  9221. self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
  9222. self.signSelectedPin.layer.cornerRadius = 1
  9223. self.signSelectedPin.clipsToBounds = true
  9224. self.signSelectedPin.alignment = .fill
  9225. self.signSelectedPin.axis = .vertical
  9226. self.signSelectedPin.distribution = .fill
  9227. self.signSelectedPin.spacing = dataMessages.count == 3 ? 1.5 : 2
  9228. let heightSign: CGFloat = CGFloat((30 / dataMessages.count) - 1)
  9229. let widthSign: CGFloat = 2
  9230. for i in 0..<dataMessages.count {
  9231. let viewSign = UIView()
  9232. viewSign.backgroundColor = (i == (dataMessages.count - 1)) ? .white : .gray
  9233. viewSign.anchor(width: widthSign, height: heightSign)
  9234. viewSign.layer.cornerRadius = 1
  9235. viewSign.clipsToBounds = true
  9236. self.signSelectedPin.addArrangedSubview(viewSign)
  9237. }
  9238. if isPinned == -1 {
  9239. self.nextPinShowed = dataMessages.count - 1
  9240. } else if self.nextPinShowed != 0 {
  9241. if (self.nextPinShowed > isPinned) {
  9242. self.nextPinShowed-=1
  9243. same = true
  9244. } else if self.nextPinShowed == isPinned && dataMessages.count == 3 {
  9245. self.nextPinShowed-=2
  9246. }
  9247. } else if self.nextPinShowed != isPinned {
  9248. same = true
  9249. }
  9250. } else if self.nextPinShowed != isPinned {
  9251. same = true
  9252. }
  9253. if !same{
  9254. let chat = Chat.getMessageFromId(message_id: dataMessages[dataMessages.count - 1][TypeDataMessage.message_id] as? String ?? "")
  9255. if chat.count > 0 {
  9256. let text = Utils.previewMessageText(chat: chat[0])
  9257. if let attributeText = text as? NSMutableAttributedString {
  9258. animateLabelTextChange(label: self.textPin, newText: attributeText.string)
  9259. }
  9260. }
  9261. }
  9262. }
  9263. } else if self.containerPin.isDescendant(of: self.view) {
  9264. self.containerPin.subviews.forEach({ $0.removeFromSuperview() })
  9265. self.containerPin.removeFromSuperview()
  9266. self.tableChatView.contentInset.top = 0
  9267. }
  9268. }
  9269. @objc func viewPinTapped() {
  9270. var dataMessagesPin = self.dataMessages.filter({ $0[TypeDataMessage.is_pinned] as? String ?? "0" != "0"})
  9271. dataMessagesPin.sort {
  9272. let firstPinned = Int64($0[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  9273. let secondPinned = Int64($1[TypeDataMessage.is_pinned] as? String ?? "0") ?? 0
  9274. return firstPinned < secondPinned
  9275. }
  9276. let obj = ObjectGesture()
  9277. obj.message_id = dataMessagesPin[nextPinShowed][TypeDataMessage.message_id] as? String ?? ""
  9278. contentMessageTapped(obj)
  9279. if dataMessagesPin.count > 0 {
  9280. if nextPinShowed < dataMessagesPin.count - 1 {
  9281. nextPinShowed+=1
  9282. } else {
  9283. nextPinShowed = 0
  9284. }
  9285. DispatchQueue.main.async {
  9286. self.signSelectedPin.subviews.forEach({ $0.removeFromSuperview() })
  9287. self.signSelectedPin.removeFromSuperview()
  9288. self.containerPin.addSubview(self.signSelectedPin)
  9289. self.signSelectedPin.anchor(left: self.containerPin.leftAnchor, paddingLeft: 8, centerY: self.containerPin.centerYAnchor, width: 2, height: 30)
  9290. self.signSelectedPin.layer.cornerRadius = 1
  9291. self.signSelectedPin.clipsToBounds = true
  9292. self.signSelectedPin.alignment = .fill
  9293. self.signSelectedPin.axis = .vertical
  9294. self.signSelectedPin.distribution = .fill
  9295. self.signSelectedPin.spacing = dataMessagesPin.count == 3 ? 1.5 : 2
  9296. let heightSign: CGFloat = CGFloat((30 / dataMessagesPin.count) - 1)
  9297. let widthSign: CGFloat = 2
  9298. for i in 0..<dataMessagesPin.count {
  9299. let viewSign = UIView()
  9300. viewSign.backgroundColor = (i == self.nextPinShowed) ? .white : .gray
  9301. viewSign.anchor(width: widthSign, height: heightSign)
  9302. viewSign.layer.cornerRadius = 1
  9303. viewSign.clipsToBounds = true
  9304. self.signSelectedPin.addArrangedSubview(viewSign)
  9305. }
  9306. let chat = Chat.getMessageFromId(message_id: dataMessagesPin[dataMessagesPin.count - 1][TypeDataMessage.message_id] as? String ?? "")
  9307. if chat.count > 0 {
  9308. let text = Utils.previewMessageText(chat: chat[0])
  9309. if let attributeText = text as? NSMutableAttributedString {
  9310. self.animateLabelTextChange(label: self.textPin, newText: attributeText.string)
  9311. }
  9312. }
  9313. }
  9314. }
  9315. }
  9316. func animateLabelTextChange(label: UILabel, newText: String) {
  9317. let animationDuration = 0.1
  9318. UIView.animate(withDuration: animationDuration, animations: {
  9319. label.transform = CGAffineTransform(translationX: 0, y: -10)
  9320. label.alpha = 0
  9321. }) { _ in
  9322. // Change text after fade out
  9323. label.attributedText = newText.richText(fontSize: 14)
  9324. label.transform = CGAffineTransform(translationX: 0, y: 10)
  9325. // Animate back to original position and fade in
  9326. UIView.animate(withDuration: animationDuration) {
  9327. label.transform = .identity
  9328. label.alpha = 1
  9329. }
  9330. }
  9331. }
  9332. private func handleReply(indexPath: IndexPath, dataMessagesImage: [String: Any?] = [:], reffId: String = "") {
  9333. var dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  9334. var chatGroup: [Chat] = []
  9335. if reffId.isEmpty {
  9336. self.deleteReplyView()
  9337. if dataMessagesImage.count != 0 {
  9338. dataMessages = [dataMessagesImage]
  9339. } else {
  9340. self.textFieldSend.becomeFirstResponder()
  9341. }
  9342. self.reffId = dataMessages[indexPath.row]["message_id"] as? String
  9343. } else {
  9344. dataMessages = self.dataMessages.filter({ $0["message_id"] as? String ?? "" == reffId })
  9345. if dataMessages.count == 0 {
  9346. chatGroup = Chat.getMessageFromId(message_id: reffId)
  9347. }
  9348. self.reffId = reffId
  9349. }
  9350. if dataMessages.count == 0 && chatGroup.count == 0 {
  9351. self.deleteReplyView()
  9352. return
  9353. }
  9354. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  9355. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50 + (self.offset()*3)
  9356. if self.contraintBottomMention.constant > 0 {
  9357. self.contraintBottomMention.constant = self.contraintBottomMention.constant + self.heightTextFieldSend.constant
  9358. }
  9359. }, completion: nil)
  9360. if (self.currentIndexpath != nil) {
  9361. DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
  9362. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  9363. }
  9364. } else {
  9365. self.tableChatView.scrollToBottom()
  9366. }
  9367. self.viewTextfield.addSubview(self.containerPreviewReply)
  9368. self.containerPreviewReply.translatesAutoresizingMaskIntoConstraints = false
  9369. self.containerPreviewReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  9370. self.containerPreviewReply.topAnchor.constraint(equalTo: self.viewTextfield.topAnchor).isActive = true
  9371. if !self.containerLink.isDescendant(of: self.viewTextfield) {
  9372. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  9373. } else {
  9374. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  9375. }
  9376. self.bottomAnchorPreviewReply.isActive = true
  9377. self.containerPreviewReply.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  9378. self.containerPreviewReply.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  9379. let leftReply = UIView()
  9380. self.containerPreviewReply.addSubview(leftReply)
  9381. leftReply.translatesAutoresizingMaskIntoConstraints = false
  9382. leftReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  9383. leftReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor).isActive = true
  9384. leftReply.bottomAnchor.constraint(equalTo: self.containerPreviewReply.bottomAnchor).isActive = true
  9385. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  9386. leftReply.backgroundColor = .orangeColor
  9387. let titleReply = UILabel()
  9388. self.containerPreviewReply.addSubview(titleReply)
  9389. titleReply.translatesAutoresizingMaskIntoConstraints = false
  9390. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  9391. titleReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor, constant: 10).isActive = true
  9392. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  9393. let idMe = User.getMyPin() as String?
  9394. let f_pin = chatGroup.count == 0 ? (dataMessages[indexPath.row]["f_pin"] as? String ?? "") : chatGroup[0].fpin
  9395. if f_pin == idMe {
  9396. titleReply.text = "You".localized()
  9397. } else {
  9398. if self.isContactCenter {
  9399. let user: [User] = self.users.filter({$0.pin == dataMessages[indexPath.row]["f_pin"] as? String})
  9400. titleReply.text = user.first!.fullName
  9401. } else {
  9402. if chatGroup.count == 0 {
  9403. titleReply.text = self.dataPerson["name"]!!
  9404. } else {
  9405. if let dataPerson = User.getData(pin: f_pin) {
  9406. let namePerson = dataPerson.fullName
  9407. titleReply.text = namePerson + " ● " + "\(chatGroup[0].groupName)(\(chatGroup[0].name))"
  9408. }
  9409. }
  9410. }
  9411. }
  9412. titleReply.textColor = .orangeColor
  9413. let contentReply = UILabel()
  9414. self.containerPreviewReply.addSubview(contentReply)
  9415. contentReply.translatesAutoresizingMaskIntoConstraints = false
  9416. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  9417. contentReply.trailingAnchor.constraint(equalTo: containerPreviewReply.trailingAnchor, constant: -20).isActive = true
  9418. contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor).isActive = true
  9419. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  9420. let message_text = chatGroup.count == 0 ? (dataMessages[indexPath.row]["message_text"] as? String ?? "") : chatGroup[0].messageText
  9421. let attachment_flag = chatGroup.count == 0 ? (dataMessages[indexPath.row]["attachment_flag"] as? String ?? "") : chatGroup[0].attachmentFlag
  9422. let thumb_chat = chatGroup.count == 0 ? (dataMessages[indexPath.row]["thumb_id"] as? String ?? "") : chatGroup[0].thumb
  9423. let image_chat = chatGroup.count == 0 ? (dataMessages[indexPath.row]["image_id"] as? String ?? "") : chatGroup[0].image
  9424. let video_chat = chatGroup.count == 0 ? (dataMessages[indexPath.row]["video_id"] as? String ?? "") : chatGroup[0].video
  9425. let file_chat = chatGroup.count == 0 ? (dataMessages[indexPath.row]["file_id"] as? String ?? "") : chatGroup[0].file
  9426. if (attachment_flag == "0" && thumb_chat == "") {
  9427. contentReply.attributedText = message_text.richText()
  9428. } else if (attachment_flag == "1" || image_chat != "") {
  9429. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  9430. contentReply.text = "📷 Photo".localized()
  9431. } else {
  9432. contentReply.attributedText = message_text.richText()
  9433. }
  9434. } else if (attachment_flag == "2" || video_chat != "") {
  9435. if (message_text.trimmingCharacters(in: .whitespacesAndNewlines) == "") {
  9436. contentReply.text = "📹 Video".localized()
  9437. } else {
  9438. contentReply.attributedText = message_text.richText()
  9439. }
  9440. } else if (attachment_flag == "6" || file_chat != ""){
  9441. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  9442. } else if (attachment_flag == "11") {
  9443. contentReply.text = "❤️ Sticker"
  9444. }
  9445. contentReply.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .gray
  9446. let buttonCancelReply = UIButton(type: .custom)
  9447. self.containerPreviewReply.addSubview(buttonCancelReply)
  9448. buttonCancelReply.translatesAutoresizingMaskIntoConstraints = false
  9449. buttonCancelReply.trailingAnchor.constraint(equalTo: self.containerPreviewReply.trailingAnchor, constant: -10).isActive = true
  9450. buttonCancelReply.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  9451. buttonCancelReply.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  9452. buttonCancelReply.addTarget(nil, action: #selector(self.deleteReplyView), for: .touchUpInside)
  9453. buttonCancelReply.backgroundColor = .clear
  9454. buttonCancelReply.tintColor = .mainColor
  9455. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  9456. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  9457. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  9458. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  9459. if let dirPath = paths.first {
  9460. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  9461. let image : UIImage? = {
  9462. if let img = Nexilis.imageCache.object(forKey: thumb_chat as NSString) {
  9463. return img
  9464. }
  9465. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  9466. Nexilis.imageCache.setObject(img, forKey: thumb_chat as NSString)
  9467. return img
  9468. }
  9469. return nil
  9470. }()
  9471. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  9472. let imageThumb = UIImageView(image: image)
  9473. self.containerPreviewReply.addSubview(imageThumb)
  9474. imageThumb.layer.cornerRadius = 2.0
  9475. imageThumb.clipsToBounds = true
  9476. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  9477. imageThumb.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  9478. imageThumb.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  9479. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  9480. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  9481. if (attachment_flag == "2") {
  9482. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  9483. imageThumb.addSubview(imagePlay)
  9484. imagePlay.clipsToBounds = true
  9485. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  9486. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  9487. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  9488. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  9489. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  9490. imagePlay.tintColor = .white
  9491. }
  9492. }
  9493. }
  9494. if (attachment_flag == "11") {
  9495. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  9496. self.containerPreviewReply.addSubview(imageSticker)
  9497. imageSticker.layer.cornerRadius = 2.0
  9498. imageSticker.clipsToBounds = true
  9499. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  9500. imageSticker.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  9501. imageSticker.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  9502. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  9503. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  9504. }
  9505. if chatGroup.count > 0 {
  9506. self.textFieldSend.becomeFirstResponder()
  9507. }
  9508. }
  9509. func scrollToFirstSearchMessage(indexScroll: Int = 1) {
  9510. if textSearch.count < 2 {
  9511. return
  9512. }
  9513. var lastIndex = 0
  9514. let messageTextForSearch: [[String: Any?]] = self.dataMessages.reversed()
  9515. for idx in 0..<messageTextForSearch.count {
  9516. if (messageTextForSearch[idx]["message_text"] as? String ?? "").lowercased().contains(textSearch) {
  9517. lastIndex += 1
  9518. if lastIndex < indexScroll {
  9519. continue
  9520. }
  9521. lastScrollIdxSearch = lastIndex
  9522. let section = self.dataDates.firstIndex(of: messageTextForSearch[idx]["chat_date"] as? String ?? "")
  9523. if section == nil {
  9524. return
  9525. }
  9526. 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})
  9527. if row == nil {
  9528. return
  9529. }
  9530. let indexPath = IndexPath(row: row!, section: section!)
  9531. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  9532. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  9533. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  9534. let containerMessage = cell.contentView.subviews[0]
  9535. let idMe = User.getMyPin() as String?
  9536. if (messageTextForSearch[idx]["f_pin"] as? String == idMe) {
  9537. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  9538. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  9539. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  9540. containerMessage.backgroundColor = .clear
  9541. } else {
  9542. containerMessage.backgroundColor = .blueBubbleColor
  9543. }
  9544. }
  9545. } else {
  9546. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  9547. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  9548. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  9549. containerMessage.backgroundColor = .clear
  9550. } else {
  9551. containerMessage.backgroundColor = .whiteBubbleColor
  9552. }
  9553. }
  9554. }
  9555. }
  9556. }
  9557. titleSearchMatches.isHidden = false
  9558. if countMatchesSearch != 0 {
  9559. if countMatchesSearch > 1 {
  9560. titleSearchMatches.text = "\(lastScrollIdxSearch) " + "of".localized() + " \(countMatchesSearch) " + "matches".localized()
  9561. } else {
  9562. titleSearchMatches.text = "\(countMatchesSearch) " + "matches".localized()
  9563. }
  9564. } else {
  9565. titleSearchMatches.text = "Not found".localized()
  9566. }
  9567. if lastScrollIdxSearch == countMatchesSearch || countMatchesSearch == 0 {
  9568. buttonUp.isEnabled = false
  9569. buttonUp.tintColor = .gray
  9570. } else {
  9571. buttonUp.isEnabled = true
  9572. buttonUp.tintColor = .mainColor
  9573. }
  9574. if countMatchesSearch == 0 || lastScrollIdxSearch == 1 || countMatchesSearch == 1 {
  9575. buttonDown.isEnabled = false
  9576. buttonDown.tintColor = .gray
  9577. } else {
  9578. buttonDown.isEnabled = true
  9579. buttonDown.tintColor = .mainColor
  9580. }
  9581. break
  9582. }
  9583. }
  9584. }
  9585. // public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  9586. // let indexPath = tableChatView.indexPathsForVisibleRows?.first
  9587. // if indexPath != nil {
  9588. // let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  9589. // let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  9590. // if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && indexPath!.row > 0 {
  9591. // let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  9592. // let headerView = listViewOnSection[sect]
  9593. // headerView.isHidden = true
  9594. // }
  9595. // }
  9596. // }
  9597. //
  9598. // public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  9599. // if !decelerate {
  9600. // let indexPath = tableChatView.indexPathsForVisibleRows?.first
  9601. // if indexPath != nil {
  9602. // let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  9603. // let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  9604. // if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  9605. // let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  9606. // let headerView = listViewOnSection[sect]
  9607. // headerView.isHidden = true
  9608. // }
  9609. // }
  9610. // }
  9611. // }
  9612. }
  9613. extension UITableView {
  9614. func scrollToBottom(isAnimated:Bool = true){
  9615. DispatchQueue.main.asyncAfter(deadline: .now() + (isAnimated ? 0 : 0.6)) { [weak self] in
  9616. guard let self = self, self.numberOfSections > 0 else { return }
  9617. let lastSection = self.numberOfSections - 1
  9618. let numberOfRows = self.numberOfRows(inSection: lastSection)
  9619. guard numberOfRows > 0 else { return }
  9620. let indexPath = IndexPath(row: numberOfRows - 1, section: lastSection)
  9621. self.scrollToRow(at: indexPath, at: .bottom, animated: isAnimated)
  9622. }
  9623. }
  9624. func scrollToTop(isAnimated:Bool = true) {
  9625. DispatchQueue.main.async {
  9626. let indexPath = IndexPath(row: 0, section: 0)
  9627. if indexPath.row != -1 {
  9628. self.scrollToRow(at: indexPath, at: .top, animated: isAnimated)
  9629. }
  9630. }
  9631. }
  9632. }
  9633. extension UIImage {
  9634. public func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
  9635. UIGraphicsBeginImageContextWithOptions(
  9636. CGSize(width: self.size.width + insets.left + insets.right,
  9637. height: self.size.height + insets.top + insets.bottom), false, self.scale)
  9638. let _ = UIGraphicsGetCurrentContext()
  9639. let origin = CGPoint(x: insets.left, y: insets.top)
  9640. self.draw(at: origin)
  9641. let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
  9642. UIGraphicsEndImageContext()
  9643. return imageWithInsets
  9644. }
  9645. }
  9646. extension EditorPersonal: UISearchBarDelegate {
  9647. public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
  9648. timerSearch?.invalidate()
  9649. if searchText.count > 1 {
  9650. timerSearch = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {[self] _ in
  9651. textSearch = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
  9652. titleSearchMatches.isHidden = true
  9653. countMatchesSearch = Chat.getCountSearchMessage(key: textSearch, pin: unique_l_pin, isPersonal: true)
  9654. tableChatView.reloadData()
  9655. scrollToFirstSearchMessage()
  9656. })
  9657. }
  9658. }
  9659. }
  9660. public class ObjectGesture: UITapGestureRecognizer {
  9661. public var message_id = ""
  9662. public var image_id = ""
  9663. public var video_id = ""
  9664. public var file_id = ""
  9665. public var audio_id = ""
  9666. public var gif_id = ""
  9667. public var specFile = ""
  9668. public var imageView = UIImageView()
  9669. public var containerFile = UIView()
  9670. public var labelFile = UILabel()
  9671. public var videoURL: NSURL?
  9672. public var indexPath = IndexPath()
  9673. public var indexImageTapped: Int!
  9674. public var listImageFromGrouping: [ImageGrouping]!
  9675. public var isInitiator: Bool!
  9676. }
  9677. class navigationQLPreviewDocument: UIBarButtonItem {
  9678. var navigation = UINavigationController()
  9679. }
  9680. class segmentedControllerObject: UISegmentedControl {
  9681. var navigation = UINavigationController()
  9682. }
  9683. public class ImageGrouping {
  9684. public var messageId = ""
  9685. public var thumbId = ""
  9686. public var imageId = ""
  9687. public var status = ""
  9688. public var time = ""
  9689. public var lPin = ""
  9690. public var dataMessage: [String: Any?] = [:]
  9691. public var dataPerson: [String: String?] = [:]
  9692. public var dataGroup: [String: Any?] = [:]
  9693. public var dataTopic: [String: Any?] = [:]
  9694. public var isSelected = false
  9695. public init(messageId: String, thumbId: String, imageId: String, status: String, time: String, lPin: String, dataMessage: [String: Any?], dataPerson: [String: String?], dataGroup: [String: Any?], dataTopic: [String: Any?]) {
  9696. self.messageId = messageId
  9697. self.thumbId = thumbId
  9698. self.imageId = imageId
  9699. self.status = status
  9700. self.time = time
  9701. self.lPin = lPin
  9702. self.dataMessage = dataMessage
  9703. self.dataPerson = dataPerson
  9704. self.dataGroup = dataGroup
  9705. self.dataTopic = dataTopic
  9706. }
  9707. }
  9708. public class TypeDataMessage {
  9709. public static let message_id = "message_id"
  9710. public static let f_pin = "f_pin"
  9711. public static let l_pin = "l_pin"
  9712. public static let message_scope_id = "message_scope_id"
  9713. public static let server_date = "server_date"
  9714. public static let status = "status"
  9715. public static let message_text = "message_text"
  9716. public static let audio_id = "audio_id"
  9717. public static let video_id = "video_id"
  9718. public static let image_id = "image_id"
  9719. public static let thumb_id = "thumb_id"
  9720. public static let read_receipts = "read_receipts"
  9721. public static let chat_id = "chat_id"
  9722. public static let file_id = "file_id"
  9723. public static let attachment_flag = "attachment_flag"
  9724. public static let reff_id = "reff_id"
  9725. public static let lock = "lock"
  9726. public static let is_stared = "is_stared"
  9727. public static let blog_id = "blog_id"
  9728. public static let credential = "credential"
  9729. public static let progress = "progress"
  9730. public static let chat_date = "chat_date"
  9731. public static let is_call_center = "is_call_center"
  9732. public static let call_center_id = "call_center_id"
  9733. public static let opposite_pin = "opposite_pin"
  9734. public static let last_edit = "last_edit"
  9735. public static let gif_id = "gif_id"
  9736. public static let is_forwarded = "is_forwarded"
  9737. public static let is_pinned = "is_pinned"
  9738. public static let is_secret = "is_secret"
  9739. public static let spec_file = "spec_file"
  9740. public static let is_bot = "is_bot"
  9741. }
  9742. extension String {
  9743. var nilIfEmpty: String? {
  9744. let v = self.trimmingCharacters(in: .whitespacesAndNewlines)
  9745. return v.isEmpty ? nil : v
  9746. }
  9747. }