EditorPersonal.swift 521 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533
  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. public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate {
  19. @IBOutlet var wallpaperView: UIImageView!
  20. @IBOutlet var viewButton: UIView!
  21. @IBOutlet var constraintViewTextField: NSLayoutConstraint!
  22. @IBOutlet var buttonVoice: UIButton!
  23. @IBOutlet var buttonSendImage: UIButton!
  24. @IBOutlet var buttonSendPhoto: UIButton!
  25. @IBOutlet var buttonSendSticker: UIButton!
  26. @IBOutlet var buttonSendFile: UIButton!
  27. @IBOutlet var textFieldSend: CustomTextView!
  28. @IBOutlet var heightTextFieldSend: NSLayoutConstraint!
  29. @IBOutlet var buttonSendChat: UIButton!
  30. @IBOutlet var tableChatView: UITableView!
  31. @IBOutlet var constraintTopTextField: NSLayoutConstraint!
  32. @IBOutlet var constraintBottomAttachment: NSLayoutConstraint!
  33. @IBOutlet var viewTextfield: UIView!
  34. @IBOutlet weak var buttonAckConfidential: UIButton!
  35. @IBOutlet weak var constraintLeftTextField: NSLayoutConstraint!
  36. @IBOutlet weak var constraintBottomTableViewWithTextfield: NSLayoutConstraint!
  37. @IBOutlet weak var viewAttachment: UIStackView!
  38. public var dataPerson: [String: String?] = [:]
  39. var dataMessages: [[String: Any?]] = []
  40. var dataDates: [String] = []
  41. var users: [User] = []
  42. public var dataMessageForward: [[String: Any?]]?
  43. var imageVideoPicker: ImageVideoPicker!
  44. var documentPicker: DocumentPicker!
  45. var currentIndexpath: IndexPath?
  46. var previewItem: NSURL?
  47. var reffId: String?
  48. var stickers = [String]()
  49. public var unique_l_pin = ""
  50. public var isContactCenter = false
  51. public var isRequestContactCenter = true
  52. public var fromNotification = false
  53. public var onGoingCC = false
  54. public var fPinContacCenter = ""
  55. public var complaintId = ""
  56. public var referenceMessageId = ""
  57. public var referenceChatDate = ""
  58. var channelContactCenter = ""
  59. var counter = 0
  60. var dateStartCC = ""
  61. var markerCounter: String?
  62. var buttonScrollToBottom = UIButton()
  63. let indicatorCounterBSTB = UIView()
  64. let labelCounter = UILabel()
  65. var copySession = false
  66. var forwardSession = false
  67. var deleteSession = false
  68. var isSearching = false
  69. let containerMultpileSelectSession = UIView()
  70. let containerAction = UIView()
  71. var removed = false
  72. var isConfidential = false
  73. var isAck = false
  74. var isSecret = false
  75. let viewSticker = UIView()
  76. let containerLink = UIView()
  77. let containerPreviewReply = UIView()
  78. var bottomAnchorPreviewReply = NSLayoutConstraint()
  79. var blocking = ""
  80. var timeoutCC = Timer()
  81. var nowSelectedCategoryCC = ""
  82. var showToastTwiceClick = false
  83. var showToast30s = false
  84. var allowTyping = true
  85. var hapticSwipeLeft = false
  86. var listTimerCredential: [String: Int] = [:]
  87. var timerCredential: [String: Timer] = [:]
  88. let contactChatNav = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController
  89. var searchBar: UISearchBar!
  90. var constraintBottomContainerMultpileSelectSession = NSLayoutConstraint()
  91. var titleSearchMatches: UILabel!
  92. var textSearch = ""
  93. var countMatchesSearch = 0
  94. var lastScrollIdxSearch = 0
  95. var buttonUp: UIButton!
  96. var buttonDown: UIButton!
  97. var multipleOffsetUp = 1
  98. var lastOffsetDown = 1
  99. var gettingDataMessage = true
  100. var showingLink = ""
  101. var isAlwaysHideLinkPreview = false
  102. var timerCheckLink: Timer?
  103. var timerFakeProgress: Timer?
  104. var showMenuContext = false
  105. var touchedSubview = UIView()
  106. var listViewOnSection: [UIView] = []
  107. var fromVCAC = false
  108. var serviceIdCC = ""
  109. var isDirectCC = false
  110. var fakeProgMultip = 0
  111. let maxFakeProgMultip = 2
  112. var groupImages: [String:[ImageGrouping]] = [:]
  113. var titleText: String!
  114. var lastY: CGFloat = 0
  115. var editVC = UIViewController()
  116. var editTextView = CustomTextView()
  117. var isEditingMessage = false
  118. var constraintBottomeditTextView: NSLayoutConstraint!
  119. var constraintHeighteditTextView: NSLayoutConstraint!
  120. var constraintBottomSendEditTV: NSLayoutConstraint!
  121. let locationManager = CLLocationManager()
  122. var longitude = ""
  123. var latitude = ""
  124. var isBlackCancelButton = false
  125. let buttonSendEdit = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
  126. var audioPlayers: [IndexPath: AVAudioPlayer] = [:]
  127. var timers: [IndexPath: Timer] = [:]
  128. var playingIndexPath: IndexPath?
  129. var timerSearch: Timer?
  130. func offset() -> CGFloat{
  131. guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
  132. return CGFloat(fontSize)
  133. }
  134. public override func viewDidDisappear(_ animated: Bool) {
  135. if self.isMovingFromParent {
  136. removeAllObjectBeforeDismissVC()
  137. }
  138. }
  139. public override func viewDidAppear(_ animated: Bool) {
  140. let navBarAppearance = UINavigationBarAppearance()
  141. navBarAppearance.configureWithOpaqueBackground()
  142. navBarAppearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  143. navigationController?.navigationBar.standardAppearance = navBarAppearance
  144. navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
  145. navigationController?.navigationBar.isTranslucent = false
  146. navigationController?.navigationBar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  147. navigationController?.navigationBar.tintColor = .white
  148. navigationController?.navigationBar.overrideUserInterfaceStyle = .dark
  149. self.setNeedsStatusBarAppearanceUpdate()
  150. navigationController?.navigationBar.barStyle = .black
  151. if self.navigationController?.isNavigationBarHidden ?? false {
  152. self.navigationController?.setNavigationBarHidden(false, animated: false)
  153. }
  154. navigationController?.navigationBar.prefersLargeTitles = false
  155. navigationController?.navigationItem.largeTitleDisplayMode = .never
  156. updateProfile()
  157. gettingDataMessage = false
  158. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  159. if indexPath != nil && currentIndexpath != nil {
  160. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  161. let isPinned = headerRect.origin.y <= tableChatView.contentOffset.y
  162. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  163. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  164. let headerView = listViewOnSection[sect]
  165. headerView.isHidden = true
  166. }
  167. }
  168. }
  169. public override func viewDidLoad() {
  170. super.viewDidLoad()
  171. // navigationController?.navigationBar.topItem?.title = ""
  172. Utils.addBackground(view: contactChatNav.view)
  173. if let dataWall = UserDefaults.standard.data(forKey: "chatWallpaper") {
  174. wallpaperView.image = UIImage(data: UserDefaults.standard.data(forKey: "chatWallpaper")!)
  175. }
  176. else {
  177. wallpaperView.isHidden = true
  178. }
  179. if Nexilis.fromMAB {
  180. Nexilis.floatingButton.isHidden = true
  181. }
  182. viewButton.layer.shadowColor = self.traitCollection.userInterfaceStyle == .dark ? UIColor.white.cgColor : UIColor.gray.cgColor
  183. viewButton.layer.shadowOpacity = 1
  184. viewButton.layer.shadowOffset = .zero
  185. viewButton.layer.shadowRadius = 3
  186. viewButton.addTopBorder(with: UIColor.lightGray, andWidth: 1.0)
  187. // buttonVoice.setImage(resizeImage(image: UIImage(named: "Voice-Record", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)), for: .normal)
  188. viewAttachment.backgroundColor = .white
  189. 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)
  190. 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)
  191. 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)
  192. 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)
  193. 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)
  194. buttonSendChat.circle()
  195. buttonSendChat.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
  196. buttonSendChat.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  197. if isContactCenter {
  198. buttonAckConfidential.isHidden = true
  199. constraintLeftTextField.constant = 20
  200. } else {
  201. buttonAckConfidential.circle()
  202. buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
  203. buttonAckConfidential.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  204. buttonAckConfidential.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  205. }
  206. textFieldSend.backgroundColor = .white
  207. textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
  208. textFieldSend.layer.borderWidth = 1.0
  209. textFieldSend.text = "Send message".localized()
  210. textFieldSend.textColor = UIColor.lightGray
  211. textFieldSend.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  212. textFieldSend.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  213. textFieldSend.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  214. textFieldSend.font = UIFont.systemFont(ofSize: 12 + offset())
  215. textFieldSend.delegate = self
  216. textFieldSend.customDelegate = self
  217. textFieldSend.allowsEditingTextAttributes = true
  218. navigationItem.rightBarButtonItem?.tintColor = UIColor.secondaryColor
  219. imageVideoPicker = ImageVideoPicker(presentationController: self, delegate: self)
  220. documentPicker = DocumentPicker(presentationController: self, delegate: self)
  221. let fm = FileManager.default
  222. if Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") != nil {
  223. let path = Bundle.resourceBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  224. let items = try! fm.contentsOfDirectory(atPath: path)
  225. for item in items {
  226. if item.hasPrefix("sticker") {
  227. stickers.append(item)
  228. }
  229. }
  230. } else {
  231. let path = Bundle.resourcesMediaBundle(for: Nexilis.self).resourcePath! //resourcesMediaBundle
  232. let items = try! fm.contentsOfDirectory(atPath: path)
  233. for item in items {
  234. if item.hasPrefix("sticker") {
  235. stickers.append(item)
  236. }
  237. }
  238. }
  239. tableChatView.register(UITableViewCell.self, forCellReuseIdentifier: "cellEditorPersonal")
  240. loadData()
  241. setRightButtonItem()
  242. let center: NotificationCenter = NotificationCenter.default
  243. center.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  244. center.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  245. center.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil)
  246. center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil)
  247. center.addObserver(self, selector: #selector(onUploadChat(notification:)), name: NSNotification.Name(rawValue: "onUploadChat"), object: nil)
  248. center.addObserver(self, selector: #selector(onUnfriend(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil)
  249. center.addObserver(self, selector: #selector(onTyping(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerTypingChat), object: nil)
  250. center.addObserver(self, selector: #selector(onFailedSendMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.failedSendMessage), object: nil)
  251. center.addObserver(self, selector: #selector(onRefreshCallLog(notification:)), name: NSNotification.Name(rawValue: "refreshCallLog"), object: nil)
  252. locationManager.delegate = self
  253. locationManager.requestWhenInUseAuthorization()
  254. DispatchQueue.global().async { [self] in
  255. // Check if location services are enabled
  256. if CLLocationManager.locationServicesEnabled() {
  257. locationManager.desiredAccuracy = kCLLocationAccuracyBest
  258. locationManager.startUpdatingLocation()
  259. } else {
  260. print("Location services are not enabled.")
  261. }
  262. }
  263. if dataMessageForward != nil {
  264. for i in 0..<dataMessageForward!.count {
  265. let isForwarded = (dataMessageForward![i][TypeDataMessage.is_forwarded] as? Int) ?? 0
  266. 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)
  267. }
  268. dataMessageForward = nil
  269. }
  270. if isContactCenter && !isRequestContactCenter && !onGoingCC {
  271. var companyName = ""
  272. Database.shared.database?.inTransaction({ fmdb, rollback in
  273. do {
  274. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT first_name, last_name FROM BUDDY where official_account = '1'"), cursor.next() {
  275. companyName = cursor.string(forColumnIndex: 0)! + " " + cursor.string(forColumnIndex: 1)!
  276. companyName = companyName.trimmingCharacters(in: .whitespaces)
  277. cursor.close()
  278. }
  279. } catch {
  280. rollback.pointee = true
  281. print("Access database error: \(error.localizedDescription)")
  282. }
  283. })
  284. self.dateStartCC = "\(Date().currentTimeMillis())"
  285. let myName = User.getData(pin: User.getMyPin() as String?)
  286. 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)
  287. if channelContactCenter == "1" {
  288. if let pin = dataPerson["f_pin"] {
  289. let controller = QmeraAudioViewController()
  290. controller.user = User.getData(pin: pin)
  291. controller.isOutgoing = true
  292. controller.modalPresentationStyle = .overCurrentContext
  293. present(controller, animated: true, completion: nil)
  294. }
  295. } else if channelContactCenter == "2" {
  296. let videoVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
  297. videoVC.dataPerson.append(dataPerson)
  298. self.show(videoVC, sender: nil)
  299. }
  300. }
  301. if isDirectCC {
  302. directCC()
  303. }
  304. // else if isContactCenter {
  305. // let buttonId = UIButton()
  306. // if channelContactCenter == "0" {
  307. // buttonId.tag = 0
  308. // ccAction(sender: buttonId)
  309. // } else if channelContactCenter == "1" {
  310. // buttonId.tag = 1
  311. // ccAction(sender: buttonId)
  312. // } else if channelContactCenter == "2" {
  313. // buttonId.tag = 2
  314. // ccAction(sender: buttonId)
  315. // }
  316. // }
  317. }
  318. public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  319. guard let location = locations.last else { return }
  320. latitude = "\(location.coordinate.latitude)"
  321. longitude = "\(location.coordinate.longitude)"
  322. locationManager.stopUpdatingLocation()
  323. }
  324. public func afterUnfriend() {
  325. DispatchQueue.main.async {
  326. for timer in self.timerCredential.values {
  327. timer.invalidate()
  328. }
  329. self.timeoutCC.invalidate()
  330. SecureUserDefaults.shared.removeValue(forKey: "inEditorPersonal")
  331. NotificationCenter.default.removeObserver(self)
  332. }
  333. }
  334. private func setRightButtonItem() {
  335. navigationItem.rightBarButtonItems = nil
  336. let actionDelete = UIAction(title: "Delete Conversation".localized(), handler: {(_) in
  337. if !self.isContactCenter {
  338. let alert = LibAlertController(title: "", message: "Are you sure to delete all message in this conversation?".localized(), preferredStyle: .alert)
  339. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.default, handler: nil))
  340. alert.addAction(UIAlertAction(title: "Delete".localized(), style: .destructive, handler: {(_) in
  341. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  342. do {
  343. _ = 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")
  344. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "l_pin='\(self.dataPerson["f_pin"]!!)'")
  345. let l_pin = self.dataPerson["f_pin"]!!
  346. SecureUserDefaults.shared.removeValue(forKey: "new_saved_\(l_pin)")
  347. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  348. if self.fromNotification {
  349. self.didTapExit()
  350. } else {
  351. self.navigationController?.popViewController(animated: true)
  352. }
  353. } catch {
  354. rollback.pointee = true
  355. print("Access database error: \(error.localizedDescription)")
  356. }
  357. })
  358. }))
  359. self.present(alert, animated: true, completion: nil)
  360. }
  361. })
  362. let actionSearch = UIAction(title: "Search".localized(), handler: {(_) in
  363. self.isSearching = true
  364. if self.reffId != nil {
  365. self.deleteReplyView()
  366. }
  367. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  368. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  369. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  370. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  371. self.navigationItem.rightBarButtonItems = nil
  372. }
  373. self.navigationItem.rightBarButtonItem = cancelButton
  374. if self.isContactCenter || self.fromNotification {
  375. self.navigationItem.leftBarButtonItem = nil
  376. }
  377. self.changeAppBar()
  378. self.addMultipleSelectSession()
  379. }
  380. })
  381. let actionUnblock = UIAction(title: "Unblock".localized(), handler: {(_) in
  382. if !self.isContactCenter {
  383. DispatchQueue.global().async {
  384. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getUnBlock(l_pin: self.dataPerson["f_pin"]!!)) {
  385. if !response.isOk() {
  386. DispatchQueue.main.async {
  387. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  388. imageView.tintColor = .white
  389. 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)
  390. banner.show()
  391. }
  392. } else {
  393. DispatchQueue.main.async { [self] in
  394. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  395. do {
  396. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  397. "ex_block" : "0"
  398. ], _where: "f_pin = '\(self.dataPerson["f_pin"]!!)'")
  399. } catch {
  400. rollback.pointee = true
  401. print("Access database error: \(error.localizedDescription)")
  402. }
  403. })
  404. containerAction.subviews.forEach({ $0.removeFromSuperview() })
  405. containerAction.removeFromSuperview()
  406. setRightButtonItem()
  407. changeAppBar()
  408. }
  409. }
  410. } else {
  411. DispatchQueue.main.async {
  412. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  413. imageView.tintColor = .white
  414. 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)
  415. banner.show()
  416. }
  417. }
  418. }
  419. }
  420. })
  421. let actionBlock = UIAction(title: "Block".localized(), handler: {(_) in
  422. if !self.isContactCenter {
  423. DispatchQueue.global().async {
  424. if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getBlock(l_pin: self.dataPerson["f_pin"]!!)) {
  425. if !response.isOk() {
  426. DispatchQueue.main.async {
  427. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  428. imageView.tintColor = .white
  429. 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)
  430. banner.show()
  431. }
  432. } else {
  433. DispatchQueue.main.async { [self] in
  434. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  435. do {
  436. _ = Database.shared.updateRecord(fmdb: fmdb, table: "BUDDY", cvalues: [
  437. "ex_block" : "1"
  438. ], _where: "f_pin = '\(self.dataPerson["f_pin"]!!)'")
  439. } catch {
  440. rollback.pointee = true
  441. print("Access database error: \(error.localizedDescription)")
  442. }
  443. })
  444. setRightButtonItem()
  445. changeAppBar()
  446. }
  447. }
  448. } else {
  449. DispatchQueue.main.async {
  450. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  451. imageView.tintColor = .white
  452. 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)
  453. banner.show()
  454. }
  455. }
  456. }
  457. }
  458. })
  459. var menu = UIMenu(title: "", children: [
  460. actionSearch,
  461. actionDelete
  462. ])
  463. let exblock = User.getDataCanNil(pin: self.dataPerson["f_pin"]!!)?.ex_block
  464. blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
  465. if blocking == "1" && self.dataPerson["f_pin"]!! != "-999" {
  466. menu = UIMenu(title: "", children: [
  467. actionSearch,
  468. actionUnblock,
  469. actionDelete
  470. ])
  471. blockedView(blocked: "1")
  472. } else if blocking == "0" {
  473. if self.dataPerson["f_pin"]!! != "-999" && complaintId.isEmpty {
  474. menu = UIMenu(title: "", children: [
  475. actionSearch,
  476. actionBlock,
  477. actionDelete
  478. ])
  479. } else if !complaintId.isEmpty{
  480. menu = UIMenu(title: "", children: [
  481. actionSearch
  482. ])
  483. }
  484. else {
  485. menu = UIMenu(title: "", children: [
  486. actionSearch,
  487. actionDelete
  488. ])
  489. }
  490. if containerAction.isDescendant(of: self.view) {
  491. containerAction.subviews.forEach({ $0.removeFromSuperview() })
  492. containerAction.removeFromSuperview()
  493. }
  494. } else {
  495. blockedView(blocked: "-1")
  496. changeAppBar()
  497. }
  498. let moreIcon = UIBarButtonItem(image: UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), menu: menu)
  499. let buttonAudioCall = UIBarButtonItem(image: UIImage(systemName: "phone", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  500. // let buttonSearch = UIBarButtonItem(image: UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  501. buttonAudioCall.tag = 0
  502. let buttonVideoCall = UIBarButtonItem(image: UIImage(systemName: "video", withConfiguration: UIImage.SymbolConfiguration(pointSize: 18, weight: .regular, scale: .default)), style: .plain, target: self, action: #selector(audioVideoCall(sender:)))
  503. buttonVideoCall.tag = 1
  504. 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:)))
  505. if dataPerson["f_pin"] != "-999" && !isContactCenter && blocking == "0" {
  506. navigationItem.rightBarButtonItems = [moreIcon,buttonAudioCall,buttonVideoCall]
  507. } else if !isContactCenter {
  508. navigationItem.rightBarButtonItem = moreIcon
  509. } else if !complaintId.isEmpty { //!complaintId.isEmpty
  510. navigationItem.rightBarButtonItems = [moreIcon,buttonAddRoom]
  511. }
  512. }
  513. func loadData() {
  514. if (unique_l_pin != "" || isContactCenter) {
  515. getDataProfile(fPin: unique_l_pin)
  516. if isContactCenter && !isRequestContactCenter && users.count == 0 {
  517. if !unique_l_pin.isEmpty {
  518. users.append(User.getData(pin: unique_l_pin) ?? User(pin: ""))
  519. }
  520. }
  521. }
  522. if onGoingCC {
  523. SecureUserDefaults.shared.set(self.fPinContacCenter, forKey: "inEditorPersonal")
  524. } else {
  525. SecureUserDefaults.shared.set(dataPerson["f_pin"] ?? "", forKey: "inEditorPersonal")
  526. }
  527. UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [dataPerson["f_pin"]!!])
  528. if isContactCenter || fromNotification {
  529. let imageButton = UIImageView(frame: CGRect(x: -16, y: 0, width: 20, height: 44))
  530. imageButton.image = UIImage(systemName: "chevron.backward", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default))?.withTintColor(.white)
  531. imageButton.contentMode = .left
  532. let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapExit))
  533. imageButton.isUserInteractionEnabled = true
  534. imageButton.addGestureRecognizer(tapGestureRecognizer)
  535. let leftItem = UIBarButtonItem(customView: imageButton)
  536. self.navigationItem.leftBarButtonItem = leftItem
  537. }
  538. if dataPerson["f_pin"] == "-999" {
  539. chatbot()
  540. }
  541. let exblock = User.getData(pin: self.dataPerson["f_pin"]!!)?.ex_block
  542. blocking = exblock == nil ? "0" : exblock!.isEmpty ? "0" : exblock!
  543. changeAppBar()
  544. getData()
  545. tableChatView.alpha = 0
  546. tableChatView.delegate = self
  547. tableChatView.dataSource = self
  548. tableChatView.keyboardDismissMode = .interactive
  549. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  550. tapGesture.cancelsTouchesInView = false
  551. tableChatView.addGestureRecognizer(tapGesture)
  552. if !isContactCenter {
  553. getCounter()
  554. if counter > 0 && dataMessages.count >= counter {
  555. markerCounter = dataMessages[dataMessages.count - counter]["message_id"] as? String
  556. }
  557. if !referenceMessageId.isEmpty {
  558. if dataMessages.firstIndex(where: {$0["message_id"] as? String == referenceMessageId} ) != 0 {
  559. DispatchQueue.main.async {
  560. let section = self.dataDates.firstIndex(of: self.referenceChatDate)
  561. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.referenceChatDate}).firstIndex(where: { $0["message_id"] as? String == self.referenceMessageId})
  562. if row != nil && section != nil {
  563. let indexPath = IndexPath(row: row!, section: section!)
  564. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: false)
  565. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .yellow
  566. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  567. self.tableChatView.cellForRow(at: indexPath)?.contentView.backgroundColor = .clear
  568. })
  569. }
  570. }
  571. }
  572. } else if counter != 0 && dataMessages.count >= counter {
  573. if dataMessages.firstIndex(where: {$0["message_id"] as? String == markerCounter} ) != 0 {
  574. DispatchQueue.main.async {
  575. let data = self.dataMessages.filter({ $0["message_id"] as? String == self.markerCounter })
  576. if data.count > 0 {
  577. let section = self.dataDates.firstIndex(of: data[0]["chat_date"] as? String ?? "")
  578. 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})
  579. self.tableChatView.scrollToRow(at: IndexPath(row: row!, section: section!), at: .bottom, animated: false)
  580. }
  581. }
  582. } else {
  583. tableChatView.scrollToTop()
  584. }
  585. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in
  586. if currentIndexpath == nil && counter != 0 {
  587. let idMe = User.getMyPin() as String?
  588. if let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == markerCounter}) {
  589. for i in idx..<dataMessages.count {
  590. if dataMessages[i]["f_pin"] as? String != idMe {
  591. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: dataMessages[i]["message_id"] as? String ?? "")
  592. }
  593. }
  594. counter = 0
  595. updateCounter(counter: counter)
  596. }
  597. }
  598. }
  599. } else {
  600. let l_pin = self.dataPerson["f_pin"] as? String ?? ""
  601. if let dataSaved: String = SecureUserDefaults.shared.value(forKey: "new_saved_\(l_pin)") {
  602. let data = dataSaved
  603. if let jsonData = data.data(using: .utf8),
  604. let dataJson = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] {
  605. let last_m = dataJson["text"] ?? ""
  606. let last_r = dataJson["reffId"] ?? ""
  607. if !last_m.isEmpty {
  608. textFieldSend.attributedText = last_m.richText(isEditing: true)
  609. textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  610. }
  611. if !last_r.isEmpty {
  612. handleReply(indexPath: IndexPath(row: 0, section: 0), reffId: last_r)
  613. }
  614. }
  615. }
  616. tableChatView.scrollToBottom(isAnimated: false)
  617. }
  618. } else if isContactCenter && onGoingCC {
  619. let idMe = User.getMyPin() as String?
  620. for i in 0..<dataMessages.count {
  621. if dataMessages[i]["f_pin"] as? String != idMe {
  622. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: dataMessages[i]["message_id"] as? String ?? "")
  623. }
  624. }
  625. tableChatView.scrollToBottom(isAnimated: false)
  626. } else {
  627. tableChatView.scrollToBottom(isAnimated: false)
  628. }
  629. if tableChatView.alpha != 1 {
  630. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  631. UIView.animate(withDuration: 0.5, animations: {
  632. self.tableChatView.alpha = 1.0
  633. })
  634. })
  635. }
  636. for data in listTimerCredential {
  637. if data.value > 0 {
  638. var second = data.value
  639. var timer = Timer()
  640. timerCredential[data.key] = timer
  641. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  642. second -= 1
  643. self.listTimerCredential[data.key] = second
  644. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key })
  645. if (idx != nil) {
  646. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  647. 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 })
  648. if second == 0 {
  649. timer.invalidate()
  650. self.listTimerCredential.removeValue(forKey: data.key)
  651. self.timerCredential.removeValue(forKey: data.key)
  652. SecureUserDefaults.shared.removeValue(forKey: data.key)
  653. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data.key})
  654. if idx != nil {
  655. self.dataMessages[idx!]["lock"] = "2"
  656. self.dataMessages[idx!]["reff_id"] = ""
  657. }
  658. DispatchQueue.global().async {
  659. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  660. do {
  661. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  662. "lock" : "2"
  663. ], _where: "message_id = '\(data.key)'")
  664. } catch {
  665. rollback.pointee = true
  666. print("Access database error: \(error.localizedDescription)")
  667. }
  668. })
  669. }
  670. }
  671. if row != nil && section != nil {
  672. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  673. }
  674. }
  675. })
  676. }
  677. }
  678. }
  679. private func chatbot() {
  680. let containerChatbot = UIView()
  681. self.view.addSubview(containerChatbot)
  682. containerChatbot.translatesAutoresizingMaskIntoConstraints = false
  683. NSLayoutConstraint.activate([
  684. containerChatbot.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  685. containerChatbot.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  686. containerChatbot.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  687. containerChatbot.heightAnchor.constraint(equalToConstant: 120)
  688. ])
  689. containerChatbot.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  690. let labelChatbot = UILabel()
  691. containerChatbot.addSubview(labelChatbot)
  692. labelChatbot.translatesAutoresizingMaskIntoConstraints = false
  693. NSLayoutConstraint.activate([
  694. labelChatbot.centerYAnchor.constraint(equalTo: containerChatbot.centerYAnchor),
  695. labelChatbot.centerXAnchor.constraint(equalTo: containerChatbot.centerXAnchor),
  696. ])
  697. labelChatbot.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  698. labelChatbot.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  699. labelChatbot.text = "Interactive chatbot. Coming soon".localized()
  700. }
  701. private func changeAppBar() {
  702. let viewAppBar = UIView()
  703. viewAppBar.frame.size = CGSize(width: self.view.frame.size.width, height: 44)
  704. if !isSearching {
  705. let imageProfile = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  706. imageProfile.circle()
  707. imageProfile.clipsToBounds = true
  708. let pictureImage = dataPerson["picture"]!
  709. var count = 0
  710. if isContactCenter {
  711. if fPinContacCenter.isEmpty && isRequestContactCenter {
  712. getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  713. imageProfile.image = image
  714. }
  715. viewAppBar.addSubview(imageProfile)
  716. } else {
  717. if users.count == 1 {
  718. viewAppBar.addSubview(imageProfile)
  719. getImage(name: users[0].thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  720. imageProfile.image = image
  721. imageProfile.contentMode = .scaleAspectFit
  722. }
  723. } else {
  724. for user in users {
  725. if count == 3 {
  726. count += 1
  727. continue
  728. }
  729. if count == 0 {
  730. let pictures = UIImageView(frame: CGRect(x: 0, y: 7, width: 30, height: 30))
  731. pictures.circle()
  732. pictures.clipsToBounds = true
  733. viewAppBar.addSubview(pictures)
  734. getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  735. pictures.image = image
  736. pictures.contentMode = .scaleAspectFit
  737. }
  738. } else {
  739. let pictures = UIImageView(frame: CGRect(x: count * 20 , y: 7, width: 30, height: 30))
  740. pictures.circle()
  741. pictures.clipsToBounds = true
  742. viewAppBar.addSubview(pictures)
  743. getImage(name: user.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  744. pictures.image = image
  745. pictures.contentMode = .scaleAspectFit
  746. }
  747. }
  748. count += 1
  749. }
  750. }
  751. }
  752. } else if dataPerson["f_pin"]!! == "-999" {
  753. viewAppBar.addSubview(imageProfile)
  754. if !Utils.getIconDock().isEmpty {
  755. let urlString = Utils.getUrlDock()!
  756. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  757. let imageData = cachedImage
  758. imageProfile.image = imageData
  759. } else {
  760. DispatchQueue.global().async{
  761. Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in
  762. guard let data = data, error == nil else { return }
  763. DispatchQueue.main.async() {
  764. if UIImage(data: data) != nil {
  765. let imageData = UIImage(data: data)!
  766. imageProfile.image = imageData
  767. ImageCache.shared.save(image: imageData, forKey: urlString)
  768. }
  769. }
  770. }
  771. }
  772. }
  773. } else {
  774. imageProfile.image = UIImage(named: "pb_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  775. }
  776. imageProfile.contentMode = .scaleAspectFit
  777. }
  778. else if (pictureImage != "" && pictureImage != nil) {
  779. viewAppBar.addSubview(imageProfile)
  780. imageProfile.setImage(name: pictureImage!)
  781. imageProfile.contentMode = .scaleAspectFill
  782. } else {
  783. viewAppBar.addSubview(imageProfile)
  784. imageProfile.image = UIImage(systemName: "person")
  785. imageProfile.contentMode = .scaleAspectFit
  786. imageProfile.backgroundColor = .lightGray
  787. }
  788. var titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 250, height: 44))
  789. if blocking == "-1" || blocking == "1" {
  790. titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
  791. } else if isContactCenter {
  792. titleNavigation = UILabel(frame: CGRect(x: 35, y: 0, width: viewAppBar.frame.size.width - 150, height: 44))
  793. if users.count > 0 {
  794. 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))
  795. }
  796. }
  797. viewAppBar.addSubview(titleNavigation)
  798. 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) {
  799. var name = dataPerson["name"]!!
  800. if (isContactCenter) {
  801. name = name + " " + "Contact Center".localized()
  802. titleNavigation.text = name
  803. } else {
  804. titleNavigation.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  805. }
  806. } else if User.isVerified(official_account: (dataPerson["isOfficial"] ?? "")!) && !isContactCenter {
  807. let name = dataPerson["name"]!!
  808. titleNavigation.set(image: UIImage(named: "ic_verified", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  809. } else if User.isInternal(userType: (dataPerson["user_type"] ?? "")!) && !isContactCenter {
  810. let name = dataPerson["name"]!!
  811. titleNavigation.set(image: UIImage(named: "ic_internal", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(name)", size: 15, y: -4)
  812. } else {
  813. if !isContactCenter {
  814. titleNavigation.text = dataPerson["name"] as? String
  815. } else {
  816. if users.count == 1 {
  817. titleNavigation.text = users[0].fullName
  818. } else {
  819. var stringName = ""
  820. for user in users {
  821. if stringName.isEmpty {
  822. stringName = user.fullName
  823. } else {
  824. stringName += ", \(user.fullName)"
  825. }
  826. }
  827. titleNavigation.text = stringName
  828. }
  829. }
  830. }
  831. titleNavigation.textColor = .white
  832. titleNavigation.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  833. navigationItem.titleView = viewAppBar
  834. titleText = titleNavigation.text
  835. } else {
  836. searchBar = UISearchBar()
  837. searchBar.autocapitalizationType = .none
  838. searchBar.delegate = self
  839. searchBar.searchTextField.tintColor = .mainColor
  840. searchBar.searchTextField.textColor = .mainColor
  841. searchBar.showsCancelButton = false
  842. // searchBar.setMagnifyingGlassColorTo(color: .white)
  843. // searchBar.updateHeight(height: 36, radius: 18)
  844. searchBar.setImage(UIImage(), for: .search, state: .normal)
  845. searchBar.setPositionAdjustment(UIOffset(horizontal: 10, vertical: 0), for: .search)
  846. searchBar.setCustomBackgroundImage(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!)
  847. navigationItem.titleView = searchBar
  848. self.definesPresentationContext = true
  849. }
  850. if copySession || forwardSession || deleteSession || isSearching {
  851. navigationItem.hidesBackButton = true
  852. navigationController?.interactivePopGestureRecognizer?.isEnabled = false
  853. } else {
  854. navigationItem.hidesBackButton = false
  855. navigationController?.interactivePopGestureRecognizer?.isEnabled = true
  856. }
  857. viewAppBar.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(seeProfileTapped)))
  858. }
  859. private func getDataProfile(fPin: String) {
  860. 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)'"
  861. if (isContactCenter && isRequestContactCenter) {
  862. query = "SELECT group_id, f_name, official, image_id FROM GROUPZ where group_type = 1 AND official = 1"
  863. }
  864. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  865. do {
  866. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  867. if cursorData.next() {
  868. dataPerson["f_pin"] = cursorData.string(forColumnIndex: 0)
  869. dataPerson["name"] = cursorData.string(forColumnIndex: 1)?.trimmingCharacters(in: .whitespaces)
  870. dataPerson["picture"] = cursorData.string(forColumnIndex: 3)
  871. dataPerson["isOfficial"] = cursorData.string(forColumnIndex: 2)
  872. if isContactCenter && isRequestContactCenter {
  873. dataPerson["user_type"] = "0"
  874. } else {
  875. dataPerson["user_type"] = cursorData.string(forColumnIndex: 6)
  876. }
  877. } else {
  878. dataPerson["f_pin"] = "-999"
  879. dataPerson["name"] = "Bot"
  880. dataPerson["picture"] = ""
  881. dataPerson["isOfficial"] = ""
  882. dataPerson["deviceId"] = ""
  883. dataPerson["isOffline"] = "0"
  884. dataPerson["user_type"] = "0"
  885. }
  886. cursorData.close()
  887. }
  888. } catch {
  889. rollback.pointee = true
  890. print("Access database error: \(error.localizedDescription)")
  891. }
  892. })
  893. }
  894. private func addDataMessage() {
  895. multipleOffsetUp += 1
  896. 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"
  897. 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 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"
  898. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  899. do {
  900. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  901. var tempData: [[String: Any?]] = []
  902. while cursorData.next() {
  903. var row: [String: Any?] = [:]
  904. row["message_id"] = cursorData.string(forColumnIndex: 0)
  905. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  906. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  907. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  908. row["server_date"] = cursorData.string(forColumnIndex: 4)
  909. row["status"] = cursorData.string(forColumnIndex: 5)
  910. row["message_text"] = cursorData.string(forColumnIndex: 6)
  911. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  912. row["video_id"] = cursorData.string(forColumnIndex: 8)
  913. row["image_id"] = cursorData.string(forColumnIndex: 9)
  914. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  915. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  916. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  917. row["file_id"] = cursorData.string(forColumnIndex: 13)
  918. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  919. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  920. row["lock"] = cursorData.string(forColumnIndex: 16)
  921. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  922. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  923. row["credential"] = cursorData.string(forColumnIndex: 19)
  924. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  925. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  926. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  927. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  928. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  929. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  930. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
  931. while cursorStatus.next() {
  932. row["status"] = cursorStatus.string(forColumnIndex: 0)
  933. }
  934. cursorStatus.close()
  935. }
  936. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  937. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  938. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  939. if let dirPath = paths.first {
  940. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  941. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  942. if ((row["video_id"] as? String ?? "") != "") {
  943. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
  944. row["progress"] = 100.0
  945. } else {
  946. row["progress"] = 0.0
  947. }
  948. } else {
  949. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent){
  950. row["progress"] = 100.0
  951. } else {
  952. row["progress"] = 0.0
  953. }
  954. }
  955. }
  956. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  957. row["isSelected"] = false
  958. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  959. let idMe = User.getMyPin()!
  960. if row["f_pin"] as? String ?? "" == idMe {
  961. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  962. if second > 60 {
  963. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  964. row["lock"] = "2"
  965. row["reff_id"] = ""
  966. DispatchQueue.global().async {
  967. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  968. do {
  969. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  970. "lock" : "2"
  971. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  972. } catch {
  973. rollback.pointee = true
  974. print("Access database error: \(error.localizedDescription)")
  975. }
  976. })
  977. }
  978. } else {
  979. let second = 60 - second
  980. listTimerCredential[row["message_id"] as? String ?? ""] = second
  981. }
  982. } else {
  983. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  984. if hasMessageId != nil {
  985. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  986. if second > 60 {
  987. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  988. row["lock"] = "2"
  989. row["reff_id"] = ""
  990. DispatchQueue.global().async {
  991. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  992. do {
  993. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  994. "lock" : "2"
  995. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  996. } catch {
  997. rollback.pointee = true
  998. print("Access database error: \(error.localizedDescription)")
  999. }
  1000. })
  1001. }
  1002. } else {
  1003. let second = 60 - second
  1004. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1005. }
  1006. } else {
  1007. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1008. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1009. }
  1010. }
  1011. }
  1012. tempData.append(row)
  1013. }
  1014. if tempData.count != 0 && (dataMessages.firstIndex(where: { $0["message_id"] as? String == tempData[0]["message_id"] as? String }) == nil) {
  1015. let lastIndex = tempData.count - 1
  1016. for i in 0..<tempData.count {
  1017. dataMessages.insert(tempData[lastIndex - i], at: 0)
  1018. if dataMessages.firstIndex(where: { $0["chat_date"] as? String == tempData[lastIndex - i]["chat_date"] as? String }) != nil {
  1019. tableChatView.insertRows(at: [IndexPath(row: 0, section: currentIndexpath!.section)], with: .top)
  1020. } else {
  1021. tableChatView.insertSections(IndexSet(integer: 0), with: .top)
  1022. tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
  1023. }
  1024. }
  1025. }
  1026. cursorData.close()
  1027. gettingDataMessage = false
  1028. }
  1029. } catch {
  1030. rollback.pointee = true
  1031. print("Access database error: \(error.localizedDescription)")
  1032. }
  1033. })
  1034. }
  1035. private func getData() {
  1036. // 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"
  1037. // 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"
  1038. 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 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"
  1039. if isContactCenter {
  1040. if complaintId.isEmpty {
  1041. 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"
  1042. } else {
  1043. 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"
  1044. }
  1045. if isRequestContactCenter && !isDirectCC {
  1046. viewButton.isHidden = true
  1047. viewTextfield.isHidden = true
  1048. var row: [String: Any?] = [:]
  1049. row["f_pin"] = nil
  1050. row["message_id"] = ""
  1051. row["chat_date"] = "Today".localized()
  1052. let listStringName: [String] = ["Messaging".localized(), "Secure SMS".localized(), "VoIP Call".localized(), "Email".localized(), "Video Call".localized(), "GSM Call".localized(), "GPT Chatbot".localized(), "WhatsApp"]
  1053. var data : [CategoryCC] = []
  1054. let channels : [String] = ["0", "4", "1", "3", "2", "5", "7", "6"]
  1055. if Utils.getDefaultCC() == "No" {
  1056. let category = CategoryCC.getDatafromParent(parent: CategoryCC.default_parent)
  1057. for i in 0..<category.count {
  1058. 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"))
  1059. }
  1060. } else {
  1061. for i in 0..<listStringName.count {
  1062. data.append(CategoryCC(id: "level0_\(channels[i])", service_id: "", service_name: listStringName[i], parent: "\(i)", description: "", is_tablet: "0"))
  1063. }
  1064. row["attachment_flag"] = "503"
  1065. }
  1066. row["category_cc"] = data
  1067. dataDates.append("Today".localized())
  1068. dataMessages.append(row)
  1069. } else if isDirectCC {
  1070. dataDates.append("Today".localized())
  1071. }
  1072. }
  1073. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1074. do {
  1075. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  1076. var tempImages: [ImageGrouping] = []
  1077. while cursorData.next() {
  1078. var row: [String: Any?] = [:]
  1079. row["message_id"] = cursorData.string(forColumnIndex: 0)
  1080. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  1081. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  1082. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  1083. row["server_date"] = cursorData.string(forColumnIndex: 4)
  1084. row["status"] = cursorData.string(forColumnIndex: 5)
  1085. row["message_text"] = cursorData.string(forColumnIndex: 6)
  1086. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  1087. row["video_id"] = cursorData.string(forColumnIndex: 8)
  1088. row["image_id"] = cursorData.string(forColumnIndex: 9)
  1089. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  1090. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  1091. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  1092. row["file_id"] = cursorData.string(forColumnIndex: 13)
  1093. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  1094. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  1095. row["lock"] = cursorData.string(forColumnIndex: 16)
  1096. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  1097. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  1098. row["credential"] = cursorData.string(forColumnIndex: 19)
  1099. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  1100. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  1101. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  1102. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  1103. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  1104. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  1105. if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status FROM MESSAGE_STATUS WHERE message_id='\(row["message_id"] as? String ?? "")'") {
  1106. while cursorStatus.next() {
  1107. row["status"] = cursorStatus.string(forColumnIndex: 0)
  1108. }
  1109. cursorStatus.close()
  1110. }
  1111. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  1112. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  1113. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  1114. if let dirPath = paths.first {
  1115. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["video_id"] as? String ?? "")
  1116. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(row["file_id"] as? String ?? "")
  1117. if ((row["video_id"] as? String ?? "") != "") {
  1118. if FileManager.default.fileExists(atPath: videoURL.path) || FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent){
  1119. row["progress"] = 100.0
  1120. } else {
  1121. row["progress"] = 0.0
  1122. }
  1123. } else {
  1124. if FileManager.default.fileExists(atPath: fileURL.path) || FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent){
  1125. row["progress"] = 100.0
  1126. } else {
  1127. row["progress"] = 0.0
  1128. }
  1129. }
  1130. }
  1131. row["chat_date"] = chatDate(stringDate: row["server_date"] as? String ?? "")
  1132. row["isSelected"] = false
  1133. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1134. let idMe = User.getMyPin()!
  1135. if row["f_pin"] as? String ?? "" == idMe {
  1136. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(row["server_date"] as? String ?? "")!), end: Date())
  1137. if second > 60 {
  1138. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1139. row["lock"] = "2"
  1140. row["reff_id"] = ""
  1141. DispatchQueue.global().async {
  1142. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1143. do {
  1144. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1145. "lock" : "2"
  1146. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1147. } catch {
  1148. rollback.pointee = true
  1149. print("Access database error: \(error.localizedDescription)")
  1150. }
  1151. })
  1152. }
  1153. } else {
  1154. let second = 60 - second
  1155. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1156. }
  1157. } else {
  1158. let hasMessageId: String? = SecureUserDefaults.shared.value(forKey: row["message_id"] as? String ?? "") ?? nil
  1159. if hasMessageId != nil {
  1160. let second = getSecondsDifferenceFromTwoDates(start: Date.init(milliseconds: Int64(hasMessageId!)!), end: Date())
  1161. if second > 60 {
  1162. listTimerCredential[row["message_id"] as? String ?? ""] = 0
  1163. row["lock"] = "2"
  1164. row["reff_id"] = ""
  1165. DispatchQueue.global().async {
  1166. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1167. do {
  1168. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1169. "lock" : "2"
  1170. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1171. } catch {
  1172. rollback.pointee = true
  1173. print("Access database error: \(error.localizedDescription)")
  1174. }
  1175. })
  1176. }
  1177. } else {
  1178. let second = 60 - second
  1179. listTimerCredential[row["message_id"] as? String ?? ""] = second
  1180. }
  1181. } else {
  1182. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1183. listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1184. }
  1185. }
  1186. }
  1187. if (dataMessages.count == 0 || dataMessages.last!["f_pin"] as? String ?? "" == row["f_pin"] as? String ?? "") && tempImages.count <= 30 && row["image_id"] != nil && !(row["image_id"] as? String ?? "").isEmpty && (row["message_text"] as? String ?? "").isEmpty && (row["reff_id"] as? String ?? "").isEmpty && (row["credential"] as? String ?? "") != "1" && (row["read_receipts"] as? String ?? "") != "8" {
  1188. 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 {
  1189. if tempImages.count >= 4 {
  1190. groupImages[tempImages[0].messageId] = tempImages
  1191. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1192. for _ in 1..<tempImages.count {
  1193. dataMessages.remove(at: idxTemp + 1)
  1194. }
  1195. }
  1196. }
  1197. tempImages.removeAll()
  1198. }
  1199. 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: [:]))
  1200. } else if tempImages.count >= 4 {
  1201. groupImages[tempImages[0].messageId] = tempImages
  1202. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1203. for _ in 1..<tempImages.count {
  1204. dataMessages.remove(at: idxTemp + 1)
  1205. }
  1206. }
  1207. tempImages.removeAll()
  1208. } else if tempImages.count != 0 {
  1209. tempImages.removeAll()
  1210. }
  1211. dataMessages.append(row)
  1212. }
  1213. if tempImages.count >= 4 {
  1214. if tempImages.count > 30 {
  1215. tempImages.removeSubrange(30..<tempImages.count)
  1216. }
  1217. groupImages[tempImages[0].messageId] = tempImages
  1218. if let idxTemp = dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == tempImages[0].messageId }) {
  1219. for _ in 1..<tempImages.count {
  1220. dataMessages.remove(at: idxTemp + 1)
  1221. }
  1222. }
  1223. }
  1224. cursorData.close()
  1225. }
  1226. } catch {
  1227. rollback.pointee = true
  1228. print("Access database error: \(error.localizedDescription)")
  1229. }
  1230. })
  1231. }
  1232. func getSecondsDifferenceFromTwoDates(start: Date, end: Date) -> Int {
  1233. let diff = Int(end.timeIntervalSince1970 - start.timeIntervalSince1970)
  1234. let hours = diff / 3600
  1235. let seconds = (diff - hours * 3600)
  1236. return seconds
  1237. }
  1238. func chatDate(stringDate: String) -> String {
  1239. let date = Date(milliseconds: Int64(stringDate)!)
  1240. let calendar = Calendar.current
  1241. if (calendar.isDateInToday(date)) {
  1242. if !dataDates.contains("Today".localized()){
  1243. dataDates.append("Today".localized())
  1244. }
  1245. return "Today".localized()
  1246. } else {
  1247. let startOfNow = calendar.startOfDay(for: Date())
  1248. let startOfTimeStamp = calendar.startOfDay(for: date)
  1249. let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
  1250. let day = -(components.day!)
  1251. if day == 1{
  1252. if !dataDates.contains("Yesterday".localized()){
  1253. dataDates.append("Yesterday".localized())
  1254. }
  1255. return "Yesterday".localized()
  1256. } else if day < 7 {
  1257. let formatter = DateFormatter()
  1258. formatter.dateFormat = "EEEE"
  1259. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  1260. if lang == "id" {
  1261. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1262. }
  1263. if !dataDates.contains(formatter.string(from: date)){
  1264. dataDates.append(formatter.string(from: date))
  1265. }
  1266. return formatter.string(from: date)
  1267. } else {
  1268. let formatter = DateFormatter()
  1269. formatter.dateFormat = "EE, dd MMM"
  1270. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  1271. if lang == "id" {
  1272. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1273. }
  1274. let stringFormat = formatter.string(from: date as Date)
  1275. if !dataDates.contains(stringFormat){
  1276. dataDates.append(stringFormat)
  1277. }
  1278. return stringFormat
  1279. }
  1280. }
  1281. }
  1282. func updateProgress(_ data: [AnyHashable : Any]){
  1283. var isImage = false
  1284. var idx = dataMessages.lastIndex(where: { $0["video_id"] as? String == data["name"] as? String || $0["video_id"] as? String == data["video_id"] as? String })
  1285. if (idx == nil) {
  1286. idx = dataMessages.lastIndex(where: { $0["image_id"] as? String == data["name"] as? String || $0["image_id"] as? String == data["image_id"] as? String })
  1287. isImage = true
  1288. }
  1289. if (idx != nil) {
  1290. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  1291. if section == nil {
  1292. return
  1293. }
  1294. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == dataMessages[idx!]["message_id"] as? String})
  1295. if row == nil {
  1296. return
  1297. }
  1298. DispatchQueue.main.async {
  1299. let indexPath = IndexPath(row: row!, section: section!)
  1300. if(self.fakeProgMultip < self.maxFakeProgMultip){
  1301. self.fakeProgMultip = self.fakeProgMultip + 1
  1302. }
  1303. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  1304. let progress = max(data["progress"] as! Double, fakeProgress)
  1305. if(data["progress"] as! Double == 100.0){
  1306. self.fakeProgMultip = 0
  1307. }
  1308. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1309. for view in cell.contentView.subviews {
  1310. if !(view is UITextView) && !(view is UIImageView) {
  1311. for viewInContainer in view.subviews {
  1312. if viewInContainer is UIImageView {
  1313. if viewInContainer.subviews.count == 0 {
  1314. return
  1315. }
  1316. var containerView : UIView?
  1317. if (isImage) {
  1318. containerView = viewInContainer.subviews[0]
  1319. } else if viewInContainer.subviews.count > 1 {
  1320. containerView = viewInContainer.subviews[1]
  1321. }
  1322. if let loading = containerView?.layer.sublayers?[1] as? CAShapeLayer {
  1323. loading.strokeEnd = CGFloat(progress / 100)
  1324. if (progress == 100.0) {
  1325. self.dataMessages[idx!]["progress"] = progress
  1326. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1327. }
  1328. }
  1329. }
  1330. }
  1331. }
  1332. }
  1333. }
  1334. }
  1335. } else {
  1336. idx = dataMessages.lastIndex(where: { $0["file_id"] as? String == data["name"] as? String || $0["file_id"] as? String == data["file_id"] as? String })
  1337. if (idx != nil) {
  1338. let section = dataDates.firstIndex(of: dataMessages[idx!]["chat_date"] as? String ?? "")
  1339. if section == nil {
  1340. return
  1341. }
  1342. let row = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section!]}).firstIndex(where: { $0["message_id"] as? String == dataMessages[idx!]["message_id"] as? String})
  1343. if row == nil {
  1344. return
  1345. }
  1346. DispatchQueue.main.async {
  1347. let indexPath = IndexPath(row: row!, section: section!)
  1348. if(self.fakeProgMultip < self.maxFakeProgMultip){
  1349. self.fakeProgMultip = self.fakeProgMultip + 1
  1350. }
  1351. let fakeProgress = Double(self.fakeProgMultip) * (100.0 / Double(self.maxFakeProgMultip))
  1352. let progress = max(data["progress"] as! Double, fakeProgress)
  1353. if(data["progress"] as! Double == 100.0){
  1354. self.fakeProgMultip = 0
  1355. }
  1356. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  1357. for view in cell.contentView.subviews {
  1358. if !(view is UITextView) && !(view is UIImageView) {
  1359. for viewSubviews in view.subviews {
  1360. if !(viewSubviews is UITextView) {
  1361. for viewInContainer in viewSubviews.subviews {
  1362. if !(viewInContainer is UITextView) && !(viewInContainer is UIImageView) {
  1363. if let cont = viewInContainer.layer.sublayers {
  1364. if cont.count < 2 {
  1365. return
  1366. }
  1367. }
  1368. if let layers = viewInContainer.layer.sublayers {
  1369. if let loading = layers [1] as? CAShapeLayer {
  1370. loading.strokeEnd = CGFloat(progress / 100)
  1371. if (progress == 100.0) {
  1372. self.dataMessages[idx!]["progress"] = progress
  1373. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  1374. }
  1375. }
  1376. }
  1377. }
  1378. }
  1379. }
  1380. }
  1381. }
  1382. }
  1383. }
  1384. }
  1385. }
  1386. }
  1387. }
  1388. @objc func onUploadChat(notification: NSNotification) {
  1389. let data:[AnyHashable : Any] = notification.userInfo!
  1390. updateProgress(data)
  1391. }
  1392. @objc func onReceiveMessage(notification: NSNotification) {
  1393. DispatchQueue.main.async { [self] in
  1394. let data:[AnyHashable : Any] = notification.userInfo!
  1395. if let dataMessage = data["message"] as? TMessage {
  1396. let chatData = dataMessage.mBodies
  1397. if (dataMessage.getCode() == CoreMessage_TMessageCode.PUSH_MEMBER_ROOM_CONTACT_CENTER && isContactCenter) {
  1398. let data = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  1399. if !data.isEmpty {
  1400. if let jsonArray = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
  1401. var members = ""
  1402. let idMe = User.getMyPin()!
  1403. var user : [User] = []
  1404. for json in jsonArray {
  1405. if "\(json)" != idMe {
  1406. if members.isEmpty {
  1407. members = "\(json)"
  1408. } else {
  1409. members += ",\(json)"
  1410. }
  1411. if let userData = User.getData(pin: "\(json)") {
  1412. user.append(userData)
  1413. } else {
  1414. Nexilis.addFriend (fpin: "\(json)") { result in
  1415. DispatchQueue.main.async {
  1416. if result {
  1417. let userData = User.getData(pin: "\(json)")!
  1418. user.append(userData)
  1419. } else {
  1420. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  1421. imageView.tintColor = .white
  1422. 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)
  1423. banner.show()
  1424. }
  1425. }
  1426. }
  1427. }
  1428. }
  1429. }
  1430. self.users = user
  1431. self.fPinContacCenter = members
  1432. self.changeAppBar()
  1433. SecureUserDefaults.shared.set(members, forKey: "inEditorPersonal")
  1434. }
  1435. }
  1436. } else if (dataMessage.getCode() == CoreMessage_TMessageCode.ACCEPT_CALL_CENTER) {
  1437. if !self.isRequestContactCenter || !isContactCenter {
  1438. return
  1439. }
  1440. SecureUserDefaults.shared.set(dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN), forKey: "inEditorPersonal")
  1441. let date = Date()
  1442. let formatter = DateFormatter()
  1443. formatter.dateFormat = "HH:mm"
  1444. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  1445. self.fPinContacCenter = dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN)
  1446. self.complaintId = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  1447. self.channelContactCenter = dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL)
  1448. var row: [String: Any?] = [:]
  1449. 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()
  1450. row["message_id"] = ""
  1451. 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()
  1452. row["chat_date"] = "Today".localized()
  1453. self.dataMessages.append(row)
  1454. self.users.append(User.getData(pin: dataMessage.getBody(key: CoreMessage_TMessageKey.F_PIN))!)
  1455. self.changeAppBar()
  1456. self.setRightButtonItem()
  1457. self.dateStartCC = "\(Date().currentTimeMillis())"
  1458. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  1459. self.tableChatView.scrollToBottom()
  1460. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  1461. if dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL) != "0" {
  1462. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: "startTimeCC")
  1463. SecureUserDefaults.shared.set(dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL), forKey: "channelCC")
  1464. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  1465. self.dismiss(animated: true, completion: {
  1466. self.removeAllObjectBeforeDismissVC()
  1467. })
  1468. })
  1469. } else {
  1470. viewButton.isHidden = false
  1471. viewTextfield.isHidden = false
  1472. }
  1473. } 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 {
  1474. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1475. if onGoingCC.isEmpty || !isContactCenter {
  1476. return
  1477. }
  1478. let requester = onGoingCC.components(separatedBy: ",")[0]
  1479. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1480. let fPin = dataMessage.getCode() == CoreMessage_TMessageCode.END_CALL_CENTER ? chatData[CoreMessage_TMessageKey.F_PIN] : dataMessage.getPIN()
  1481. if fPin == officer || fPin == requester {
  1482. DispatchQueue.global().async {
  1483. let date = "\(Date().currentTimeMillis())"
  1484. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1485. do {
  1486. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  1487. "type" : self.channelContactCenter,
  1488. "title" : "Contact Center".localized(),
  1489. "time" : self.dateStartCC,
  1490. "f_pin" : officer,
  1491. "data" : self.complaintId,
  1492. "time_end" : date,
  1493. "complaint_id" : self.complaintId,
  1494. "members" : "",
  1495. "requester": requester
  1496. ], replace: true)
  1497. } catch {
  1498. rollback.pointee = true
  1499. print("Access database error: \(error.localizedDescription)")
  1500. }
  1501. })
  1502. }
  1503. self.dismissKeyboard()
  1504. self.disableEditor()
  1505. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  1506. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  1507. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  1508. DispatchQueue.main.async {
  1509. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1510. imageView.tintColor = .white
  1511. 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)
  1512. banner.show()
  1513. }
  1514. timeoutCC.invalidate()
  1515. DispatchQueue.main.asyncAfter(deadline: .now() + 1.5, execute: {
  1516. if !(self.presentedViewController is EditorPersonal) {
  1517. self.dismiss(animated: true, completion: {
  1518. self.removeAllObjectBeforeDismissVC()
  1519. })
  1520. }
  1521. DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
  1522. self.dismiss(animated: true, completion: {
  1523. self.removeAllObjectBeforeDismissVC()
  1524. })
  1525. }
  1526. })
  1527. } else {
  1528. var members = ""
  1529. self.users.removeAll(where: {$0.pin == fPin})
  1530. for user in self.users {
  1531. if members.isEmpty {
  1532. members = "\(user.pin)"
  1533. } else {
  1534. members = ",\(user.pin)"
  1535. }
  1536. }
  1537. SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
  1538. self.fPinContacCenter = members
  1539. self.changeAppBar()
  1540. }
  1541. }
  1542. 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]) {
  1543. if chatData[CoreMessage_TMessageKey.F_PIN] == nil {
  1544. return
  1545. }
  1546. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]})
  1547. if idx != nil {
  1548. self.dataMessages[idx!][TypeDataMessage.message_text] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1549. self.dataMessages[idx!][TypeDataMessage.last_edit] = Int64(chatData[CoreMessage_TMessageKey.LAST_EDIT]!)
  1550. self.dataMessages[idx!][TypeDataMessage.status] = chatData[CoreMessage_TMessageKey.STATUS]
  1551. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1552. 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 })
  1553. if row != nil && section != nil {
  1554. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1555. }
  1556. return
  1557. }
  1558. var row: [String: Any?] = [:]
  1559. row["message_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_ID]
  1560. row["f_pin"] = chatData[CoreMessage_TMessageKey.F_PIN]
  1561. row["l_pin"] = chatData[CoreMessage_TMessageKey.L_PIN]
  1562. row["message_scope_id"] = chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]
  1563. row["server_date"] = chatData[CoreMessage_TMessageKey.SERVER_DATE]
  1564. row["status"] = chatData[CoreMessage_TMessageKey.STATUS]
  1565. row["message_text"] = chatData[CoreMessage_TMessageKey.MESSAGE_TEXT]
  1566. if (chatData.keys.contains(CoreMessage_TMessageKey.AUDIO_ID)) {
  1567. row["audio_id"] = chatData[CoreMessage_TMessageKey.AUDIO_ID]
  1568. } else {
  1569. row["audio_id"] = ""
  1570. }
  1571. if (chatData.keys.contains(CoreMessage_TMessageKey.GIF_ID)) {
  1572. row["gif_id"] = chatData[CoreMessage_TMessageKey.GIF_ID]
  1573. } else {
  1574. row["gif_id"] = ""
  1575. }
  1576. if (chatData.keys.contains(CoreMessage_TMessageKey.VIDEO_ID)) {
  1577. row["video_id"] = chatData[CoreMessage_TMessageKey.VIDEO_ID]
  1578. } else {
  1579. row["video_id"] = ""
  1580. }
  1581. if (chatData.keys.contains(CoreMessage_TMessageKey.IMAGE_ID)) {
  1582. row["image_id"] = chatData[CoreMessage_TMessageKey.IMAGE_ID]
  1583. } else {
  1584. row["image_id"] = ""
  1585. }
  1586. if (chatData.keys.contains(CoreMessage_TMessageKey.THUMB_ID)) {
  1587. row["thumb_id"] = chatData[CoreMessage_TMessageKey.THUMB_ID]
  1588. } else {
  1589. row["thumb_id"] = ""
  1590. }
  1591. if (chatData.keys.contains(CoreMessage_TMessageKey.READ_RECEIPTS)) {
  1592. row["read_receipts"] = chatData[CoreMessage_TMessageKey.READ_RECEIPTS]
  1593. } else {
  1594. row["read_receipts"] = ""
  1595. }
  1596. if (chatData.keys.contains(CoreMessage_TMessageKey.CREDENTIAL)) {
  1597. row["credential"] = chatData[CoreMessage_TMessageKey.CREDENTIAL]
  1598. } else {
  1599. row["credential"] = ""
  1600. }
  1601. row["chat_id"] = ""
  1602. if (chatData.keys.contains(CoreMessage_TMessageKey.FILE_ID)) {
  1603. row["file_id"] = chatData[CoreMessage_TMessageKey.FILE_ID]
  1604. } else {
  1605. row["file_id"] = ""
  1606. }
  1607. row["progress"] = 0.0
  1608. row["attachment_flag"] = chatData[CoreMessage_TMessageKey.ATTACHMENT_FLAG]
  1609. row["reff_id"] = chatData[CoreMessage_TMessageKey.REF_ID] ?? ""
  1610. row["lock"] = ""
  1611. row["is_stared"] = "0"
  1612. row[TypeDataMessage.is_forwarded] = Int(chatData[CoreMessage_TMessageKey.IS_FORWARDED_MESSAGE] ?? "0")
  1613. row["isSelected"] = false
  1614. if !self.dataDates.contains("Today".localized()) {
  1615. self.dataDates.append("Today".localized())
  1616. self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
  1617. }
  1618. row["chat_date"] = "Today".localized()
  1619. row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
  1620. self.counter += 1
  1621. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1622. self.listTimerCredential[row["message_id"] as? String ?? ""] = 60
  1623. }
  1624. self.dataMessages.append(row)
  1625. 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)
  1626. if row["credential"] != nil && row["credential"] as? String ?? "" == "1" {
  1627. var timer = Timer()
  1628. var minute = 60
  1629. self.timerCredential[row["message_id"] as? String ?? ""] = timer
  1630. SecureUserDefaults.shared.set("\(Date().currentTimeMillis())", forKey: row["message_id"] as? String ?? "")
  1631. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  1632. minute -= 1
  1633. self.listTimerCredential[row["message_id"] as? String ?? ""] = minute
  1634. if minute == 0 {
  1635. timer.invalidate()
  1636. self.listTimerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1637. self.timerCredential.removeValue(forKey: row["message_id"] as? String ?? "")
  1638. SecureUserDefaults.shared.removeValue(forKey: row["message_id"] as? String ?? "")
  1639. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == row["message_id"] as? String})
  1640. if idx != nil {
  1641. self.dataMessages[idx!]["lock"] = "2"
  1642. self.dataMessages[idx!]["reff_id"] = ""
  1643. }
  1644. DispatchQueue.global().async {
  1645. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1646. do {
  1647. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  1648. "lock" : "2"
  1649. ], _where: "message_id = '\(row["message_id"] as? String ?? "")'")
  1650. } catch {
  1651. rollback.pointee = true
  1652. print("Access database error: \(error.localizedDescription)")
  1653. }
  1654. })
  1655. }
  1656. }
  1657. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  1658. 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})
  1659. if row != nil && section != nil{
  1660. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1661. }
  1662. })
  1663. }
  1664. if self.isContactCenter {
  1665. let idMe = User.getMyPin()!
  1666. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1667. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1668. if officer == idMe {
  1669. self.timeoutCC.invalidate()
  1670. } else if !fromVCAC {
  1671. if !self.showToast30s {
  1672. self.view.makeToast("Please reply within 30 seconds so the call center session doesn't end.".localized(), duration: 3)
  1673. sendTyping(l_pin: fPinContacCenter, isTyping: true)
  1674. self.showToast30s = true
  1675. }
  1676. }
  1677. }
  1678. if chatData[CoreMessage_TMessageKey.FORMAT] == "1" {
  1679. 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]!)
  1680. self.tableChatView.scrollToBottom()
  1681. } else if self.currentIndexpath?.row == (self.dataMessages.count - 2) {
  1682. if (self.viewIfLoaded?.window != nil) {
  1683. 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]!)
  1684. }
  1685. self.tableChatView.scrollToBottom()
  1686. 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) {
  1687. self.counter = 0
  1688. self.updateCounter(counter: self.counter)
  1689. }
  1690. let lastMarkerCounter = markerCounter
  1691. if self.markerCounter != nil {
  1692. self.markerCounter = nil
  1693. }
  1694. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  1695. if indexMessage != nil {
  1696. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1697. 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 })
  1698. if row != nil && section != nil {
  1699. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1700. }
  1701. }
  1702. }
  1703. else if self.currentIndexpath == nil {
  1704. self.counter = 0
  1705. self.updateCounter(counter: self.counter)
  1706. if (self.viewIfLoaded?.window != nil) {
  1707. 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]!)
  1708. }
  1709. }
  1710. else if self.counter != 0 {
  1711. if !self.indicatorCounterBSTB.isDescendant(of: self.view) && self.buttonScrollToBottom.isDescendant(of: self.view) {
  1712. self.markerCounter = row["message_id"] as? String
  1713. self.addCounterAtButttonScrollToBottom()
  1714. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.markerCounter })
  1715. if indexMessage != nil {
  1716. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  1717. 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 })
  1718. if row != nil && section != nil {
  1719. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1720. }
  1721. }
  1722. } else if self.indicatorCounterBSTB.isDescendant(of: self.view) {
  1723. self.labelCounter.text = "\(self.counter)"
  1724. }
  1725. }
  1726. }
  1727. } else if !self.isContactCenter {
  1728. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1729. }
  1730. }
  1731. }
  1732. private func disableEditor() {
  1733. view.addSubview(containerAction)
  1734. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1735. NSLayoutConstraint.activate([
  1736. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1737. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1738. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1739. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1740. ])
  1741. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1742. let labelDisable = UILabel()
  1743. containerAction.addSubview(labelDisable)
  1744. labelDisable.translatesAutoresizingMaskIntoConstraints = false
  1745. NSLayoutConstraint.activate([
  1746. labelDisable.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1747. labelDisable.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1748. ])
  1749. labelDisable.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1750. labelDisable.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1751. labelDisable.text = "Call center session is over".localized()
  1752. }
  1753. @objc func onStatusChat(notification: NSNotification) {
  1754. DispatchQueue.main.async {
  1755. let data:[AnyHashable : Any] = notification.userInfo!
  1756. if let dataMessage = data["message"] as? TMessage {
  1757. let chatData = dataMessage.mBodies
  1758. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1759. let requester = onGoingCC.components(separatedBy: ",")[0]
  1760. let idMe = User.getMyPin()!
  1761. 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 {
  1762. if (chatData.keys.contains(CoreMessage_TMessageKey.MESSAGE_ID) && !(chatData[CoreMessage_TMessageKey.MESSAGE_ID]!).contains("-2,")) {
  1763. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! })
  1764. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) }) {
  1765. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == chatData[CoreMessage_TMessageKey.MESSAGE_ID]! }) {
  1766. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1767. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1768. }
  1769. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1770. }
  1771. if (idx != nil) {
  1772. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1773. self.updateStatusDelete(idx: idx, chatData: chatData)
  1774. } else {
  1775. self.updateStatusMessage(idx: idx, chatData: chatData)
  1776. }
  1777. }
  1778. }
  1779. else if (chatData.keys.contains("message_id")) {
  1780. var idMessage = dataMessage.getBody(key: "message_id")
  1781. if idMessage.contains("'") {
  1782. idMessage = idMessage.replacingOccurrences(of: "'", with: "")
  1783. }
  1784. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == idMessage })
  1785. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == idMessage }) }) {
  1786. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == idMessage }) {
  1787. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1788. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1789. }
  1790. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1791. }
  1792. if (idx != nil) {
  1793. if (chatData[CoreMessage_TMessageKey.DELETE_MESSAGE_FLAG] == "1") {
  1794. self.updateStatusDelete(idx: idx, chatData: chatData)
  1795. } else {
  1796. self.updateStatusMessage(idx: idx, chatData: chatData)
  1797. }
  1798. }
  1799. }
  1800. else {
  1801. let listMessageId = chatData[CoreMessage_TMessageKey.MESSAGE_ID]!.split(separator: ",")
  1802. for i in 1..<listMessageId.count {
  1803. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == listMessageId[i] })
  1804. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == listMessageId[i] }) }) {
  1805. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == listMessageId[i] }) {
  1806. self.groupImages[idxMessageIdParent].value[idxInImages].status = chatData[CoreMessage_TMessageKey.STATUS]!
  1807. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1808. }
  1809. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1810. }
  1811. if (idx != nil) {
  1812. self.updateStatusMessage(idx: idx, chatData: chatData)
  1813. }
  1814. }
  1815. }
  1816. }
  1817. }
  1818. }
  1819. }
  1820. @objc func onFailedSendMessage(notification: NSNotification) {
  1821. DispatchQueue.main.async {
  1822. let data:[AnyHashable : Any] = notification.userInfo!
  1823. let messageId = data["message_id"] as? String ?? ""
  1824. let status = data["status"] as? String ?? ""
  1825. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  1826. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  1827. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  1828. self.groupImages[idxMessageIdParent].value[idxInImages].status = status
  1829. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage["status"] = status
  1830. }
  1831. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  1832. }
  1833. if (idx != nil) {
  1834. do {
  1835. self.dataMessages[idx!]["status"] = status
  1836. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1837. 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 })
  1838. if row != nil && section != nil {
  1839. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1840. }
  1841. } catch {
  1842. }
  1843. }
  1844. }
  1845. }
  1846. @objc func onRefreshCallLog(notification: NSNotification) {
  1847. DispatchQueue.main.async {
  1848. let data:[AnyHashable : Any] = notification.userInfo!
  1849. let messageId = data["message_id"] as? String ?? ""
  1850. let pin = data["pin"] as? String ?? ""
  1851. if pin == self.dataPerson["f_pin"]!! {
  1852. self.appendNewMessage(messageId: messageId)
  1853. }
  1854. }
  1855. }
  1856. private func appendNewMessage(messageId: String) {
  1857. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  1858. 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 from MESSAGE where message_id = '\(messageId)'"), cursorData.next() {
  1859. var row: [String: Any?] = [:]
  1860. row["message_id"] = cursorData.string(forColumnIndex: 0)
  1861. row["f_pin"] = cursorData.string(forColumnIndex: 1)
  1862. row["l_pin"] = cursorData.string(forColumnIndex: 2)
  1863. row["message_scope_id"] = cursorData.string(forColumnIndex: 3)
  1864. row["server_date"] = cursorData.string(forColumnIndex: 4)
  1865. row["status"] = cursorData.string(forColumnIndex: 5)
  1866. row["message_text"] = cursorData.string(forColumnIndex: 6)
  1867. row["audio_id"] = cursorData.string(forColumnIndex: 7)
  1868. row["video_id"] = cursorData.string(forColumnIndex: 8)
  1869. row["image_id"] = cursorData.string(forColumnIndex: 9)
  1870. row["thumb_id"] = cursorData.string(forColumnIndex: 10)
  1871. row["read_receipts"] = cursorData.string(forColumnIndex: 11)
  1872. row["chat_id"] = cursorData.string(forColumnIndex: 12)
  1873. row["file_id"] = cursorData.string(forColumnIndex: 13)
  1874. row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
  1875. row["reff_id"] = cursorData.string(forColumnIndex: 15)
  1876. row["lock"] = cursorData.string(forColumnIndex: 16)
  1877. row["is_stared"] = cursorData.string(forColumnIndex: 17)
  1878. row["blog_id"] = cursorData.string(forColumnIndex: 18)
  1879. row["credential"] = cursorData.string(forColumnIndex: 19)
  1880. row[TypeDataMessage.is_call_center] = cursorData.string(forColumnIndex: 20)
  1881. row[TypeDataMessage.call_center_id] = cursorData.string(forColumnIndex: 21)
  1882. row[TypeDataMessage.opposite_pin] = cursorData.string(forColumnIndex: 22)
  1883. row[TypeDataMessage.last_edit] = cursorData.longLongInt(forColumnIndex: 23)
  1884. row[TypeDataMessage.gif_id] = cursorData.string(forColumnIndex: 24)
  1885. row[TypeDataMessage.is_forwarded] = Int(cursorData.int(forColumnIndex: 25))
  1886. row["progress"] = 0.0
  1887. row["isSelected"] = false
  1888. if !self.dataDates.contains("Today".localized()) {
  1889. self.dataDates.append("Today".localized())
  1890. self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
  1891. }
  1892. row["chat_date"] = "Today".localized()
  1893. dataMessages.append(row)
  1894. 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)
  1895. cursorData.close()
  1896. }
  1897. })
  1898. }
  1899. private func updateStatusDelete(idx: Int?, chatData: [String: String]) {
  1900. do {
  1901. if self.dataMessages[idx!]["lock"] != nil && self.dataMessages[idx!]["lock"] as? String ?? "" == "1" {
  1902. return
  1903. }
  1904. self.dataMessages[idx!]["lock"] = "1"
  1905. self.dataMessages[idx!]["reff_id"] = ""
  1906. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1907. 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 })
  1908. if row != nil && section != nil {
  1909. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1910. }
  1911. if self.listTimerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""] != nil {
  1912. self.listTimerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1913. self.timerCredential[self.dataMessages[idx!]["message_id"] as? String ?? ""]?.invalidate()
  1914. self.timerCredential.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1915. SecureUserDefaults.shared.removeValue(forKey: self.dataMessages[idx!]["message_id"] as? String ?? "")
  1916. }
  1917. if self.reffId != nil && self.reffId == chatData["message_id"]! {
  1918. self.deleteReplyView()
  1919. }
  1920. } catch {
  1921. }
  1922. }
  1923. private func updateStatusMessage(idx: Int?, chatData: [String: String]) {
  1924. do {
  1925. if Int(self.dataMessages[idx!]["status"] as? String ?? "")! > Int(chatData[CoreMessage_TMessageKey.STATUS]!)! {
  1926. return
  1927. }
  1928. self.dataMessages[idx!]["status"] = chatData[CoreMessage_TMessageKey.STATUS]!
  1929. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  1930. 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 })
  1931. if row != nil && section != nil {
  1932. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  1933. }
  1934. } catch {
  1935. }
  1936. }
  1937. @objc func onTyping(notification: NSNotification) {
  1938. DispatchQueue.main.async { [self] in
  1939. let data:[AnyHashable : Any] = notification.userInfo!
  1940. let message: TMessage = data["message"] as! TMessage
  1941. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1942. if !onGoingCC.isEmpty {
  1943. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1944. if message.getBody(key: CoreMessage_TMessageKey.F_PIN) != officer {
  1945. //print("RESET TIMER")
  1946. // timeoutCC.invalidate()
  1947. // timeoutCC = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: {_ in
  1948. // let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1949. // imageView.tintColor = .white
  1950. // 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)
  1951. // banner.show()
  1952. // self.endCallCenter()
  1953. // })
  1954. }
  1955. } else {
  1956. }
  1957. }
  1958. }
  1959. @objc func onUnfriend(notification: NSNotification) {
  1960. let data:[AnyHashable : Any] = notification.userInfo!
  1961. DispatchQueue.main.async { [self] in
  1962. if data["state"] as! Int == 99 && (data["message"] as? String ?? "").components(separatedBy: ",")[0] == "delete_buddy" {
  1963. removed = true
  1964. if forwardSession || copySession || deleteSession || isSearching {
  1965. cancelAction()
  1966. }
  1967. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {[self] in
  1968. navigationItem.rightBarButtonItem = nil
  1969. navigationItem.rightBarButtonItems = nil
  1970. changeAppBar()
  1971. view.addSubview(containerAction)
  1972. containerAction.translatesAutoresizingMaskIntoConstraints = false
  1973. NSLayoutConstraint.activate([
  1974. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  1975. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  1976. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  1977. containerAction.heightAnchor.constraint(equalToConstant: 120)
  1978. ])
  1979. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  1980. let labelUnfriend = UILabel()
  1981. containerAction.addSubview(labelUnfriend)
  1982. labelUnfriend.translatesAutoresizingMaskIntoConstraints = false
  1983. NSLayoutConstraint.activate([
  1984. labelUnfriend.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  1985. labelUnfriend.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  1986. ])
  1987. labelUnfriend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  1988. labelUnfriend.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  1989. labelUnfriend.text = "You have unfriended this user".localized()
  1990. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  1991. if contactChatNav.viewIfLoaded?.window != nil {
  1992. contactChatNav.dismiss(animated: true)
  1993. }
  1994. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  1995. if self.fromNotification {
  1996. self.didTapExit()
  1997. } else {
  1998. self.navigationController?.popViewController(animated: true)
  1999. }
  2000. })
  2001. })
  2002. } else if data["state"] as! Int == 01 {
  2003. if let dataMessage = try! JSONSerialization.jsonObject(with: (data["message"] as? String ?? "").data(using: .utf8)!, options: []) as? [String: String] {
  2004. if(dataMessage["l_pin"] == dataPerson["f_pin"]!){
  2005. if let block = dataMessage["block"] {
  2006. if(block == "-1"){
  2007. dismissKeyboard()
  2008. }
  2009. blockedView(blocked: block)
  2010. if contactChatNav.viewIfLoaded?.window != nil {
  2011. contactChatNav.dismiss(animated: true)
  2012. }
  2013. cancelAction()
  2014. }
  2015. }
  2016. setRightButtonItem()
  2017. }
  2018. }
  2019. }
  2020. }
  2021. func blockedView(blocked: String) {
  2022. dismissKeyboard()
  2023. view.addSubview(containerAction)
  2024. containerAction.translatesAutoresizingMaskIntoConstraints = false
  2025. NSLayoutConstraint.activate([
  2026. containerAction.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  2027. containerAction.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  2028. containerAction.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  2029. containerAction.heightAnchor.constraint(equalToConstant: 120)
  2030. ])
  2031. containerAction.backgroundColor = .secondaryColor.withAlphaComponent(0.8)
  2032. let labelBlocked = UILabel()
  2033. containerAction.addSubview(labelBlocked)
  2034. labelBlocked.translatesAutoresizingMaskIntoConstraints = false
  2035. NSLayoutConstraint.activate([
  2036. labelBlocked.centerYAnchor.constraint(equalTo: containerAction.centerYAnchor),
  2037. labelBlocked.centerXAnchor.constraint(equalTo: containerAction.centerXAnchor),
  2038. ])
  2039. labelBlocked.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  2040. labelBlocked.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  2041. if blocked == "1" {
  2042. labelBlocked.text = "You blocked this user".localized()
  2043. } else {
  2044. labelBlocked.text = "You have been blocked by this user".localized()
  2045. }
  2046. }
  2047. @objc func seeProfileTapped() {
  2048. if dataPerson["f_pin"] == "-999" || dataPerson["isOfficial"] == "1" || removed || copySession || forwardSession || deleteSession || isContactCenter {
  2049. return
  2050. }
  2051. dismissKeyboard()
  2052. let controller = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "profileView") as! ProfileViewController
  2053. controller.data = dataPerson["f_pin"]!!
  2054. controller.checkReadMessage = {
  2055. self.dataPerson.removeAll()
  2056. self.getDataProfile(fPin: self.unique_l_pin)
  2057. self.changeAppBar()
  2058. if self.currentIndexpath == nil {
  2059. var listData = self.dataMessages
  2060. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2061. if listData.count != 0 && !self.isContactCenter {
  2062. let idMe = User.getMyPin() as String?
  2063. for i in 0...listData.count - 1 {
  2064. if listData[i]["f_pin"] as? String != idMe {
  2065. self.sendReadMessageStatus(chat_id: "", f_pin: self.dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: listData[i]["message_id"] as? String ?? "")
  2066. }
  2067. }
  2068. }
  2069. } else {
  2070. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[self.currentIndexpath!.section] })
  2071. var listData = dataMessages
  2072. listData = listData.filter({$0["status"] as? String ?? "" != "4" && $0["status"] as? String ?? "" != "8"})
  2073. if listData.count != 0 && !self.isContactCenter {
  2074. let idMe = User.getMyPin() as String?
  2075. for i in 0...listData.count - 1 {
  2076. if listData[i]["f_pin"] as? String != idMe {
  2077. self.sendReadMessageStatus(chat_id: "", f_pin: self.dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: listData[i]["message_id"] as? String ?? "")
  2078. }
  2079. }
  2080. }
  2081. }
  2082. }
  2083. navigationController?.show(controller, sender: nil)
  2084. }
  2085. @IBAction func voiceTapped(_ sender: UIButton) {
  2086. if (self.constraintBottomAttachment.constant != 0.0) {
  2087. constraintBottomAttachment.constant = 0.0
  2088. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2089. self.viewSticker.removeFromSuperview()
  2090. }
  2091. }
  2092. @IBAction func imageTapped(_ sender: UIButton) {
  2093. if (self.constraintBottomAttachment.constant != 0.0) {
  2094. constraintBottomAttachment.constant = 0.0
  2095. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2096. self.viewSticker.removeFromSuperview()
  2097. }
  2098. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2099. return
  2100. }
  2101. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  2102. if let action = self.actionImageVideo(for: "image", title: "Choose Photo".localized()) {
  2103. alertController.addAction(action)
  2104. }
  2105. if let action = self.actionImageVideo(for: "video", title: "Choose Video".localized()) {
  2106. alertController.addAction(action)
  2107. }
  2108. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  2109. self.present(alertController, animated: true)
  2110. }
  2111. private func actionImageVideo(for type: String, title: String) -> UIAlertAction? {
  2112. return UIAlertAction(title: title, style: .default) { [unowned self] _ in
  2113. switch type {
  2114. case "image":
  2115. var config = PHPickerConfiguration()
  2116. config.filter = .images
  2117. let picker = PHPickerViewController(configuration: config)
  2118. picker.delegate = self
  2119. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  2120. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  2121. }
  2122. if !isBlackCancelButton {
  2123. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2124. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2125. }
  2126. present(picker, animated: true, completion: nil)
  2127. case "video":
  2128. var config = PHPickerConfiguration()
  2129. config.filter = .videos
  2130. let picker = PHPickerViewController(configuration: config)
  2131. picker.delegate = self
  2132. if UIBarButtonItem.appearance().titleTextAttributes(for: .normal) != nil {
  2133. isBlackCancelButton = UIBarButtonItem.appearance().titleTextAttributes(for: .normal)?.values.first as! NSObject == UIColor.black
  2134. }
  2135. if !isBlackCancelButton {
  2136. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  2137. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  2138. }
  2139. present(picker, animated: true, completion: nil)
  2140. case "imageCamera":
  2141. if isContactCenter && channelContactCenter == "2" {
  2142. self.view.makeToast("You can't take photo when Video Call".localized(), duration: 3)
  2143. return
  2144. }
  2145. imageVideoPicker.present(source: .imageCamera)
  2146. case "videoCamera":
  2147. if isContactCenter && channelContactCenter == "2" {
  2148. self.view.makeToast("You can't take video when Video Call".localized(), duration: 3)
  2149. return
  2150. }
  2151. imageVideoPicker.present(source: .videoCamera)
  2152. default:
  2153. break
  2154. }
  2155. }
  2156. }
  2157. @IBAction func photoTapped(_ sender: UIButton) {
  2158. if (self.constraintBottomAttachment.constant != 0.0) {
  2159. constraintBottomAttachment.constant = 0.0
  2160. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2161. self.viewSticker.removeFromSuperview()
  2162. }
  2163. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2164. return
  2165. }
  2166. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  2167. if let action = self.actionImageVideo(for: "imageCamera", title: "Take Photo".localized()) {
  2168. alertController.addAction(action)
  2169. }
  2170. if let action = self.actionImageVideo(for: "videoCamera", title: "Take Video".localized()) {
  2171. alertController.addAction(action)
  2172. }
  2173. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  2174. self.present(alertController, animated: true)
  2175. }
  2176. @IBAction func stickerTapped(_ sender: UIButton) {
  2177. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2178. return
  2179. }
  2180. if textFieldSend.isFirstResponder {
  2181. dismissKeyboard()
  2182. }
  2183. DispatchQueue.main.async {
  2184. if !self.viewSticker.isDescendant(of: self.view) {
  2185. self.constraintBottomAttachment.constant = 200.0
  2186. self.view.addSubview(self.viewSticker)
  2187. self.viewSticker.translatesAutoresizingMaskIntoConstraints = false
  2188. NSLayoutConstraint.activate([
  2189. self.viewSticker.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  2190. self.viewSticker.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  2191. self.viewSticker.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  2192. self.viewSticker.heightAnchor.constraint(equalToConstant: 200)
  2193. ])
  2194. let layout = UICollectionViewFlowLayout()
  2195. layout.scrollDirection = .vertical
  2196. let collectionSticker = UICollectionView(frame: .zero, collectionViewLayout: layout)
  2197. collectionSticker.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellSticker")
  2198. collectionSticker.delegate = self
  2199. collectionSticker.dataSource = self
  2200. collectionSticker.backgroundColor = .clear
  2201. self.viewSticker.addSubview(collectionSticker)
  2202. collectionSticker.translatesAutoresizingMaskIntoConstraints = false
  2203. NSLayoutConstraint.activate([
  2204. collectionSticker.topAnchor.constraint(equalTo: self.viewSticker.topAnchor, constant: 20),
  2205. collectionSticker.bottomAnchor.constraint(equalTo: self.viewSticker.bottomAnchor, constant: -20),
  2206. collectionSticker.leadingAnchor.constraint(equalTo: self.viewSticker.leadingAnchor, constant: 20),
  2207. collectionSticker.trailingAnchor.constraint(equalTo: self.viewSticker.trailingAnchor, constant: -20)
  2208. ])
  2209. if (self.currentIndexpath != nil) {
  2210. DispatchQueue.main.async {
  2211. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  2212. }
  2213. } else {
  2214. self.tableChatView.scrollToBottom()
  2215. }
  2216. } else {
  2217. self.constraintBottomAttachment.constant = 0.0
  2218. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2219. self.viewSticker.removeFromSuperview()
  2220. }
  2221. }
  2222. }
  2223. @IBAction func fileTapped(_ sender: UIButton) {
  2224. if isContactCenter && fPinContacCenter.isEmpty && isRequestContactCenter {
  2225. return
  2226. }
  2227. if (self.constraintBottomAttachment.constant != 0.0) {
  2228. constraintBottomAttachment.constant = 0.0
  2229. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2230. self.viewSticker.removeFromSuperview()
  2231. }
  2232. documentPicker.present()
  2233. }
  2234. @objc func sendTapped() {
  2235. sendChat(message_text: textFieldSend.text!, viewController: self)
  2236. }
  2237. @objc func showChooserACKConfidential() {
  2238. // dismissKeyboard()
  2239. let alertController = LibAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
  2240. 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)
  2241. 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)
  2242. 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)
  2243. let imageSticker = resizeImage(image: UIImage(named: "Sticker---Emoji", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2244. let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
  2245. self.isConfidential = !self.isConfidential
  2246. if self.isConfidential {
  2247. self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
  2248. } else {
  2249. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2250. }
  2251. if self.isAck {
  2252. self.isAck = false
  2253. }
  2254. if self.isSecret {
  2255. self.isSecret = false
  2256. }
  2257. })
  2258. let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
  2259. self.isAck = !self.isAck
  2260. if self.isAck {
  2261. self.buttonAckConfidential.setImage(imageAck, for: .normal)
  2262. } else {
  2263. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2264. }
  2265. if self.isConfidential {
  2266. self.isConfidential = false
  2267. }
  2268. if self.isSecret {
  2269. self.isSecret = false
  2270. }
  2271. })
  2272. let secretAction = UIAlertAction(title: "Secret Message".localized(), style: .default, handler: { (UIAlertAction) in
  2273. self.isSecret = !self.isSecret
  2274. if self.isSecret {
  2275. self.buttonAckConfidential.setImage(imageSecret, for: .normal)
  2276. } else {
  2277. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2278. }
  2279. if self.isConfidential {
  2280. self.isConfidential = false
  2281. }
  2282. if self.isAck {
  2283. self.isAck = false
  2284. }
  2285. })
  2286. let stickerAction = UIAlertAction(title: "Open Sticker".localized(), style: .default, handler: { (UIAlertAction) in
  2287. self.stickerTapped(UIButton())
  2288. })
  2289. confidentialAction.setValue(imageConfidential, forKey: "image")
  2290. ackAction.setValue(imageAck, forKey: "image")
  2291. secretAction.setValue(imageSecret, forKey: "image")
  2292. secretAction.setValue(imageSecret, forKey: "image")
  2293. stickerAction.setValue(imageSticker, forKey: "image")
  2294. alertController.addAction(confidentialAction)
  2295. alertController.addAction(ackAction)
  2296. alertController.addAction(secretAction)
  2297. // alertController.addAction(stickerAction)
  2298. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
  2299. self.isConfidential = false
  2300. self.isAck = false
  2301. self.isSecret = false
  2302. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2303. }))
  2304. self.present(alertController, animated: true, completion: nil)
  2305. }
  2306. public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
  2307. self.isConfidential = isConfidential
  2308. self.isAck = isAck
  2309. let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2310. let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
  2311. if isAck {
  2312. buttonAckConfidential.setImage(imageAck, for: .normal)
  2313. } else if isConfidential {
  2314. buttonAckConfidential.setImage(imageConfidential, for: .normal)
  2315. } else {
  2316. self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
  2317. }
  2318. }
  2319. @objc func addRoom(sender: UIBarButtonItem) {
  2320. let controller = QmeraCallContactViewController()
  2321. controller.isDismiss = { user in
  2322. DispatchQueue.global().async {
  2323. _ = Nexilis.write(message: CoreMessage_TMessageBank.getCCRoomInvite(l_pin: user.pin, ticket_id: self.complaintId, channel: self.channelContactCenter))
  2324. }
  2325. }
  2326. controller.selectedUser.append(contentsOf: users)
  2327. controller.isInviteCC = true
  2328. self.navigationController?.show(controller, sender: nil)
  2329. }
  2330. @objc func audioVideoCall(sender: UIBarButtonItem) {
  2331. if sender.tag == 0 {
  2332. if !Nexilis.checkingAccess(key: "audio_call") {
  2333. self.view.makeToast("Feature disabled..".localized(), duration: 3)
  2334. return
  2335. }
  2336. let goAudioCall = Nexilis.checkMicPermission()
  2337. if !goAudioCall{
  2338. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2339. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2340. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2341. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2342. }
  2343. }))
  2344. self.navigationController?.present(alert, animated: true, completion: nil)
  2345. return
  2346. }
  2347. if let pin = dataPerson["f_pin"] {
  2348. if !CheckConnection.isConnectedToNetwork() {
  2349. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2350. imageView.tintColor = .white
  2351. 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)
  2352. banner.show()
  2353. return
  2354. }
  2355. let controller = QmeraAudioViewController()
  2356. controller.user = User.getData(pin: pin)
  2357. controller.isOutgoing = true
  2358. controller.modalPresentationStyle = .overCurrentContext
  2359. present(controller, animated: true, completion: nil)
  2360. }
  2361. } else {
  2362. if !Nexilis.checkingAccess(key: "video_call") {
  2363. self.view.makeToast("Feature disabled..".localized(), duration: 3)
  2364. return
  2365. }
  2366. let goAudioCall = Nexilis.checkMicPermission()
  2367. let goVideoCall = Nexilis.checkCameraPermission()
  2368. if goVideoCall == 0 {
  2369. 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)
  2370. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2371. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2372. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2373. }
  2374. }))
  2375. self.navigationController?.present(alert, animated: true, completion: nil)
  2376. return
  2377. } else if goVideoCall == -1 {
  2378. return
  2379. }
  2380. if !CheckConnection.isConnectedToNetwork() {
  2381. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2382. imageView.tintColor = .white
  2383. 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)
  2384. banner.show()
  2385. return
  2386. }
  2387. let videoVC = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "videoVCQmera") as! QmeraVideoViewController
  2388. videoVC.dataPerson.append(dataPerson)
  2389. self.show(videoVC, sender: nil)
  2390. }
  2391. }
  2392. @objc func dismissKeyboard() {
  2393. if isSearching {
  2394. searchBar.resignFirstResponder()
  2395. } else {
  2396. textFieldSend.resignFirstResponder() // dismiss keyoard
  2397. if (self.constraintBottomAttachment.constant != 0.0) {
  2398. constraintBottomAttachment.constant = 0.0
  2399. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2400. self.viewSticker.removeFromSuperview()
  2401. }
  2402. }
  2403. }
  2404. @objc func didTapExit() {
  2405. if complaintId.isEmpty || fromVCAC {
  2406. self.dismiss(animated: true, completion: {
  2407. self.removeAllObjectBeforeDismissVC()
  2408. })
  2409. } else if !complaintId.isEmpty {
  2410. 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)
  2411. alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
  2412. alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  2413. self.endCallCenter()
  2414. }))
  2415. self.present(alert, animated: true, completion: nil)
  2416. }
  2417. }
  2418. private func removeAllObjectBeforeDismissVC() {
  2419. for timer in self.timerCredential.values {
  2420. timer.invalidate()
  2421. }
  2422. self.timeoutCC.invalidate()
  2423. SecureUserDefaults.shared.removeValue(forKey: "inEditorPersonal")
  2424. NotificationCenter.default.removeObserver(self)
  2425. self.removeFromParent()
  2426. if !self.isContactCenter {
  2427. let l_pin = self.dataPerson["f_pin"]!!
  2428. let data: [String: String] = ["text": self.textFieldSend.textColor != UIColor.lightGray ? self.textFieldSend.text! : "", "reffId": self.reffId ?? ""]
  2429. if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: []),
  2430. let jsonString = String(data: jsonData, encoding: .utf8) {
  2431. SecureUserDefaults.shared.set(jsonString, forKey: "new_saved_\(l_pin)")
  2432. }
  2433. }
  2434. }
  2435. public func endCallCenter() {
  2436. timeoutCC.invalidate()
  2437. let complaintId = self.complaintId
  2438. let idMe = User.getMyPin()!
  2439. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  2440. let requester = onGoingCC.components(separatedBy: ",")[0]
  2441. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  2442. DispatchQueue.global().async {
  2443. let date = "\(Date().currentTimeMillis())"
  2444. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2445. do {
  2446. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  2447. "type" : self.channelContactCenter,
  2448. "title" : "Contact Center".localized(),
  2449. "time" : self.dateStartCC,
  2450. "f_pin" : officer,
  2451. "data" : self.complaintId,
  2452. "time_end" : date,
  2453. "complaint_id" : self.complaintId,
  2454. "members" : "",
  2455. "requester": requester
  2456. ], replace: true)
  2457. } catch {
  2458. rollback.pointee = true
  2459. print("Access database error: \(error.localizedDescription)")
  2460. }
  2461. })
  2462. if officer == idMe {
  2463. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: requester))
  2464. } else {
  2465. if requester == idMe {
  2466. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: officer))
  2467. } else {
  2468. _ = Nexilis.write(message: CoreMessage_TMessageBank.leaveCCRoomInvite(ticket_id: complaintId))
  2469. }
  2470. }
  2471. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  2472. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  2473. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  2474. }
  2475. self.dismiss(animated: true, completion: {
  2476. self.removeAllObjectBeforeDismissVC()
  2477. })
  2478. }
  2479. @objc func keyboardWillHide(notification: NSNotification) {
  2480. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  2481. let info:NSDictionary = notification.userInfo! as NSDictionary
  2482. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2483. self.constraintViewTextField.constant = 0
  2484. self.constraintBottomAttachment.constant = 0
  2485. self.constraintBottomContainerMultpileSelectSession.constant = 0
  2486. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2487. self.view.layoutIfNeeded()
  2488. })
  2489. }
  2490. }
  2491. @objc func keyboardWillShow(notification: NSNotification) {
  2492. if self.viewIfLoaded?.window != nil && !isEditingMessage {
  2493. // if (self.constraintBottomAttachment.constant != 0.0) {
  2494. // self.constraintBottomAttachment.constant = 0.0
  2495. // self.viewSticker.removeConstraints(self.viewSticker.constraints)
  2496. // self.viewSticker.removeFromSuperview()
  2497. // }
  2498. let info:NSDictionary = notification.userInfo! as NSDictionary
  2499. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  2500. let keyboardHeight: CGFloat = keyboardSize.height
  2501. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2502. if self.constraintBottomAttachment.constant != keyboardHeight || self.constraintViewTextField.constant != keyboardHeight - 60 {
  2503. // self.constraintViewTextField.constant = keyboardHeight - 60
  2504. self.constraintBottomAttachment.constant = keyboardHeight
  2505. if isSearching {
  2506. self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
  2507. self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
  2508. }
  2509. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2510. self.view.layoutIfNeeded()
  2511. })
  2512. if isSearching {
  2513. self.tableChatView.scrollToBottom()
  2514. } else {
  2515. if (self.currentIndexpath != nil) {
  2516. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  2517. } else {
  2518. self.tableChatView.scrollToBottom()
  2519. }
  2520. }
  2521. }
  2522. } else if isEditingMessage {
  2523. let info:NSDictionary = notification.userInfo! as NSDictionary
  2524. let keyboardSize = (info[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
  2525. let keyboardHeight: CGFloat = keyboardSize.height
  2526. let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
  2527. let constant: CGFloat = 0 - keyboardHeight - 15
  2528. constraintBottomeditTextView.constant = constant
  2529. constraintBottomSendEditTV.constant = constant
  2530. UIView.animate(withDuration: TimeInterval(duration), animations: {
  2531. self.view.layoutIfNeeded()
  2532. })
  2533. }
  2534. }
  2535. 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) {
  2536. if viewController is EditorPersonal && file_id == "" && dataMessageForward == nil && !isAutoSendCC{
  2537. if ((textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) == "Send message".localized() && textFieldSend.textColor == UIColor.lightGray && attachment_flag != "11") || textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ) {
  2538. dismissKeyboard()
  2539. viewController.view.makeToast("Write Messages".localized(), duration: 3)
  2540. if (textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized()) {
  2541. textFieldSend.text = ""
  2542. }
  2543. if (self.heightTextFieldSend.constant != 40) {
  2544. self.heightTextFieldSend.constant = 40
  2545. }
  2546. return
  2547. }
  2548. }
  2549. var reff_id = reff_id
  2550. if (reffId != nil) {
  2551. reff_id = reffId!
  2552. }
  2553. var is_call_center = is_call_center
  2554. var call_center_id = call_center_id
  2555. var l_pin = dataPerson["f_pin"]!!
  2556. var message_scope_id = message_scope_id
  2557. var chat_id = chat_id
  2558. if (isContactCenter) {
  2559. if fPinContacCenter.isEmpty && isRequestContactCenter {
  2560. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintBottomAttachment.constant == 0 {
  2561. textFieldSend.text = "Send message".localized()
  2562. textFieldSend.textColor = UIColor.lightGray
  2563. } else if constraintBottomAttachment.constant != 0 {
  2564. textFieldSend.text = ""
  2565. }
  2566. dismissKeyboard()
  2567. self.view.makeToast("Unable to send message. Waiting for the officer to accept your request".localized(), duration: 3)
  2568. return
  2569. }
  2570. is_call_center = "1"
  2571. call_center_id = complaintId
  2572. l_pin = fPinContacCenter
  2573. message_scope_id = MessageScope.CHATROOM
  2574. chat_id = complaintId
  2575. if isAutoSendCC {
  2576. timeoutCC = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: false, block: {_ in
  2577. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  2578. imageView.tintColor = .white
  2579. 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)
  2580. banner.show()
  2581. self.endCallCenter()
  2582. })
  2583. }
  2584. }
  2585. var message_text = message_text
  2586. let bulletPoint = " • "
  2587. let numberPattern = #" \d+\.\ "#
  2588. let firstLine = message_text.components(separatedBy: .newlines).first ?? ""
  2589. // Check if text contains bullet points or numbered list using regex
  2590. if !message_text.isEmpty && !firstLine.contains(bulletPoint) && firstLine.range(of: numberPattern, options: .regularExpression) == nil {
  2591. message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
  2592. }
  2593. let idMe = User.getMyPin() as String?
  2594. var opposite_pin = ""
  2595. if isContactCenter {
  2596. opposite_pin = ""
  2597. } else {
  2598. opposite_pin = idMe ?? ""
  2599. }
  2600. var credential = credential
  2601. if isConfidential {
  2602. credential = "1"
  2603. }
  2604. var read_receipts = read_receipts
  2605. if isAck {
  2606. read_receipts = "8"
  2607. }
  2608. var is_secret = is_secret
  2609. if isSecret {
  2610. is_secret = 1
  2611. }
  2612. sendTyping(l_pin: l_pin, isTyping: true)
  2613. 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)")
  2614. Nexilis.addQueueMessage(message: message)
  2615. let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
  2616. if credential == "1" {
  2617. self.listTimerCredential[messageId] = 60
  2618. }
  2619. var row: [String: Any?] = [:]
  2620. row["message_id"] = messageId
  2621. row["f_pin"] = idMe
  2622. row["l_pin"] = dataPerson["f_pin"]!!
  2623. row["message_scope_id"] = message_scope_id
  2624. row["server_date"] = "\(Date().currentTimeMillis())"
  2625. row["status"] = status
  2626. row["message_text"] = message_text
  2627. row["audio_id"] = audio_id
  2628. row["video_id"] = video_id
  2629. row["image_id"] = image_id
  2630. row["thumb_id"] = thumb_id
  2631. row["read_receipts"] = read_receipts
  2632. row["credential"] = credential
  2633. row["chat_id"] = chat_id
  2634. row["file_id"] = file_id
  2635. row["blog_id"] = ex_blog_id
  2636. row["attachment_flag"] = attachment_flag
  2637. row["reff_id"] = reff_id
  2638. row["progress"] = 0.0
  2639. row["lock"] = "0"
  2640. row["is_stared"] = "0"
  2641. row["gif_id"] = gif_id
  2642. row[TypeDataMessage.is_forwarded] = is_forwarded
  2643. row["isSelected"] = false
  2644. row[TypeDataMessage.is_call_center] = is_call_center
  2645. row[TypeDataMessage.call_center_id] = call_center_id
  2646. row[TypeDataMessage.opposite_pin] = opposite_pin
  2647. if !dataDates.contains("Today".localized()) {
  2648. dataDates.append("Today".localized())
  2649. tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .none)
  2650. }
  2651. row["chat_date"] = "Today".localized()
  2652. dataMessages.append(row)
  2653. tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
  2654. if credential == "1" {
  2655. var timer = Timer()
  2656. var minute = 60
  2657. timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: {_ in
  2658. minute -= 1
  2659. self.listTimerCredential[messageId] = minute
  2660. if minute == 0 {
  2661. timer.invalidate()
  2662. self.listTimerCredential.removeValue(forKey: messageId)
  2663. self.timerCredential.removeValue(forKey: messageId)
  2664. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
  2665. if idx != nil {
  2666. self.dataMessages[idx!]["lock"] = "2"
  2667. self.dataMessages[idx!]["reff_id"] = ""
  2668. }
  2669. DispatchQueue.global().async {
  2670. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  2671. do {
  2672. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  2673. "lock" : "2"
  2674. ], _where: "message_id = '\(messageId)'")
  2675. } catch {
  2676. rollback.pointee = true
  2677. print("Access database error: \(error.localizedDescription)")
  2678. }
  2679. })
  2680. }
  2681. }
  2682. let section = self.dataDates.firstIndex(of: self.dataDates[self.dataDates.count - 1])
  2683. let row = self.dataMessages.filter({$0["chat_date"] as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).firstIndex(where: { $0["message_id"] as? String == messageId})
  2684. if row != nil && section != nil{
  2685. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2686. }
  2687. })
  2688. self.timerCredential[messageId] = timer
  2689. }
  2690. if textFieldSend.text!.trimmingCharacters(in: .whitespacesAndNewlines) != "Send message".localized() && textFieldSend.textColor != UIColor.lightGray && constraintBottomAttachment.constant == 0 {
  2691. textFieldSend.text = "Send message".localized()
  2692. textFieldSend.textColor = UIColor.lightGray
  2693. } else if constraintBottomAttachment.constant != 0 {
  2694. textFieldSend.text = ""
  2695. heightTextFieldSend.constant = 40
  2696. }
  2697. deleteReplyView()
  2698. deleteLinkPreview()
  2699. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  2700. self.tableChatView.scrollToBottom()
  2701. if self.markerCounter != nil {
  2702. let lastMarkerCounter = self.markerCounter
  2703. self.markerCounter = nil
  2704. let indexMessage = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == lastMarkerCounter })
  2705. if indexMessage != nil {
  2706. let section = self.dataDates.firstIndex(of: self.dataMessages[indexMessage!]["chat_date"] as? String ?? "")
  2707. 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 })
  2708. if row != nil && section != nil {
  2709. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  2710. }
  2711. }
  2712. }
  2713. // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  2714. // self.timerFakeProgress = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
  2715. // self.updateProgress(row as [AnyHashable : Any])
  2716. // if self.fakeProgMultip == self.maxFakeProgMultip {
  2717. // self.timerFakeProgress?.invalidate()
  2718. // self.fakeProgMultip = 0
  2719. // }
  2720. // }
  2721. // }
  2722. }
  2723. @objc func addFriendReqAction(sender: UIButton) {
  2724. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  2725. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2726. imageView.tintColor = .white
  2727. 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)
  2728. banner.show()
  2729. return
  2730. }
  2731. Nexilis.showLoader()
  2732. let lPin = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  2733. let messageId = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  2734. let isAccept = (sender.tag == 0)
  2735. DispatchQueue.global().async {
  2736. if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getAddFriendApproval(lPin: lPin ?? "", isAccept: isAccept), timeout: 5 * 1000) {
  2737. if response.isOk() {
  2738. self.deleteMessage(l_pin: self.dataPerson["f_pin"]!!, message_id: messageId ?? "", scope: MessageScope.WHISPER, type: "1", chat: "")
  2739. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == messageId})
  2740. if idx != nil {
  2741. self.dataMessages.remove(at: idx!)
  2742. if (idx == self.dataMessages.count - 1) {
  2743. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  2744. }
  2745. for i in 0..<self.dataDates.count {
  2746. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == self.dataDates[i] }).count == 0 {
  2747. self.dataDates.remove(at: i)
  2748. }
  2749. }
  2750. }
  2751. DispatchQueue.main.async {
  2752. Nexilis.hideLoader {
  2753. if self.dataMessages.count == 0 {
  2754. if self.fromNotification {
  2755. self.didTapExit()
  2756. } else {
  2757. self.navigationController?.popViewController(animated: true)
  2758. }
  2759. } else {
  2760. self.tableChatView.reloadData()
  2761. }
  2762. UIApplication.shared.visibleViewController?.view.makeToast(sender.tag == 0 ? "Friend request has been accepted".localized() : "Friend request has been rejected".localized(), duration: 3)
  2763. }
  2764. }
  2765. } else {
  2766. Nexilis.hideLoader {
  2767. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2768. imageView.tintColor = .white
  2769. 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)
  2770. banner.show()
  2771. }
  2772. }
  2773. } else {
  2774. Nexilis.hideLoader {
  2775. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  2776. imageView.tintColor = .white
  2777. 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)
  2778. banner.show()
  2779. }
  2780. }
  2781. }
  2782. }
  2783. @objc func ccAction(sender: UIButton) {
  2784. if self.nowSelectedCategoryCC == "CantReturn" {
  2785. if sender.tag == 503 {
  2786. self.view.makeToast("You can't request Call Center more than one".localized(), duration: 3)
  2787. } else if sender.tag == 504 {
  2788. busyCCAction(sender: sender)
  2789. }
  2790. return
  2791. }
  2792. if self.nowSelectedCategoryCC == "endCC" {
  2793. return
  2794. }
  2795. let id = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  2796. let service_id = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  2797. let level = id!.substring(from: 5, to: 5)
  2798. let levelNow = self.nowSelectedCategoryCC.substring(from: 5, to: 5)
  2799. var isRequest = false
  2800. var channel = 0
  2801. var row: [String: Any?] = [:]
  2802. if nowSelectedCategoryCC.isEmpty || level > levelNow {
  2803. if Utils.getDefaultCC() == "No" && !showToastTwiceClick {
  2804. self.view.makeToast("You can press your choice again to change category".localized(), duration: 3)
  2805. showToastTwiceClick = true
  2806. }
  2807. row["message_id"] = ""
  2808. row["chat_date"] = "Today".localized()
  2809. let dataChat: [CategoryCC] = CategoryCC.getDatafromParent(parent: service_id!)
  2810. if dataChat.count != 0 {
  2811. var data : [CategoryCC] = []
  2812. for i in 0..<dataChat.count {
  2813. 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))
  2814. }
  2815. row["category_cc"] = data
  2816. } else if dataMessages[Int(level)!]["attachment_flag"] == nil {
  2817. let listStringName: [String] = ["Informasi Umum Produk Call 1500046", "Informasi Spesifik Produk"]
  2818. var data : [CategoryCC] = []
  2819. for i in 0..<listStringName.count {
  2820. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(i)", service_id: service_id!, service_name: listStringName[i], parent: id!, description: "", is_tablet: "0"))
  2821. }
  2822. row["category_cc"] = data
  2823. row["attachment_flag"] = "502"
  2824. } else if dataMessages[Int(level)!]["attachment_flag"] != nil && dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" == "502" {
  2825. if id == "level\(Int(level)!)_0" {
  2826. if let url = URL(string: "tel://1500046") {
  2827. UIApplication.shared.open(url)
  2828. }
  2829. return
  2830. } else {
  2831. let listStringName: [String] = ["Messaging".localized(), "Secure SMS".localized(), "VoIP Call".localized(), "Email".localized(), "Video Call".localized(), "GSM Call".localized(), "GPT Chatbot".localized(), "WhatsApp"]
  2832. // let listStringName: [String] = ["Chat with a Representative".localized(), "Video Call a Representative".localized(), "Call a Representative".localized()]
  2833. var data : [CategoryCC] = []
  2834. let channels : [String] = ["0", "4", "1", "3", "2", "5", "7", "6"]
  2835. for i in 0..<listStringName.count {
  2836. data.append(CategoryCC(id: "level\(Int(level)! + 1)_\(channels[i])", service_id: service_id!, service_name: listStringName[i], parent: id!, description: "", is_tablet: "0"))
  2837. }
  2838. row["category_cc"] = data
  2839. row["attachment_flag"] = "503"
  2840. }
  2841. } else {
  2842. channel = Int((id?.components(separatedBy: "_")[1])!)!
  2843. if channel == 1 || channel == 2 {
  2844. if channel == 2 {
  2845. let goAudioCall = Nexilis.checkMicPermission()
  2846. let goVideoCall = Nexilis.checkCameraPermission()
  2847. if goVideoCall == 0 {
  2848. 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)
  2849. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2850. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2851. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2852. }
  2853. }))
  2854. self.navigationController?.present(alert, animated: true, completion: nil)
  2855. return
  2856. } else if goVideoCall == -1 {
  2857. return
  2858. }
  2859. } else if channel == 1 {
  2860. let goAudioCall = Nexilis.checkMicPermission()
  2861. if !goAudioCall{
  2862. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2863. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2864. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2865. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2866. }
  2867. }))
  2868. self.navigationController?.present(alert, animated: true, completion: nil)
  2869. return
  2870. }
  2871. }
  2872. } else if channel == 3 {
  2873. requestEmailContactCenter(channel)
  2874. return
  2875. } else if channel == 4 {
  2876. requestSMSContactCenter(channel)
  2877. return
  2878. } else if channel == 5 {
  2879. requestGSMCallContactCenter(channel)
  2880. return
  2881. } else if channel == 6 {
  2882. requestWhatsappContactCenter(channel)
  2883. return
  2884. } else if channel == 7 {
  2885. APIS.openSmartChatbot()
  2886. return
  2887. }
  2888. row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
  2889. isRequest = true
  2890. }
  2891. if dataMessages[Int(level)!]["attachment_flag"] == nil || dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" != "503" {
  2892. dataMessages.append(row)
  2893. self.nowSelectedCategoryCC = id!
  2894. tableChatView.insertRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
  2895. }
  2896. } else {
  2897. if id == self.nowSelectedCategoryCC {
  2898. if level == "0" {
  2899. self.nowSelectedCategoryCC = ""
  2900. } else {
  2901. let categoryCC = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
  2902. self.nowSelectedCategoryCC = categoryCC[0].parent
  2903. }
  2904. tableChatView.deleteRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
  2905. dataMessages.remove(at: dataMessages.count - 1)
  2906. } else {
  2907. return
  2908. }
  2909. }
  2910. if Utils.getDefaultCC() == "No" {
  2911. if sender.backgroundColor != .orangeBNI {
  2912. var button = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
  2913. if dataMessages[Int(level)!]["attachment_flag"] != nil && dataMessages[Int(level)!]["attachment_flag"] as? String ?? "" == "503" {
  2914. button = dataMessages[dataMessages.count - 1]["category_cc"] as! [CategoryCC]
  2915. }
  2916. for i in button {
  2917. if i.id == id! {
  2918. i.isActive = true
  2919. break
  2920. }
  2921. }
  2922. sender.backgroundColor = .orangeBNI
  2923. dataMessages[dataMessages.count - 2]["category_cc"] = button
  2924. } else {
  2925. let button = dataMessages[dataMessages.count - 1]["category_cc"] as! [CategoryCC]
  2926. for i in button {
  2927. if i.id == id! {
  2928. i.isActive = false
  2929. break
  2930. }
  2931. }
  2932. sender.backgroundColor = .clear
  2933. dataMessages[dataMessages.count - 1]["category_cc"] = button
  2934. }
  2935. }
  2936. if isRequest {
  2937. requestContactCenter(channel: channel, service_id: service_id!, row: row)
  2938. } else {
  2939. self.tableChatView.scrollToBottom()
  2940. }
  2941. }
  2942. private func directCC() {
  2943. if channelContactCenter == "1" || channelContactCenter == "2" {
  2944. if channelContactCenter == "2" {
  2945. let goAudioCall = Nexilis.checkMicPermission()
  2946. let goVideoCall = Nexilis.checkCameraPermission()
  2947. if goVideoCall == 0 {
  2948. 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)
  2949. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2950. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2951. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2952. }
  2953. }))
  2954. self.navigationController?.present(alert, animated: true, completion: nil)
  2955. return
  2956. } else if goVideoCall == -1 {
  2957. return
  2958. }
  2959. } else if channelContactCenter == "1" {
  2960. let goAudioCall = Nexilis.checkMicPermission()
  2961. if !goAudioCall{
  2962. let alert = LibAlertController(title: "Attention!".localized(), message: "Please allow microphone permission in your settings".localized(), preferredStyle: .alert)
  2963. alert.addAction(UIAlertAction(title: "OK".localized(), style: UIAlertAction.Style.default, handler: {_ in
  2964. if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
  2965. UIApplication.shared.open(url, options: [:], completionHandler: nil)
  2966. }
  2967. }))
  2968. self.navigationController?.present(alert, animated: true, completion: nil)
  2969. return
  2970. }
  2971. }
  2972. }
  2973. var row: [String: Any?] = [:]
  2974. row["message_id"] = ""
  2975. row["chat_date"] = "Today".localized()
  2976. row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
  2977. dataMessages.append(row)
  2978. nowSelectedCategoryCC = "CantReturn"
  2979. tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .none)
  2980. requestContactCenter(channel: Int(channelContactCenter)!, service_id: serviceIdCC, row: row)
  2981. }
  2982. @objc func busyCCAction(sender: UIButton) {
  2983. let id = sender.restorationIdentifier?.components(separatedBy: ",")[0]
  2984. let service_id = sender.restorationIdentifier?.components(separatedBy: ",")[1]
  2985. let level = id!.substring(from: 5, to: 5)
  2986. var row: [String: Any?] = [:]
  2987. if id == "level\(Int(level)!)_0" {
  2988. SecureUserDefaults.shared.set(true, forKey: "waitingRequestCC")
  2989. DispatchQueue.global().async {
  2990. let message = CoreMessage_TMessageBank.getQueuingCallCenter(p_channel: Int(self.channelContactCenter)!)
  2991. message.mBodies[CoreMessage_TMessageKey.CATEGORY_ID] = "\(service_id!)"
  2992. _ = Nexilis.writeSync(message: message, timeout: 30 * 1000)
  2993. }
  2994. row["category_cc"] = "Thank you for contacting us,\none of our officers will contact you soon".localized()
  2995. } else {
  2996. row["category_cc"] = "Thank you for being awesome,\nhave a great day!".localized()
  2997. }
  2998. row["message_id"] = ""
  2999. row["chat_date"] = "Today".localized()
  3000. self.nowSelectedCategoryCC = "endCC"
  3001. dataMessages.append(row)
  3002. tableChatView.insertRows(at: [IndexPath(row: Int(level)!, section: 0)], with: .none)
  3003. self.tableChatView.scrollToBottom()
  3004. // DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
  3005. // self.dismiss(animated: true)
  3006. // })
  3007. }
  3008. func requestEmailContactCenter(_ channel: Int){
  3009. let idMe = User.getMyPin() as String?
  3010. let complaintId = "CMP_\(idMe!)_\(String(Date().currentTimeMillis()))EML"
  3011. let message = CoreMessage_TMessageBank.getRequestEmailCallCenter(p_channel: channel)
  3012. if let response = Nexilis.writeSync(message: message) {
  3013. if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") {
  3014. DispatchQueue.main.async {
  3015. let email = response.getBody(key: CoreMessage_TMessageKey.EMAIL, default_value: "")
  3016. let officer = response.getBody(key: CoreMessage_TMessageKey.L_PIN, default_value: "")
  3017. if email.isEmpty {
  3018. self.view.makeToast("Invalid Email Address".localized(), duration: 3)
  3019. return
  3020. }
  3021. // TODO: check if mail available
  3022. Nexilis.openmailAction(to: email)
  3023. }
  3024. }
  3025. }
  3026. }
  3027. func requestSMSContactCenter(_ channel: Int){
  3028. // let idMe = User.getMyPin()!
  3029. // let complaintId = "CMP_\(idMe)_\(String(Date().currentTimeMillis()))SMS"
  3030. // isRequestContactCenter = true
  3031. var phone = Utils.getSMSCenter()
  3032. if phone.substring(from: 0, to: 0) == "0" {
  3033. phone = "+62" + phone.substring(from: 1, to: phone.count)
  3034. }
  3035. APIS.sendSMS(phoneNumber: phone)
  3036. // let tmessage = TMessage()
  3037. // tmessage.mCode = CoreMessage_TMessageCode.ACCEPT_CALL_CENTER
  3038. // tmessage.mStatus = CoreMessage_TMessageUtil.getTID()
  3039. // tmessage.mPIN = idMe
  3040. // tmessage.mBodies[CoreMessage_TMessageKey.F_PIN] = me
  3041. // tmessage.mBodies[CoreMessage_TMessageKey.UPLINE_PIN] = me
  3042. // tmessage.mBodies[CoreMessage_TMessageKey.CHANNEL] = channel
  3043. // tmessage.mBodies[CoreMessage_TMessageKey.CALL_CENTER_ID] = complaint_id
  3044. }
  3045. func requestGSMCallContactCenter(_ channel: Int){
  3046. var phone = Utils.getCallCenter()
  3047. if phone.substring(from: 0, to: 0) == "0" {
  3048. phone = "+62" + phone.substring(from: 1, to: phone.count)
  3049. }
  3050. if let url = URL(string: "tel://\(phone)") {
  3051. UIApplication.shared.open(url)
  3052. }
  3053. }
  3054. func requestWhatsappContactCenter(_ channel: Int){
  3055. var phone = Utils.getWhatsappCenter()
  3056. if phone.substring(from: 0, to: 0) == "0" {
  3057. phone = "+62" + phone.substring(from: 1, to: phone.count)
  3058. }
  3059. APIS.sendWhatsapp(phoneNumber: phone)
  3060. }
  3061. func requestContactCenter(channel: Int, service_id: String, row: [String: Any?]) {
  3062. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  3063. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  3064. imageView.tintColor = .white
  3065. 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)
  3066. banner.show()
  3067. return
  3068. }
  3069. DispatchQueue.global().async {
  3070. let message = CoreMessage_TMessageBank.getRequestCallCenter(p_channel: channel, category_id: service_id)
  3071. if let response = Nexilis.writeSync(message: message) {
  3072. if !self.isDirectCC {
  3073. DispatchQueue.main.async {
  3074. self.dataMessages.append(row)
  3075. self.nowSelectedCategoryCC = "CantReturn"
  3076. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  3077. self.tableChatView.scrollToBottom()
  3078. }
  3079. }
  3080. if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") {
  3081. DispatchQueue.main.async {
  3082. SecureUserDefaults.shared.set(true, forKey: "waitingRequestCC")
  3083. let data = response.getBody(key: CoreMessage_TMessageKey.DATA, default_value: "")
  3084. if data.isEmpty {
  3085. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  3086. var row: [String: Any?] = [:]
  3087. row["message_id"] = ""
  3088. row["chat_date"] = "Today".localized()
  3089. row["attachment_flag"] = "504"
  3090. let listStringName: [String] = ["Yes".localized(), "No".localized()]
  3091. var data : [CategoryCC] = []
  3092. for i in 0..<listStringName.count {
  3093. data.append(CategoryCC(id: "level\(self.dataMessages.count + 1)_\(i)", service_id: service_id, service_name: listStringName[i], parent: "", description: "", is_tablet: "0"))
  3094. }
  3095. row["category_cc"] = data
  3096. self.dataMessages.append(row)
  3097. self.channelContactCenter = "\(channel)"
  3098. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  3099. self.tableChatView.scrollToBottom()
  3100. } else {
  3101. self.fPinContacCenter = data
  3102. }
  3103. }
  3104. } else {
  3105. DispatchQueue.main.async {
  3106. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  3107. var row: [String: Any?] = [:]
  3108. row["message_id"] = ""
  3109. row["chat_date"] = "Today".localized()
  3110. row["attachment_flag"] = "504"
  3111. let listStringName: [String] = ["Yes".localized(), "No".localized()]
  3112. var data : [CategoryCC] = []
  3113. for i in 0..<listStringName.count {
  3114. data.append(CategoryCC(id: "level\(self.dataMessages.count + 1)_\(i)", service_id: service_id, service_name: listStringName[i], parent: "", description: "", is_tablet: "0"))
  3115. }
  3116. row["category_cc"] = data
  3117. self.dataMessages.append(row)
  3118. self.channelContactCenter = "\(channel)"
  3119. self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
  3120. self.tableChatView.scrollToBottom()
  3121. }
  3122. }
  3123. }
  3124. }
  3125. }
  3126. private func sendReadMessageStatus(chat_id: String, f_pin: String, message_scope_id: String, message_id: String) {
  3127. let message = CoreMessage_TMessageBank.getUpdateRead(p_chat_id: chat_id, p_f_pin: f_pin, p_scope_id: message_scope_id, qty: 1)
  3128. let fPin = message.getBody(key: CoreMessage_TMessageKey.F_PIN)
  3129. let scope = message.getBody(key: CoreMessage_TMessageKey.SCOPE_ID)
  3130. message.mBodies[CoreMessage_TMessageKey.SERVER_DATE] = String(Date().currentTimeMillis())
  3131. if (fPin.elementsEqual("-999") || scope.elementsEqual("16") || scope.elementsEqual("15")){
  3132. return
  3133. }
  3134. DispatchQueue.global().async {
  3135. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3136. let valueListGroupImages = listGroupImages.value
  3137. for i in 0..<valueListGroupImages.count {
  3138. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3139. do {
  3140. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3141. "status" : "4"
  3142. ], _where: "message_id = '\(valueListGroupImages[i].messageId)'")
  3143. } catch {
  3144. rollback.pointee = true
  3145. print("Access database error: \(error.localizedDescription)")
  3146. }
  3147. })
  3148. message.mStatus = CoreMessage_TMessageUtil.getTID()
  3149. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  3150. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(valueListGroupImages[i].messageId)"
  3151. }
  3152. } else {
  3153. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3154. do {
  3155. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  3156. "status" : "4"
  3157. ], _where: "message_id = '\(message_id)'")
  3158. } catch {
  3159. rollback.pointee = true
  3160. print("Access database error: \(error.localizedDescription)")
  3161. }
  3162. })
  3163. message.mStatus = CoreMessage_TMessageUtil.getTID()
  3164. message.mBodies[CoreMessage_TMessageKey.L_PIN] = f_pin
  3165. message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID] = "-2,\(message_id)"
  3166. }
  3167. _ = Nexilis.write(message: message)
  3168. }
  3169. if let index = dataMessages.firstIndex(where: {$0["message_id"] as? String == message_id}) {
  3170. dataMessages[index]["status"] = "4"
  3171. let auto: Bool = SecureUserDefaults.shared.value(forKey: "autoDownload") ?? false
  3172. if auto {
  3173. if dataMessages[index]["image_id"] as? String != nil && !((dataMessages[index]["image_id"] as? String)!.isEmpty) {
  3174. if let listGroupImages = self.groupImages.first(where: { $0.key == message_id }) {
  3175. let valueListGroupImages = listGroupImages.value
  3176. for i in 0..<valueListGroupImages.count {
  3177. Download().startHTTP(forKey:valueListGroupImages[i].imageId) { (name, progress) in
  3178. guard progress == 100 else {
  3179. return
  3180. }
  3181. do {
  3182. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3183. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3184. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3185. if let dirPath = paths.first {
  3186. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(valueListGroupImages[i].imageId)
  3187. if FileManager.default.fileExists(atPath: imageURL.path) {
  3188. let image = UIImage(contentsOfFile: imageURL.path)
  3189. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3190. if save {
  3191. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3192. }
  3193. }
  3194. else if FileEncryption.shared.isSecureExists(filename: valueListGroupImages[i].imageId) {
  3195. if var secureData = try FileEncryption.shared.readSecure(filename: valueListGroupImages[i].imageId) {
  3196. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  3197. if dataDecrypt != nil {
  3198. secureData = dataDecrypt!
  3199. }
  3200. let image = UIImage(data: secureData)
  3201. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3202. if save {
  3203. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3204. }
  3205. }
  3206. }
  3207. }
  3208. }
  3209. catch {
  3210. }
  3211. DispatchQueue.main.async { [self] in
  3212. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3213. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3214. if row != nil && section != nil{
  3215. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3216. }
  3217. }
  3218. }
  3219. }
  3220. } else {
  3221. Download().startHTTP(forKey:dataMessages[index]["image_id"] as? String ?? "") { (name, progress) in
  3222. guard progress == 100 else {
  3223. return
  3224. }
  3225. do {
  3226. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3227. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3228. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3229. if let dirPath = paths.first {
  3230. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["image_id"] as? String ?? "")
  3231. if FileManager.default.fileExists(atPath: imageURL.path) {
  3232. let image = UIImage(contentsOfFile: imageURL.path)
  3233. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3234. if save {
  3235. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3236. }
  3237. }
  3238. else if FileEncryption.shared.isSecureExists(filename: self.dataMessages[index]["image_id"] as? String ?? "") {
  3239. if var secureData = try FileEncryption.shared.readSecure(filename: self.dataMessages[index]["image_id"] as? String ?? "") {
  3240. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  3241. if dataDecrypt != nil {
  3242. secureData = dataDecrypt!
  3243. }
  3244. let image = UIImage(data: secureData)
  3245. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3246. if save {
  3247. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  3248. }
  3249. }
  3250. }
  3251. }
  3252. } catch {
  3253. }
  3254. DispatchQueue.main.async { [self] in
  3255. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3256. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3257. if row != nil && section != nil{
  3258. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3259. }
  3260. }
  3261. }
  3262. }
  3263. } else if dataMessages[index]["video_id"] as? String != nil && !((dataMessages[index]["video_id"] as? String)!.isEmpty){
  3264. Download().startHTTP(forKey: dataMessages[index]["video_id"] as? String ?? "", isImage: false) { (name, progress) in
  3265. guard progress == 100 else {
  3266. return
  3267. }
  3268. do {
  3269. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  3270. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  3271. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  3272. if let dirPath = paths.first {
  3273. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(self.dataMessages[index]["video_id"] as? String ?? "")
  3274. if FileManager.default.fileExists(atPath: videoURL.path) {
  3275. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3276. if save {
  3277. PHPhotoLibrary.shared().performChanges({
  3278. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
  3279. }) { saved, error in
  3280. }
  3281. }
  3282. }
  3283. else if FileEncryption.shared.isSecureExists(filename: self.dataMessages[index]["video_id"] as? String ?? "") {
  3284. if var secureData = try FileEncryption.shared.readSecure(filename: self.dataMessages[index]["video_id"] as? String ?? "") {
  3285. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  3286. if dataDecrypt != nil {
  3287. secureData = dataDecrypt!
  3288. }
  3289. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  3290. let tempPath = cachesDirectory.appendingPathComponent(name)
  3291. try secureData.write(to: tempPath)
  3292. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  3293. if save {
  3294. PHPhotoLibrary.shared().performChanges({
  3295. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
  3296. }) { saved, error in
  3297. }
  3298. }
  3299. }
  3300. }
  3301. }
  3302. } catch {
  3303. }
  3304. DispatchQueue.main.async { [self] in
  3305. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3306. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3307. if row != nil && section != nil{
  3308. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3309. }
  3310. }
  3311. }
  3312. }
  3313. else if dataMessages[index]["file_id"] as? String != nil && !((dataMessages[index]["file_id"] as? String)!.isEmpty) {
  3314. Download().startHTTP(forKey: dataMessages[index]["file_id"] as? String ?? "", isImage: false) { (name, progress) in
  3315. guard progress == 100 else {
  3316. return
  3317. }
  3318. DispatchQueue.main.async { [self] in
  3319. let section = dataDates.firstIndex(of: dataMessages[index]["chat_date"] as? String ?? "")
  3320. let row = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataMessages[index]["chat_date"] as? String ?? ""}).firstIndex(where: { $0["message_id"] as? String == message_id})
  3321. if row != nil && section != nil{
  3322. tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  3323. }
  3324. }
  3325. }
  3326. }
  3327. }
  3328. }
  3329. }
  3330. private func sendTyping(l_pin: String, isTyping: Bool = false) {
  3331. DispatchQueue.global().async {
  3332. let tmessage = CoreMessage_TMessageBank.getUpdateTypingStatus(p_opposite: l_pin, p_scope: MessageScope.WHISPER, p_status: isTyping ? "3": "4")
  3333. _ = Nexilis.write(message: tmessage)
  3334. }
  3335. }
  3336. private func getCounter() {
  3337. Database().database?.inTransaction({ fmdb, rollback in
  3338. if let c = Database().getRecords(fmdb: fmdb, query: "SELECT counter FROM MESSAGE_SUMMARY where l_pin='\(dataPerson["f_pin"]!!)'"), c.next() {
  3339. counter = Int(c.int(forColumnIndex: 0))
  3340. c.close()
  3341. }
  3342. })
  3343. }
  3344. private func updateCounter(counter: Int) {
  3345. DispatchQueue.global().async {
  3346. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3347. do {
  3348. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", cvalues: [
  3349. "counter" : "\(counter)"
  3350. ], _where: "l_pin = '\(self.dataPerson["f_pin"]!!)'")
  3351. } catch {
  3352. rollback.pointee = true
  3353. print("Access database error: \(error.localizedDescription)")
  3354. }
  3355. })
  3356. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  3357. }
  3358. }
  3359. private func addButtonScrollToBottom() {
  3360. if tableChatView.alpha != 1 || isSearching {
  3361. return
  3362. }
  3363. self.view.addSubview(buttonScrollToBottom)
  3364. buttonScrollToBottom.translatesAutoresizingMaskIntoConstraints = false
  3365. NSLayoutConstraint.activate([
  3366. buttonScrollToBottom.bottomAnchor.constraint(equalTo: buttonSendChat.topAnchor, constant: -50),
  3367. buttonScrollToBottom.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  3368. buttonScrollToBottom.widthAnchor.constraint(equalToConstant: 60),
  3369. buttonScrollToBottom.heightAnchor.constraint(equalToConstant: 30.0)
  3370. ])
  3371. buttonScrollToBottom.backgroundColor = .greenColor
  3372. buttonScrollToBottom.setImage(UIImage(systemName: "chevron.down.circle"), for: .normal)
  3373. buttonScrollToBottom.imageView?.contentMode = .scaleAspectFit
  3374. buttonScrollToBottom.imageView?.tintColor = .white
  3375. buttonScrollToBottom.contentVerticalAlignment = .fill
  3376. buttonScrollToBottom.contentHorizontalAlignment = .fill
  3377. buttonScrollToBottom.imageEdgeInsets.top = 2.0
  3378. buttonScrollToBottom.imageEdgeInsets.bottom = 2.0
  3379. buttonScrollToBottom.layer.cornerRadius = 10.0
  3380. buttonScrollToBottom.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
  3381. buttonScrollToBottom.clipsToBounds = true
  3382. buttonScrollToBottom.addTarget(self, action: #selector(scrollTobottomAction), for: .touchUpInside)
  3383. }
  3384. private func addCounterAtButttonScrollToBottom() {
  3385. if tableChatView.alpha != 1 || isSearching {
  3386. return
  3387. }
  3388. self.view.addSubview(indicatorCounterBSTB)
  3389. indicatorCounterBSTB.translatesAutoresizingMaskIntoConstraints = false
  3390. indicatorCounterBSTB.backgroundColor = .systemRed
  3391. indicatorCounterBSTB.layer.cornerRadius = 7.5
  3392. indicatorCounterBSTB.clipsToBounds = true
  3393. indicatorCounterBSTB.layer.borderWidth = 0.5
  3394. indicatorCounterBSTB.layer.borderColor = UIColor.secondaryColor.cgColor
  3395. NSLayoutConstraint.activate([
  3396. indicatorCounterBSTB.bottomAnchor.constraint(equalTo: buttonScrollToBottom.topAnchor, constant: 5),
  3397. indicatorCounterBSTB.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -50),
  3398. indicatorCounterBSTB.widthAnchor.constraint(greaterThanOrEqualToConstant: 15),
  3399. indicatorCounterBSTB.heightAnchor.constraint(equalToConstant: 15)
  3400. ])
  3401. indicatorCounterBSTB.addSubview(labelCounter)
  3402. labelCounter.translatesAutoresizingMaskIntoConstraints = false
  3403. NSLayoutConstraint.activate([
  3404. labelCounter.leadingAnchor.constraint(equalTo: indicatorCounterBSTB.leadingAnchor, constant: 2),
  3405. labelCounter.trailingAnchor.constraint(equalTo: indicatorCounterBSTB.trailingAnchor, constant: -2),
  3406. labelCounter.centerXAnchor.constraint(equalTo: indicatorCounterBSTB.centerXAnchor),
  3407. ])
  3408. labelCounter.font = UIFont.systemFont(ofSize: 11)
  3409. labelCounter.text = "\(counter)"
  3410. labelCounter.textColor = .secondaryColor
  3411. labelCounter.textAlignment = .center
  3412. }
  3413. @objc func scrollTobottomAction() {
  3414. tableChatView.scrollToBottom()
  3415. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [self] in
  3416. if buttonScrollToBottom.isDescendant(of: self.view) {
  3417. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  3418. buttonScrollToBottom.removeFromSuperview()
  3419. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3420. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3421. indicatorCounterBSTB.removeFromSuperview()
  3422. }
  3423. }
  3424. }
  3425. }
  3426. private func checkNewMessage(tableView: UITableView) {
  3427. // let indexPathFirst = tableView.indexPathsForVisibleRows?.first
  3428. // if indexPathFirst != nil {
  3429. // let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPathFirst!.section] })
  3430. // if self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPathFirst!.row]["message_id"] as? String }) == 0 && !gettingDataMessage {
  3431. // gettingDataMessage = true
  3432. // addDataMessage()
  3433. // }
  3434. // }
  3435. currentIndexpath = tableView.indexPathsForVisibleRows?.last
  3436. let indexFirst = tableView.indexPathsForVisibleRows?.first
  3437. if indexFirst != nil {
  3438. let dataMessages = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  3439. if dataMessages.count == 0 || dataMessages.count - 1 < currentIndexpath!.row {
  3440. return
  3441. }
  3442. let contentHeight = tableView.contentSize.height
  3443. let scrollViewHeight = tableView.frame.height
  3444. let fullContentOffset = contentHeight - scrollViewHeight
  3445. let contentOffsetY = tableView.contentOffset.y
  3446. if ((currentIndexpath!.section == dataDates.count - 1 && indexFirst!.row != dataMessages.count - 1) || indexFirst!.section != dataDates.count - 1) && fullContentOffset - contentOffsetY > 100 {
  3447. if !buttonScrollToBottom.isDescendant(of: self.view) {
  3448. addButtonScrollToBottom()
  3449. addCounterAtButttonScrollToBottom()
  3450. }
  3451. } else if (indexFirst!.section == dataDates.count - 1 && indexFirst!.row == dataMessages.count - 1) || fullContentOffset - contentOffsetY < 50 {
  3452. if buttonScrollToBottom.isDescendant(of: self.view) {
  3453. buttonScrollToBottom.removeConstraints(buttonScrollToBottom.constraints)
  3454. buttonScrollToBottom.removeFromSuperview()
  3455. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3456. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3457. indicatorCounterBSTB.removeFromSuperview()
  3458. }
  3459. }
  3460. }
  3461. let indexPathFirst = tableChatView.indexPathsForVisibleRows?.first
  3462. if indexPathFirst != nil && listViewOnSection.count != 0 && listViewOnSection.count - 1 >= indexPathFirst!.section {
  3463. let headerView = listViewOnSection[indexPathFirst!.section]
  3464. if headerView.isHidden {
  3465. headerView.isHidden = false
  3466. }
  3467. }
  3468. var listData = dataMessages[0...currentIndexpath!.row]
  3469. listData = listData.filter({$0["status"] as? String != "4" && $0["status"] as? String != "8"})
  3470. if listData.count != 0 && !isContactCenter {
  3471. let idMe = User.getMyPin() as String?
  3472. for i in 0...listData.count - 1 {
  3473. if listData[i]["f_pin"] as? String != idMe {
  3474. sendReadMessageStatus(chat_id: "", f_pin: dataPerson["f_pin"]!!, message_scope_id: MessageScope.WHISPER, message_id: listData[i]["message_id"] as? String ?? "")
  3475. }
  3476. }
  3477. }
  3478. }
  3479. if counter == 0 && indicatorCounterBSTB.isDescendant(of: self.view) {
  3480. indicatorCounterBSTB.removeConstraints(indicatorCounterBSTB.constraints)
  3481. indicatorCounterBSTB.removeFromSuperview()
  3482. } else if counter != 0 && currentIndexpath != nil {
  3483. let dataFilter = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[currentIndexpath!.section] })
  3484. if dataFilter.count == 0 {
  3485. return
  3486. }
  3487. let idx = dataMessages.firstIndex(where: { $0["message_id"] as? String == dataFilter[currentIndexpath!.row]["message_id"] as? String})
  3488. if idx == nil {
  3489. return
  3490. }
  3491. if (dataMessages.count - counter) <= idx! {
  3492. let countUpdate = idx! - (dataMessages.count - counter)
  3493. counter = counter - (countUpdate + 1)
  3494. if indicatorCounterBSTB.isDescendant(of: self.view) {
  3495. labelCounter.text = "\(counter)"
  3496. }
  3497. updateCounter(counter: counter)
  3498. }
  3499. }
  3500. }
  3501. }
  3502. //EPV
  3503. extension EditorPersonal: PreviewAttachmentImageVideoDelegate, PHPickerViewControllerDelegate {
  3504. public func didSelect(imagevideo: Any?) {
  3505. if (imagevideo != nil) {
  3506. let imageVideoData = imagevideo as! [UIImagePickerController.InfoKey: Any]
  3507. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3508. previewImageVC.imageVideoData = imageVideoData
  3509. if (textFieldSend.textColor != .lightGray) {
  3510. previewImageVC.currentTextTextField = textFieldSend.text
  3511. }
  3512. previewImageVC.modalPresentationStyle = .custom
  3513. previewImageVC.delegate = self
  3514. previewImageVC.isAck = self.isAck
  3515. previewImageVC.isConfidential = self.isConfidential
  3516. previewImageVC.isCC = self.isContactCenter
  3517. self.present(previewImageVC, animated: true, completion: nil)
  3518. }
  3519. }
  3520. public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
  3521. if !isBlackCancelButton {
  3522. let cancelButtonAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  3523. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)
  3524. }
  3525. guard let result = results.first else {
  3526. picker.dismiss(animated: true, completion: nil)
  3527. return
  3528. }
  3529. if result.itemProvider.hasItemConformingToTypeIdentifier("com.compuserve.gif") {
  3530. picker.dismiss(animated: true, completion: {
  3531. Nexilis.showLoader()
  3532. result.itemProvider.loadDataRepresentation(forTypeIdentifier: "com.compuserve.gif") { data, error in
  3533. if let error = error {
  3534. print("Error loading GIF: \(error.localizedDescription)")
  3535. Nexilis.hideLoader() {
  3536. }
  3537. } else if let data = data {
  3538. DispatchQueue.main.async {
  3539. Nexilis.hideLoader() {
  3540. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3541. if (self.textFieldSend.textColor != .lightGray) {
  3542. previewImageVC.currentTextTextField = self.textFieldSend.text
  3543. }
  3544. previewImageVC.fromCopy = true
  3545. previewImageVC.isGIF = true
  3546. previewImageVC.dataGIF = data
  3547. previewImageVC.modalPresentationStyle = .custom
  3548. previewImageVC.delegate = self
  3549. previewImageVC.isAck = self.isAck
  3550. previewImageVC.isConfidential = self.isConfidential
  3551. self.present(previewImageVC, animated: true, completion: nil)
  3552. }
  3553. }
  3554. }
  3555. }
  3556. })
  3557. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.image") {
  3558. picker.dismiss(animated: true, completion: {
  3559. Nexilis.showLoader()
  3560. result.itemProvider.loadObject(ofClass: UIImage.self) { object, error in
  3561. if let image = object as? UIImage {
  3562. DispatchQueue.main.async {
  3563. Nexilis.hideLoader {
  3564. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3565. if (self.textFieldSend.textColor != .lightGray) {
  3566. previewImageVC.currentTextTextField = self.textFieldSend.text
  3567. }
  3568. previewImageVC.fromCopy = true
  3569. previewImageVC.image = image
  3570. previewImageVC.modalPresentationStyle = .custom
  3571. previewImageVC.delegate = self
  3572. previewImageVC.isAck = self.isAck
  3573. previewImageVC.isConfidential = self.isConfidential
  3574. self.present(previewImageVC, animated: true, completion: nil)
  3575. }
  3576. }
  3577. }
  3578. }
  3579. })
  3580. } else if result.itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
  3581. picker.dismiss(animated: true, completion: {
  3582. Nexilis.showLoader()
  3583. result.itemProvider.loadFileRepresentation(forTypeIdentifier: "public.movie") { tempURL, error in
  3584. if let tempURL = tempURL {
  3585. let fileManager = FileManager.default
  3586. let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
  3587. let destinationURL = documentsDirectory.appendingPathComponent(tempURL.lastPathComponent)
  3588. do {
  3589. if fileManager.fileExists(atPath: destinationURL.path) {
  3590. try fileManager.removeItem(at: destinationURL)
  3591. }
  3592. try fileManager.copyItem(at: tempURL, to: destinationURL)
  3593. DispatchQueue.main.async {
  3594. Nexilis.hideLoader {
  3595. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3596. if (self.textFieldSend.textColor != .lightGray) {
  3597. previewImageVC.currentTextTextField = self.textFieldSend.text
  3598. }
  3599. previewImageVC.modalPresentationStyle = .custom
  3600. previewImageVC.urlVideoPhpPicker = destinationURL
  3601. previewImageVC.delegate = self
  3602. previewImageVC.isAck = self.isAck
  3603. previewImageVC.isConfidential = self.isConfidential
  3604. previewImageVC.isCC = self.isContactCenter
  3605. self.present(previewImageVC, animated: true, completion: nil)
  3606. }
  3607. }
  3608. } catch {
  3609. print("Error copying video file: \(error.localizedDescription)")
  3610. }
  3611. }
  3612. }
  3613. })
  3614. }
  3615. }
  3616. func sendChatFromPreviewImage(message_text: String, attachment_flag: String, image_id: String, video_id: String, thumb_id: String, gif_id: String, viewController: UIViewController) {
  3617. 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)
  3618. }
  3619. }
  3620. //EQL
  3621. extension EditorPersonal: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPreviewControllerDataSource {
  3622. public func didSelectDocument(document: Any?) {
  3623. if (document != nil) {
  3624. self.previewItem = (document as! [URL])[0] as NSURL
  3625. let previewController = QLPreviewController()
  3626. let navController = CustomNavigationController(rootViewController: previewController)
  3627. navController.navigationBar.tintColor = .white
  3628. navController.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  3629. navController.navigationBar.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  3630. navController.navigationBar.isTranslucent = false
  3631. let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)]
  3632. let appearance = UINavigationBarAppearance()
  3633. appearance.configureWithDefaultBackground()
  3634. appearance.titleTextAttributes = attributes
  3635. appearance.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  3636. navController.navigationBar.standardAppearance = appearance
  3637. navController.navigationBar.scrollEdgeAppearance = appearance
  3638. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  3639. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  3640. let leftBarButton = navigationQLPreviewDocument(title: "Cancel".localized(), style: .plain, target: self, action: #selector(cancelDocumentPreview))
  3641. let rightBarButton = navigationQLPreviewDocument(title: "Send".localized(), style: .done, target: self, action: #selector(sendDocument))
  3642. leftBarButton.navigation = navController
  3643. rightBarButton.navigation = navController
  3644. previewController.navigationItem.leftBarButtonItem = leftBarButton
  3645. previewController.navigationItem.rightBarButtonItem = rightBarButton
  3646. previewController.dataSource = self
  3647. previewController.modalPresentationStyle = .pageSheet
  3648. self.present(navController, animated: true, completion: nil)
  3649. }
  3650. }
  3651. @objc private func cancelDocumentPreview(sender: navigationQLPreviewDocument) {
  3652. sender.navigation.dismiss(animated: true, completion: nil)
  3653. }
  3654. @objc private func sendDocument(sender: navigationQLPreviewDocument) {
  3655. sender.navigation.dismiss(animated: true, completion: nil)
  3656. do {
  3657. let dataFile = try Data(contentsOf: self.previewItem! as URL)
  3658. let urlFile = self.previewItem?.absoluteString
  3659. var originaFileName = (urlFile! as NSString).lastPathComponent
  3660. originaFileName = NSString(string: originaFileName).removingPercentEncoding!
  3661. let renamedNameFile = "Nexilis_\(Date().currentTimeMillis())_" + originaFileName
  3662. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  3663. let fileURL = documentsDirectory.appendingPathComponent(renamedNameFile)
  3664. if !FileManager.default.fileExists(atPath: fileURL.path) {
  3665. do {
  3666. try dataFile.write(to: fileURL)
  3667. //print("file saved")
  3668. } catch {
  3669. //print("error saving file:", error)
  3670. }
  3671. }
  3672. sendChat(message_text: "\(originaFileName)|", attachment_flag: "6", file_id: renamedNameFile, viewController: self)
  3673. } catch {
  3674. }
  3675. }
  3676. public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  3677. return self.previewItem != nil ? 1 : 0
  3678. }
  3679. public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  3680. return self.previewItem!
  3681. }
  3682. }
  3683. //ETV
  3684. extension EditorPersonal: UITextViewDelegate, CustomTextViewPasteDelegate {
  3685. func customTextViewDidPasteText(image: UIImage?, dataGIF: Data?) {
  3686. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  3687. previewImageVC.image = image
  3688. previewImageVC.isGIF = image == nil
  3689. previewImageVC.fromCopy = true
  3690. previewImageVC.dataGIF = dataGIF
  3691. previewImageVC.currentTextTextField = textFieldSend.text
  3692. previewImageVC.modalPresentationStyle = .custom
  3693. previewImageVC.delegate = self
  3694. previewImageVC.isAck = self.isAck
  3695. previewImageVC.isConfidential = self.isConfidential
  3696. self.present(previewImageVC, animated: true, completion: nil)
  3697. }
  3698. public func textViewDidChangeSelection(_ textView: UITextView) {
  3699. var nowTextFieldSend = self.textFieldSend
  3700. if isEditingMessage {
  3701. nowTextFieldSend = editTextView
  3702. }
  3703. let cursorPosition = textView.caretRect(for: nowTextFieldSend!.selectedTextRange!.start).origin
  3704. let doubleCurrentLine = cursorPosition.y / nowTextFieldSend!.font!.lineHeight
  3705. if doubleCurrentLine.isFinite {
  3706. let currentLine = Int(ceil(doubleCurrentLine))
  3707. UIView.animate(withDuration: 0.3) {
  3708. let layoutManager = textView.layoutManager
  3709. var numberOfLines = 0
  3710. var index = 0
  3711. let numberOfGlyphs = layoutManager.numberOfGlyphs
  3712. while index < numberOfGlyphs {
  3713. var lineRange = NSRange()
  3714. layoutManager.lineFragmentRect(forGlyphAt: index, effectiveRange: &lineRange)
  3715. index = NSMaxRange(lineRange)
  3716. numberOfLines += 1
  3717. }
  3718. if currentLine == 1 && (numberOfLines == 1 || numberOfLines == 0) {
  3719. if self.isEditingMessage {
  3720. self.constraintHeighteditTextView.constant = 40
  3721. } else {
  3722. self.heightTextFieldSend.constant = 40
  3723. }
  3724. } else if (self.heightTextFieldSend.constant < 95.0 || (self.constraintHeighteditTextView != nil && self.constraintHeighteditTextView.constant < 95.0)) && currentLine >= 4 {
  3725. if self.isEditingMessage {
  3726. self.constraintHeighteditTextView.constant = 95.0
  3727. } else {
  3728. self.heightTextFieldSend.constant = 95.0
  3729. }
  3730. } else if currentLine < 4 && numberOfLines < 5 {
  3731. if (nowTextFieldSend!.text.count > 0 && self.heightTextFieldSend.constant != nowTextFieldSend!.contentSize.height) {
  3732. if self.isEditingMessage {
  3733. self.constraintHeighteditTextView.constant = nowTextFieldSend!.contentSize.height
  3734. } else {
  3735. self.heightTextFieldSend.constant = nowTextFieldSend!.contentSize.height
  3736. }
  3737. }
  3738. }
  3739. }
  3740. }
  3741. if self.isEditingMessage && textView == editTextView {
  3742. if textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
  3743. buttonSendEdit.isEnabled = false
  3744. } else if !buttonSendEdit.isEnabled {
  3745. buttonSendEdit.isEnabled = true
  3746. }
  3747. }
  3748. //indention code:
  3749. let text = textView.text ?? ""
  3750. let cursorPositionIndent = textView.selectedRange.location
  3751. // Prevent moving cursor before the 2-space indent
  3752. let lines = text.components(separatedBy: "\n")
  3753. var adjustedCursorPosition = cursorPositionIndent
  3754. for line in lines {
  3755. if let range = text.range(of: line), NSRange(range, in: text).contains(cursorPositionIndent) {
  3756. if line.hasPrefix(" •") || line.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  3757. let startOfLine = text.distance(from: text.startIndex, to: range.lowerBound)
  3758. let minCursorPosition = startOfLine + 2 // Prevent cursor before indentation
  3759. if cursorPositionIndent < minCursorPosition {
  3760. adjustedCursorPosition = minCursorPosition
  3761. }
  3762. }
  3763. break
  3764. }
  3765. }
  3766. if adjustedCursorPosition != cursorPositionIndent {
  3767. textView.selectedRange = NSRange(location: adjustedCursorPosition, length: 0)
  3768. }
  3769. }
  3770. public func textViewDidChange(_ textView: UITextView) {
  3771. if textView.text.count == 0 {
  3772. isAlwaysHideLinkPreview = false
  3773. }
  3774. if allowTyping {
  3775. allowTyping = false
  3776. if isContactCenter && !fPinContacCenter.isEmpty {
  3777. sendTyping(l_pin: fPinContacCenter, isTyping: true)
  3778. } else {
  3779. sendTyping(l_pin: dataPerson["f_pin"]!!, isTyping: true)
  3780. }
  3781. DispatchQueue.main.asyncAfter(deadline: .now() + 4, execute: {
  3782. self.allowTyping = true
  3783. })
  3784. }
  3785. timerCheckLink?.invalidate()
  3786. timerCheckLink = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {_ in
  3787. self.checkLink(fullText: textView.text)
  3788. })
  3789. //indention code:
  3790. let text = textView.text ?? ""
  3791. let cursorPosition = textView.selectedRange.location
  3792. // Handle Bullets (- [space] + letter → • )
  3793. let bulletPattern = #"(?<=\n|^)- (\S)"#
  3794. if let match = text.range(of: bulletPattern, options: .regularExpression) {
  3795. let matchedText = text[match]
  3796. if let spaceIndex = matchedText.firstIndex(of: " ") {
  3797. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  3798. let replacedText = text.replacingOccurrences(of: matchedText, with: " • \(firstLetter)", range: match)
  3799. let newCursorPosition = cursorPosition + 2 // Adjust cursor position
  3800. textView.text = replacedText
  3801. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  3802. }
  3803. }
  3804. // Handle Numbered Lists (e.g., "1. " [space] + letter → " 1.")
  3805. let numberPattern = #"(?<=\n|^)(\d+)\. (\S)"# // Matches "1. X"
  3806. if let match = text.range(of: numberPattern, options: .regularExpression) {
  3807. let matchedText = text[match]
  3808. if let spaceIndex = matchedText.firstIndex(of: " ") {
  3809. let firstLetter = matchedText[matchedText.index(after: spaceIndex)...]
  3810. let replacedText = text.replacingOccurrences(of: matchedText, with: " \(matchedText)", range: match)
  3811. let newCursorPosition = cursorPosition + 2 // Adjust cursor
  3812. textView.text = replacedText
  3813. textView.selectedRange = NSRange(location: newCursorPosition, length: 0)
  3814. }
  3815. }
  3816. handleRichText(textView)
  3817. }
  3818. private func checkLink(fullText: String) {
  3819. if !isAlwaysHideLinkPreview {
  3820. var text = ""
  3821. let listTextSplitBreak = fullText.components(separatedBy: "\n")
  3822. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  3823. if indexFirstLinkSplitBreak != nil {
  3824. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  3825. 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) })
  3826. if indexFirstLinkSplitSpace != nil {
  3827. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  3828. }
  3829. }
  3830. if !text.isEmpty {
  3831. var stringURl = text
  3832. if stringURl.starts(with: "www.") {
  3833. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  3834. }
  3835. var dataURL = ""
  3836. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3837. do {
  3838. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
  3839. while cursor.next() {
  3840. if let data = cursor.string(forColumnIndex: 0) {
  3841. dataURL = data
  3842. }
  3843. }
  3844. cursor.close()
  3845. }
  3846. } catch {
  3847. rollback.pointee = true
  3848. print("Access database error: \(error.localizedDescription)")
  3849. }
  3850. })
  3851. if !dataURL.isEmpty {
  3852. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  3853. let title = data["title"] as? String ?? ""
  3854. let description = data["description"] as? String ?? ""
  3855. let imageUrl = data["imageUrl"] as? String
  3856. let link = data["link"] as? String ?? ""
  3857. if self.showingLink != text {
  3858. self.showingLink = text
  3859. self.deleteLinkPreview()
  3860. if !textFieldSend.text.isEmpty || textFieldSend.text.contains(text){
  3861. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  3862. }
  3863. }
  3864. }
  3865. } else {
  3866. let urlConfig = URLSessionConfiguration.default
  3867. let sessionDelegate = SelfSignedURLSessionDelegate()
  3868. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  3869. let slp = SwiftLinkPreview(session: session,
  3870. workQueue: SwiftLinkPreview.defaultWorkQueue,
  3871. responseQueue: DispatchQueue.main,
  3872. cache: DisabledCache.instance)
  3873. let preview = slp.preview(stringURl,
  3874. onSuccess: { result in
  3875. let title = result.title ?? "No Title"
  3876. let description = stringURl.contains("google.com") ? "" : result.description
  3877. let imageUrl = result.icon
  3878. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  3879. do {
  3880. var dataJson: [String: Any] = [:]
  3881. dataJson["title"] = title
  3882. dataJson["description"] = description
  3883. dataJson["imageUrl"] = imageUrl
  3884. dataJson["link"] = text
  3885. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  3886. return
  3887. }
  3888. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  3889. "id" : "\(Date().currentTimeMillis().toHex())",
  3890. "link" : text,
  3891. "data_link" : json,
  3892. "retry": 0
  3893. ], replace: true)
  3894. } catch {
  3895. rollback.pointee = true
  3896. print("Access database error: \(error.localizedDescription)")
  3897. }
  3898. })
  3899. if self.showingLink != text {
  3900. self.showingLink = text
  3901. self.deleteLinkPreview()
  3902. if !self.textFieldSend.text.isEmpty || self.textFieldSend.text.contains(text){
  3903. self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
  3904. }
  3905. }
  3906. },
  3907. onError: { error in
  3908. self.deleteLinkPreview()
  3909. })
  3910. }
  3911. } else {
  3912. deleteLinkPreview()
  3913. }
  3914. }
  3915. }
  3916. private func buildPreviewLink(imageUrl: String?, title: String, description: String?, stringURl: String) {
  3917. if !self.viewTextfield.subviews.contains(self.containerLink){
  3918. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  3919. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
  3920. }, completion: nil)
  3921. }
  3922. self.viewTextfield.addSubview(self.containerLink)
  3923. self.containerLink.translatesAutoresizingMaskIntoConstraints = false
  3924. self.containerLink.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  3925. self.containerLink.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor).isActive = true
  3926. self.containerLink.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  3927. self.containerLink.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  3928. self.containerLink.backgroundColor = .secondaryColor
  3929. if self.reffId != nil {
  3930. self.bottomAnchorPreviewReply.isActive = false
  3931. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  3932. self.bottomAnchorPreviewReply.isActive = true
  3933. }
  3934. let imagePreview = UIImageView()
  3935. if imageUrl != nil {
  3936. self.containerLink.addSubview(imagePreview)
  3937. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  3938. imagePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor).isActive = true
  3939. imagePreview.bottomAnchor.constraint(equalTo: self.containerLink.bottomAnchor).isActive = true
  3940. imagePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor).isActive = true
  3941. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  3942. imagePreview.loadImageAsync(with: imageUrl)
  3943. imagePreview.contentMode = .scaleAspectFit
  3944. }
  3945. let titlePreview = UILabel()
  3946. self.containerLink.addSubview(titlePreview)
  3947. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  3948. if imageUrl != nil {
  3949. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  3950. } else {
  3951. titlePreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  3952. }
  3953. titlePreview.topAnchor.constraint(equalTo: self.containerLink.topAnchor, constant: 25.0).isActive = true
  3954. titlePreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  3955. titlePreview.text = title
  3956. titlePreview.font = UIFont.systemFont(ofSize: 14.0 + offset(), weight: .bold)
  3957. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  3958. let descPreview = UILabel()
  3959. self.containerLink.addSubview(descPreview)
  3960. descPreview.translatesAutoresizingMaskIntoConstraints = false
  3961. if imageUrl != nil {
  3962. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  3963. } else {
  3964. descPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  3965. }
  3966. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  3967. descPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  3968. descPreview.text = description
  3969. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  3970. descPreview.textColor = .gray
  3971. descPreview.numberOfLines = 1
  3972. let linkPreview = UILabel()
  3973. self.containerLink.addSubview(linkPreview)
  3974. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  3975. if imageUrl != nil {
  3976. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  3977. } else {
  3978. linkPreview.leadingAnchor.constraint(equalTo: self.containerLink.leadingAnchor, constant: 5.0).isActive = true
  3979. }
  3980. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  3981. linkPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -80.0).isActive = true
  3982. linkPreview.text = stringURl
  3983. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  3984. linkPreview.textColor = .gray
  3985. linkPreview.numberOfLines = 1
  3986. let cancelPreview = UIButton(type: .custom)
  3987. self.containerLink.addSubview(cancelPreview)
  3988. cancelPreview.translatesAutoresizingMaskIntoConstraints = false
  3989. cancelPreview.trailingAnchor.constraint(equalTo: self.containerLink.trailingAnchor, constant: -10).isActive = true
  3990. cancelPreview.centerYAnchor.constraint(equalTo: self.containerLink.centerYAnchor).isActive = true
  3991. cancelPreview.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  3992. cancelPreview.addTarget(nil, action: #selector(self.removeLinkPreviewUntilEmptyTextView), for: .touchUpInside)
  3993. cancelPreview.backgroundColor = .clear
  3994. cancelPreview.tintColor = .mainColor
  3995. }
  3996. public func textViewDidBeginEditing(_ textView: UITextView) {
  3997. if textView.textColor == UIColor.lightGray {
  3998. textView.text = nil
  3999. textView.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  4000. }
  4001. }
  4002. public func textViewDidEndEditing(_ textView: UITextView) {
  4003. if textView.text.isEmpty && textView != editTextView {
  4004. textView.text = "Send message".localized()
  4005. textView.textColor = UIColor.lightGray
  4006. }
  4007. }
  4008. public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  4009. let indent = handleIndent(textView, range, text)
  4010. if !indent {
  4011. handleRichText(textView)
  4012. return indent
  4013. }
  4014. if (self.textFieldSend.text.count == 0) {
  4015. return text != "\n"
  4016. }
  4017. return true
  4018. }
  4019. private func handleIndent(_ textView: UITextView, _ range: NSRange, _ text: String) -> Bool {
  4020. guard let nsText = textView.text as NSString? else { return true }
  4021. let newText = nsText.replacingCharacters(in: range, with: text)
  4022. var lines = newText.components(separatedBy: "\n")
  4023. // Ensure range location is valid, considering Unicode scalars
  4024. guard let textRange = Range(range, in: textView.text) else { return true }
  4025. let prefixText = textView.text[..<textRange.lowerBound]
  4026. let affectedLineIndex = prefixText.components(separatedBy: "\n").count - 1
  4027. guard affectedLineIndex >= 0, affectedLineIndex < lines.count else { return true }
  4028. let affectedLine = lines[affectedLineIndex]
  4029. // Prevent deleting two-space indentation before bullet/number
  4030. if affectedLine.hasPrefix(" •") || affectedLine.range(of: #"^\s{2}\d+\."#, options: .regularExpression) != nil {
  4031. if let lineStart = textView.text.range(of: affectedLine)?.lowerBound,
  4032. let startIndex = textView.text.distance(of: lineStart) {
  4033. if range.location == startIndex || range.location == startIndex + 1 {
  4034. return false
  4035. }
  4036. }
  4037. }
  4038. // Auto-indent new lines based on previous line
  4039. if text == "\n" {
  4040. let previousLine = lines[affectedLineIndex]
  4041. if previousLine.hasPrefix(" •") {
  4042. let newBullet = "\n • "
  4043. textView.text = nsText.replacingCharacters(in: range, with: newBullet)
  4044. textView.selectedRange = NSRange(location: range.location + newBullet.utf16.count, length: 0)
  4045. return false
  4046. }
  4047. if let match = previousLine.range(of: #"^\s{2}(\d+)\."#, options: .regularExpression),
  4048. let numberMatch = previousLine[match].components(separatedBy: ".").first,
  4049. let number = Int(numberMatch.trimmingCharacters(in: .whitespaces)) {
  4050. let newNumber = "\n \(number + 1). "
  4051. textView.text = nsText.replacingCharacters(in: range, with: newNumber)
  4052. textView.selectedRange = NSRange(location: range.location + newNumber.utf16.count, length: 0)
  4053. return false
  4054. }
  4055. }
  4056. // Handle Backspace on Empty Bullet (Convert " • " → "- ")
  4057. if text.isEmpty && affectedLine.trimmingCharacters(in: .whitespaces) == "•" {
  4058. lines[affectedLineIndex] = "- " // Replace " • " with "- "
  4059. textView.text = lines.joined(separator: "\n")
  4060. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  4061. return false
  4062. }
  4063. // Handle Backspace on Numbered List
  4064. if text.isEmpty, affectedLine.range(of: #"^\s{2}(\d+)\.$"#, options: .regularExpression) != nil {
  4065. lines[affectedLineIndex] = affectedLine.trimmingCharacters(in: .whitespaces)
  4066. textView.text = lines.joined(separator: "\n")
  4067. textView.selectedRange = NSRange(location: range.location - 1, length: 0)
  4068. return false
  4069. }
  4070. return true
  4071. }
  4072. private func handleRichText(_ textView: UITextView) {
  4073. textView.preserveCursorPosition(withChanges: { _ in
  4074. textView.attributedText = textView.text.richText(isEditing: true)
  4075. return .preserveCursor
  4076. })
  4077. }
  4078. func isGIFData(_ data: Data) -> Bool {
  4079. let gifSignature: [UInt8] = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]
  4080. let rawData = [UInt8](data.prefix(6))
  4081. return rawData == gifSignature
  4082. }
  4083. public func textView(_ textView: UITextView, shouldInteractWith URL: URL?, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
  4084. var urlString: String?
  4085. if let url = URL {
  4086. urlString = url.absoluteString
  4087. } else {
  4088. if let range = Range(characterRange, in: textView.text) {
  4089. let tappedText = String(textView.text[range])
  4090. urlString = tappedText
  4091. }
  4092. }
  4093. guard let finalURL = urlString else {
  4094. return false
  4095. }
  4096. switch interaction {
  4097. case .invokeDefaultAction:
  4098. let gesture = ObjectGesture()
  4099. gesture.message_id = finalURL
  4100. tapMessageText(gesture)
  4101. return false
  4102. case .presentActions:
  4103. UIPasteboard.general.string = finalURL
  4104. self.view.makeToast("Link Copied".localized(), duration: 3)
  4105. return false
  4106. case .preview:
  4107. return true
  4108. @unknown default:
  4109. return true
  4110. }
  4111. }
  4112. }
  4113. //EUC
  4114. extension EditorPersonal: UIContextMenuInteractionDelegate {
  4115. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
  4116. if showMenuContext {
  4117. showMenuContext = false
  4118. interaction.view!.removeInteraction(interaction)
  4119. }
  4120. }
  4121. public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
  4122. if textFieldSend.isFirstResponder {
  4123. textFieldSend.resignFirstResponder()
  4124. }
  4125. let indexPath = self.tableChatView.indexPathForRow(at: interaction.view!.convert(location, to: self.tableChatView))
  4126. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  4127. var star: UIAction
  4128. if (dataMessages[indexPath!.row]["is_stared"] as? String ?? "" == "0") {
  4129. star = UIAction(title: "Star".localized(), image: UIImage(systemName: "star"), handler: {(_) in
  4130. if self.removed {
  4131. return
  4132. }
  4133. DispatchQueue.global().async {
  4134. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4135. do {
  4136. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4137. "is_stared" : 1
  4138. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  4139. } catch {
  4140. rollback.pointee = true
  4141. print("Access database error: \(error.localizedDescription)")
  4142. }
  4143. })
  4144. }
  4145. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4146. if idx != nil{
  4147. self.dataMessages[idx!]["is_stared"] = "1"
  4148. }
  4149. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4150. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  4151. })
  4152. } else {
  4153. star = UIAction(title: "Unstar".localized(), image: UIImage(systemName: "star.slash"), handler: {(_) in
  4154. if self.removed {
  4155. return
  4156. }
  4157. DispatchQueue.global().async {
  4158. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4159. do {
  4160. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4161. "is_stared" : 0
  4162. ], _where: "message_id = '\(dataMessages[indexPath!.row]["message_id"] as? String ?? "")'")
  4163. } catch {
  4164. rollback.pointee = true
  4165. print("Access database error: \(error.localizedDescription)")
  4166. }
  4167. })
  4168. }
  4169. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4170. if idx != nil{
  4171. self.dataMessages[idx!]["is_stared"] = "0"
  4172. }
  4173. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4174. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "listenerStarMessage"), object: nil, userInfo: nil)
  4175. })
  4176. }
  4177. let reply = UIAction(title: "Reply".localized(), image: UIImage(systemName: "arrowshape.turn.up.left"), handler: {(_) in
  4178. if self.removed {
  4179. return
  4180. }
  4181. if self.isSearching {
  4182. self.cancelAction()
  4183. }
  4184. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: {
  4185. self.handleReply(indexPath: indexPath!)
  4186. })
  4187. })
  4188. let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right"), handler: {(_) in
  4189. if self.removed {
  4190. return
  4191. }
  4192. if self.isSearching {
  4193. self.cancelAction()
  4194. }
  4195. if self.reffId != nil {
  4196. self.deleteReplyView()
  4197. }
  4198. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  4199. self.forwardSession = true
  4200. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  4201. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  4202. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  4203. self.navigationItem.rightBarButtonItems = nil
  4204. }
  4205. self.navigationItem.rightBarButtonItem = cancelButton
  4206. if self.isContactCenter || self.fromNotification {
  4207. self.navigationItem.leftBarButtonItem = nil
  4208. }
  4209. self.changeAppBar()
  4210. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4211. if idx != nil{
  4212. self.dataMessages[idx!]["isSelected"] = true
  4213. }
  4214. self.addMultipleSelectSession()
  4215. self.tableChatView.reloadData()
  4216. }
  4217. })
  4218. let copy = UIAction(title: "Copy".localized(), image: UIImage(systemName: "doc.on.doc"), handler: {(_) in
  4219. if self.removed {
  4220. return
  4221. }
  4222. if self.isSearching {
  4223. self.cancelAction()
  4224. }
  4225. if self.reffId != nil {
  4226. self.deleteReplyView()
  4227. }
  4228. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  4229. self.copySession = true
  4230. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  4231. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  4232. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  4233. self.navigationItem.rightBarButtonItems = nil
  4234. }
  4235. self.navigationItem.rightBarButtonItem = cancelButton
  4236. if self.isContactCenter || self.fromNotification {
  4237. self.navigationItem.leftBarButtonItem = nil
  4238. }
  4239. self.changeAppBar()
  4240. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4241. if idx != nil{
  4242. self.dataMessages[idx!]["isSelected"] = true
  4243. }
  4244. self.addMultipleSelectSession()
  4245. self.tableChatView.reloadData()
  4246. }
  4247. })
  4248. let edit = UIAction(title: "Edit".localized(), image: UIImage(systemName: "pencil.tip.crop.circle"), handler: {(_) in
  4249. self.isEditingMessage = true
  4250. self.showEditMessageView(at: indexPath!)
  4251. })
  4252. let translate = UIAction(title: "Translate".localized(), image: UIImage(systemName: "t.bubble"), handler: {(_) in
  4253. self.view.makeToast("Translating...".localized(), duration: 3)
  4254. var translation: String = "English"
  4255. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en"
  4256. if lang == "id" {
  4257. translation = "Indonesia"
  4258. }
  4259. let payload: [String : Any] = [
  4260. "role": "user",
  4261. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  4262. ]
  4263. let parameter: [String : Any] = [
  4264. "use_video": "0",
  4265. "translate": translation,
  4266. "payload": [payload]
  4267. ]
  4268. DispatchQueue.global().async {
  4269. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  4270. let response = response as? HTTPURLResponse
  4271. if response?.statusCode != 200 || error != nil {
  4272. DispatchQueue.main.async {
  4273. self.view.makeToast("There is an error occurred while translating your message. Please try again or check your network connection.".localized(), duration: 3)
  4274. }
  4275. return
  4276. }
  4277. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  4278. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
  4279. let dataContent = json["content"]!
  4280. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4281. if idx != nil{
  4282. self.dataMessages[idx!][TypeDataMessage.message_text] = (dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "") + "\n\n" + "$\(dataContent)$"
  4283. }
  4284. DispatchQueue.main.async{
  4285. self.tableChatView.reloadRows(at: [indexPath!], with: .none)
  4286. }
  4287. }
  4288. }
  4289. })
  4290. }
  4291. })
  4292. let gcs = UIAction(title: "Get Chat Suggestion".localized(), image: UIImage(systemName: "exclamationmark.bubble"), handler: {(_) in
  4293. self.view.makeToast("Getting chat suggestion...".localized(), duration: 3)
  4294. let payload: [String : Any] = [
  4295. "role": "user",
  4296. "content": dataMessages[indexPath!.row][TypeDataMessage.message_text]!!
  4297. ]
  4298. let parameter: [String : Any] = [
  4299. "use_video": "0",
  4300. "suggest": "1",
  4301. "payload": [payload]
  4302. ]
  4303. DispatchQueue.global().async {
  4304. Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getGPTBotUrl())!, parameter: parameter, completion: { data, response, error in
  4305. let response = response as? HTTPURLResponse
  4306. if response?.statusCode != 200 || error != nil {
  4307. DispatchQueue.main.async {
  4308. 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)
  4309. }
  4310. return
  4311. }
  4312. if let data = data, let responseString = String(data: data, encoding: .utf8) {
  4313. if let json = try? JSONSerialization.jsonObject(with: responseString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
  4314. if let dataMessage = json["message"] as? [[String: Any]] {
  4315. if let dataContent = dataMessage[0]["content"] as? String {
  4316. DispatchQueue.main.async{
  4317. self.textFieldSend.text = dataContent
  4318. self.textFieldSend.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black
  4319. }
  4320. }
  4321. }
  4322. }
  4323. }
  4324. })
  4325. }
  4326. })
  4327. let more = UIMenu(title: "More...".localized(), children: [translate, gcs])
  4328. let info = UIAction(title: "Info".localized(), image: UIImage(systemName: "info.circle"), handler: {(_) in
  4329. if self.removed {
  4330. return
  4331. }
  4332. let messageInfoVC = MessageInfo()
  4333. messageInfoVC.data = dataMessages[indexPath!.row]
  4334. messageInfoVC.dataPerson = self.dataPerson
  4335. self.navigationController?.pushViewController(messageInfoVC, animated: true)
  4336. })
  4337. let delete = UIAction(title: "Delete".localized(), image: UIImage(systemName: "trash"), attributes: .destructive, handler: {(_) in
  4338. if self.removed {
  4339. return
  4340. }
  4341. if self.isSearching {
  4342. self.cancelAction()
  4343. }
  4344. if self.reffId != nil {
  4345. self.deleteReplyView()
  4346. }
  4347. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
  4348. self.deleteSession = true
  4349. let cancelButton = UIBarButtonItem(title: "Cancel".localized(), style: .plain, target: self, action: #selector(self.cancelAction))
  4350. cancelButton.setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], for: .normal)
  4351. if self.dataPerson["f_pin"] != "-999" && !self.isContactCenter {
  4352. self.navigationItem.rightBarButtonItems = nil
  4353. }
  4354. self.navigationItem.rightBarButtonItem = cancelButton
  4355. if self.isContactCenter || self.fromNotification {
  4356. self.navigationItem.leftBarButtonItem = nil
  4357. }
  4358. self.changeAppBar()
  4359. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath!.row]["message_id"] as? String})
  4360. if idx != nil{
  4361. self.dataMessages[idx!]["isSelected"] = true
  4362. }
  4363. self.addMultipleSelectSession()
  4364. self.tableChatView.reloadData()
  4365. }
  4366. })
  4367. let resend = UIAction(title: "Resend".localized(), image: UIImage(systemName: "arrow.clockwise"), handler: {(_) in
  4368. let messageId = dataMessages[indexPath!.row][TypeDataMessage.message_id] as? String ?? ""
  4369. let status = dataMessages[indexPath!.row][TypeDataMessage.status] as? String ?? ""
  4370. var idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == messageId })
  4371. if let idxMessageIdParent = self.groupImages.firstIndex(where: { $0.value.contains(where: { $0.messageId == messageId }) }) {
  4372. if let idxInImages = self.groupImages[idxMessageIdParent].value.firstIndex(where: { $0.messageId == messageId }) {
  4373. self.groupImages[idxMessageIdParent].value[idxInImages].status = "1"
  4374. self.groupImages[idxMessageIdParent].value[idxInImages].dataMessage[TypeDataMessage.status] = "1"
  4375. }
  4376. idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == self.groupImages[idxMessageIdParent].key })
  4377. }
  4378. if (idx != nil) {
  4379. do {
  4380. self.dataMessages[idx!][TypeDataMessage.status] = "1"
  4381. self.dataMessages[idx!][TypeDataMessage.progress] = 0.0
  4382. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  4383. 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 })
  4384. if row != nil && section != nil {
  4385. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  4386. }
  4387. } catch {
  4388. }
  4389. }
  4390. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4391. do {
  4392. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4393. "status" : "1"
  4394. ], _where: "message_id = '\(messageId)'")
  4395. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE_STATUS", cvalues: [
  4396. "status" : "1"
  4397. ], _where: "message_id = '\(messageId)'")
  4398. } catch {
  4399. rollback.pointee = true
  4400. print("Access database error: \(error.localizedDescription)")
  4401. }
  4402. })
  4403. let message = CoreMessage_TMessageBank.sendMessage(message_id: messageId,
  4404. l_pin: dataMessages[indexPath!.row][TypeDataMessage.l_pin] as? String ?? "",
  4405. message_scope_id: dataMessages[indexPath!.row][TypeDataMessage.message_scope_id] as? String ?? "",
  4406. status: "1",
  4407. message_text: dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "",
  4408. credential: dataMessages[indexPath!.row][TypeDataMessage.credential] as? String ?? "",
  4409. attachment_flag: dataMessages[indexPath!.row][TypeDataMessage.attachment_flag] as? String ?? "",
  4410. ex_blog_id: dataMessages[indexPath!.row][TypeDataMessage.blog_id] as? String ?? "",
  4411. message_large_text: "",
  4412. ex_format: "",
  4413. image_id: dataMessages[indexPath!.row][TypeDataMessage.image_id] as? String ?? "",
  4414. audio_id: dataMessages[indexPath!.row][TypeDataMessage.audio_id] as? String ?? "",
  4415. video_id: dataMessages[indexPath!.row][TypeDataMessage.video_id] as? String ?? "",
  4416. file_id: dataMessages[indexPath!.row][TypeDataMessage.file_id] as? String ?? "",
  4417. thumb_id: dataMessages[indexPath!.row][TypeDataMessage.thumb_id] as? String ?? "",
  4418. reff_id: dataMessages[indexPath!.row][TypeDataMessage.reff_id] as? String ?? "",
  4419. read_receipts: dataMessages[indexPath!.row][TypeDataMessage.read_receipts] as? String ?? "",
  4420. chat_id: dataMessages[indexPath!.row][TypeDataMessage.chat_id] as? String ?? "",
  4421. is_call_center: dataMessages[indexPath!.row][TypeDataMessage.is_call_center] as? String ?? "",
  4422. call_center_id: dataMessages[indexPath!.row][TypeDataMessage.call_center_id] as? String ?? "",
  4423. opposite_pin: dataMessages[indexPath!.row][TypeDataMessage.opposite_pin] as? String ?? "")
  4424. Nexilis.addQueueMessage(message: message)
  4425. })
  4426. var children: [UIMenuElement] = [star, reply, forward, copy, delete]
  4427. var isMore = false
  4428. // let copyOption = self.copyOption(indexPath: indexPath!)
  4429. let idMe = User.getMyPin() as String?
  4430. if dataMessages[indexPath!.row]["status"] as? String ?? "" == "0" {
  4431. children = [resend, delete]
  4432. } else if isContactCenter {
  4433. if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  4434. children = [reply]
  4435. }
  4436. else {
  4437. children = [reply, copy]
  4438. }
  4439. } 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 {
  4440. children = [delete]
  4441. } else if (groupImages[dataMessages[indexPath!.row]["message_id"] as? String ?? ""] != nil) {
  4442. forward.title = "Forward All".localized()
  4443. delete.title = "Delete All".localized()
  4444. children = [forward, delete]
  4445. } else if blocking == "1" || blocking == "-1" {
  4446. children = [star, forward, copy ,delete]
  4447. if !(dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty {
  4448. children = [star, forward ,delete]
  4449. }
  4450. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4451. children.insert(info, at: children.count - 1)
  4452. }
  4453. }
  4454. else if !(dataMessages[indexPath!.row]["image_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["video_id"] as? String ?? "").isEmpty || !(dataMessages[indexPath!.row]["file_id"] as? String ?? "").isEmpty {
  4455. children = [star, reply, forward ,delete]
  4456. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4457. children.insert(info, at: children.count - 1)
  4458. }
  4459. } else if dataMessages[indexPath!.row]["attachment_flag"] as? String ?? "" == "11" {
  4460. children = [reply, delete]
  4461. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4462. children.insert(info, at: children.count - 1)
  4463. }
  4464. } else {
  4465. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe {
  4466. children.insert(info, at: children.count - 1)
  4467. }
  4468. if !(dataMessages[indexPath!.row][TypeDataMessage.message_text] as? String ?? "").isEmpty {
  4469. if (dataMessages[indexPath!.row]["f_pin"] as? String ?? "") == idMe && ((dataMessages[indexPath!.row][TypeDataMessage.is_forwarded] as? Int) ?? 0) == 0 {
  4470. let date = Date(milliseconds: Int64(dataMessages[indexPath!.row][TypeDataMessage.server_date] as? String ?? "") ?? 0)
  4471. let pastDate = date.addingTimeInterval(-10 * 60)
  4472. let differenceInSeconds = date.timeIntervalSince(pastDate)
  4473. if abs(differenceInSeconds) <= 15 * 60 {
  4474. children.insert(edit, at: children.count - 1)
  4475. }
  4476. }
  4477. isMore = true
  4478. }
  4479. }
  4480. let mainMenu = UIMenu(title: "", options: [.displayInline],
  4481. children: children)
  4482. var menuForShow = UIMenu(title: "", children: [mainMenu])
  4483. if isMore {
  4484. menuForShow = UIMenu(title: "", children: [mainMenu, more])
  4485. }
  4486. return UIContextMenuConfiguration(identifier: nil,
  4487. previewProvider: nil) { _ in
  4488. return menuForShow
  4489. }
  4490. }
  4491. func showEditMessageView(at indexPath: IndexPath) {
  4492. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  4493. let oldText = dataMessages[indexPath.row][TypeDataMessage.message_text] as? String ?? ""
  4494. editVC = UIViewController()
  4495. if let view = editVC.view {
  4496. view.backgroundColor = .clear
  4497. let blurView = UIView()
  4498. let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
  4499. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  4500. blurEffectView.frame = blurView.bounds
  4501. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  4502. blurView.addSubview(blurEffectView)
  4503. blurView.sendSubviewToBack(blurEffectView)
  4504. view.addSubview(blurView)
  4505. blurView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  4506. let tapGesture = ObjectGesture(target: self, action: #selector(dismissEditVC))
  4507. tapGesture.message_id = oldText
  4508. blurView.addGestureRecognizer(tapGesture)
  4509. editTextView = CustomTextView()
  4510. editTextView.layer.cornerRadius = textFieldSend.maxCornerRadius()
  4511. editTextView.layer.borderWidth = 1.0
  4512. editTextView.textColor = UIColor.black
  4513. editTextView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  4514. editTextView.textContainerInset = UIEdgeInsets(top: 12, left: 20, bottom: 11, right: 40)
  4515. editTextView.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.5).cgColor
  4516. editTextView.font = UIFont.systemFont(ofSize: 12 + offset())
  4517. editTextView.delegate = self
  4518. editTextView.allowsEditingTextAttributes = true
  4519. editTextView.backgroundColor = .clear
  4520. view.addSubview(editTextView)
  4521. editTextView.anchor(left: view.leftAnchor, right: view.rightAnchor, paddingLeft: 15, paddingRight: 15)
  4522. constraintBottomeditTextView = editTextView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  4523. constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
  4524. constraintBottomeditTextView.isActive = true
  4525. constraintHeighteditTextView.isActive = true
  4526. editTextView.attributedText = oldText.richText(isEditing: true)
  4527. editTextView.becomeFirstResponder()
  4528. 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)
  4529. buttonSendEdit.circle()
  4530. buttonSendEdit.isEnabled = true
  4531. buttonSendEdit.actionHandle(controlEvents: .touchUpInside,
  4532. ForAction:{() -> Void in
  4533. let newText = self.editTextView.text ?? ""
  4534. if !newText.isEmpty && newText.trimmingCharacters(in: .whitespacesAndNewlines) != oldText {
  4535. let lastEdited = Int64(Date().currentTimeMillis())
  4536. 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)
  4537. Nexilis.addQueueMessage(message: message, isEditMessage: true)
  4538. DispatchQueue.global().async {
  4539. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  4540. do {
  4541. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  4542. "message_text" : newText,
  4543. "last_edited" : lastEdited
  4544. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  4545. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  4546. } catch {
  4547. rollback.pointee = true
  4548. print("Access database error: \(error.localizedDescription)")
  4549. }
  4550. })
  4551. }
  4552. let idx = self.dataMessages.firstIndex(where: { $0[TypeDataMessage.message_id] as? String == dataMessages[indexPath.row][TypeDataMessage.message_id] as? String})
  4553. if idx != nil{
  4554. self.dataMessages[idx!][TypeDataMessage.message_text] = newText
  4555. self.dataMessages[idx!][TypeDataMessage.last_edit] = lastEdited
  4556. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  4557. }
  4558. }
  4559. self.isEditingMessage = false
  4560. self.editVC.dismiss(animated: true)
  4561. })
  4562. buttonSendEdit.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .mainColor
  4563. view.addSubview(buttonSendEdit)
  4564. buttonSendEdit.anchor(right: view.rightAnchor, paddingRight: 15, width: 40, height: 40)
  4565. constraintBottomSendEditTV = buttonSendEdit.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -15)
  4566. constraintBottomSendEditTV.isActive = true
  4567. let viewMessage = UIView()
  4568. view.addSubview(viewMessage)
  4569. viewMessage.translatesAutoresizingMaskIntoConstraints = false
  4570. if (dataMessages[indexPath.row][TypeDataMessage.f_pin] as? String == User.getMyPin()) {
  4571. viewMessage.leftAnchor.constraint(greaterThanOrEqualTo: view.leftAnchor, constant: 60).isActive = true
  4572. viewMessage.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15).isActive = true
  4573. viewMessage.backgroundColor = .blueBubbleColor
  4574. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  4575. } else {
  4576. viewMessage.rightAnchor.constraint(lessThanOrEqualTo: view.rightAnchor, constant: 60).isActive = true
  4577. viewMessage.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15).isActive = true
  4578. viewMessage.backgroundColor = .whiteBubbleColor
  4579. viewMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  4580. }
  4581. viewMessage.bottomAnchor.constraint(equalTo: editTextView.topAnchor, constant: -15).isActive = true
  4582. viewMessage.heightAnchor.constraint(greaterThanOrEqualToConstant: 44).isActive = true
  4583. viewMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  4584. viewMessage.layer.cornerRadius = 10.0
  4585. viewMessage.clipsToBounds = true
  4586. let messageText = UILabel()
  4587. messageText.numberOfLines = 0
  4588. messageText.lineBreakMode = .byWordWrapping
  4589. viewMessage.addSubview(messageText)
  4590. messageText.translatesAutoresizingMaskIntoConstraints = false
  4591. messageText.topAnchor.constraint(equalTo: viewMessage.topAnchor, constant: 15).isActive = true
  4592. messageText.leadingAnchor.constraint(equalTo: viewMessage.leadingAnchor, constant: 15).isActive = true
  4593. messageText.bottomAnchor.constraint(equalTo: viewMessage.bottomAnchor, constant: -15).isActive = true
  4594. messageText.trailingAnchor.constraint(equalTo: viewMessage.trailingAnchor, constant: -15).isActive = true
  4595. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  4596. messageText.font = .systemFont(ofSize: 12 + offset())
  4597. messageText.attributedText = oldText.richText()
  4598. }
  4599. editVC.modalTransitionStyle = .crossDissolve
  4600. editVC.modalPresentationStyle = .overFullScreen
  4601. self.present(editVC, animated: true, completion: {
  4602. self.constraintHeighteditTextView.constant = self.editTextView.contentSize.height
  4603. if self.constraintHeighteditTextView.constant > 95 {
  4604. self.constraintHeighteditTextView.constant = 95.0
  4605. }
  4606. })
  4607. }
  4608. @objc func dismissEditVC(_ sender: ObjectGesture) {
  4609. if editTextView.text == sender.message_id {
  4610. self.isEditingMessage = false
  4611. editVC.dismiss(animated: true)
  4612. } else if self.isEditingMessage {
  4613. let alert = LibAlertController(title: "".localized(), message: "Discard edit?".localized(), preferredStyle: .alert)
  4614. alert.addAction(UIAlertAction(title: "Cancel".localized(), style: UIAlertAction.Style.cancel, handler: nil))
  4615. alert.addAction(UIAlertAction(title: "Discard".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  4616. self.isEditingMessage = false
  4617. self.editVC.dismiss(animated: true)
  4618. }))
  4619. editVC.present(alert, animated: true, completion: nil)
  4620. } else {
  4621. editVC.dismiss(animated: true)
  4622. }
  4623. }
  4624. @objc func cancelAction() {
  4625. DispatchQueue.main.async {
  4626. if self.copySession {
  4627. self.copySession = false
  4628. } else if self.forwardSession {
  4629. self.forwardSession = false
  4630. } else if self.deleteSession {
  4631. self.deleteSession = false
  4632. } else if self.isSearching {
  4633. self.countMatchesSearch = 0
  4634. self.isSearching = false
  4635. }
  4636. if self.viewTextfield.isHidden {
  4637. self.viewTextfield.isHidden = false
  4638. }
  4639. if self.viewAttachment.isHidden {
  4640. self.viewAttachment.isHidden = false
  4641. }
  4642. if self.containerAction.isHidden {
  4643. self.containerAction.isHidden = false
  4644. }
  4645. if self.viewButton.isHidden {
  4646. self.viewButton.isHidden = false
  4647. }
  4648. if self.constraintBottomTableViewWithTextfield.constant == -60.0 {
  4649. self.constraintBottomTableViewWithTextfield.constant = self.constraintBottomTableViewWithTextfield.constant + 70
  4650. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  4651. if (self.currentIndexpath != nil) {
  4652. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: true)
  4653. } else {
  4654. self.tableChatView.scrollToBottom()
  4655. }
  4656. })
  4657. }
  4658. let data = self.dataMessages.filter({ $0["isSelected"] as? Bool == true })
  4659. for i in 0..<data.count {
  4660. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == data[i]["message_id"] as? String})
  4661. if idx != nil{
  4662. self.dataMessages[idx!]["isSelected"] = false
  4663. }
  4664. }
  4665. self.tableChatView.reloadData()
  4666. self.setRightButtonItem()
  4667. self.changeAppBar()
  4668. if self.isContactCenter || self.fromNotification {
  4669. let backButton = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: self, action: #selector(self.didTapExit))
  4670. self.navigationItem.leftBarButtonItem = backButton
  4671. }
  4672. self.containerMultpileSelectSession.removeFromSuperview()
  4673. self.checkNewMessage(tableView: self.tableChatView)
  4674. }
  4675. }
  4676. private func addMultipleSelectSession() {
  4677. viewTextfield.isHidden = true
  4678. viewAttachment.isHidden = true
  4679. containerAction.isHidden = true
  4680. viewButton.isHidden = true
  4681. constraintBottomTableViewWithTextfield.constant = constraintBottomTableViewWithTextfield.constant - 70
  4682. view.addSubview(containerMultpileSelectSession)
  4683. containerMultpileSelectSession.translatesAutoresizingMaskIntoConstraints = false
  4684. constraintBottomContainerMultpileSelectSession = containerMultpileSelectSession.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)
  4685. NSLayoutConstraint.activate([
  4686. containerMultpileSelectSession.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
  4687. containerMultpileSelectSession.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
  4688. constraintBottomContainerMultpileSelectSession,
  4689. containerMultpileSelectSession.heightAnchor.constraint(equalToConstant: 50)
  4690. ])
  4691. containerMultpileSelectSession.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .white
  4692. addSubviewMultipleSession()
  4693. }
  4694. private func addSubviewMultipleSession() {
  4695. let container = UIView()
  4696. containerMultpileSelectSession.addSubview(container)
  4697. container.translatesAutoresizingMaskIntoConstraints = false
  4698. NSLayoutConstraint.activate([
  4699. container.leadingAnchor.constraint(equalTo: containerMultpileSelectSession.leadingAnchor),
  4700. container.trailingAnchor.constraint(equalTo:containerMultpileSelectSession.trailingAnchor),
  4701. container.bottomAnchor.constraint(equalTo: containerMultpileSelectSession.bottomAnchor),
  4702. container.heightAnchor.constraint(equalToConstant: 50)
  4703. ])
  4704. container.layer.shadowOpacity = 0.7
  4705. container.layer.shadowOffset = CGSize(width: 3, height: 3)
  4706. container.layer.shadowRadius = 3.0
  4707. container.layer.shadowColor = UIColor.black.cgColor
  4708. container.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  4709. if !isSearching {
  4710. let title = UILabel()
  4711. container.addSubview(title)
  4712. title.translatesAutoresizingMaskIntoConstraints = false
  4713. NSLayoutConstraint.activate([
  4714. title.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  4715. title.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4716. ])
  4717. let countSelected = dataMessages.filter({ $0["isSelected"] as? Bool == true }).count
  4718. title.text = "\(countSelected) " + "Selected".localized()
  4719. title.textColor = .mainColor
  4720. title.font = UIFont.systemFont(ofSize: 15.0).bold
  4721. let button = UIImageView()
  4722. container.addSubview(button)
  4723. button.translatesAutoresizingMaskIntoConstraints = false
  4724. NSLayoutConstraint.activate([
  4725. button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 15),
  4726. button.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4727. button.widthAnchor.constraint(equalToConstant: 30),
  4728. button.heightAnchor.constraint(equalToConstant: 30),
  4729. ])
  4730. if copySession {
  4731. button.image = UIImage(systemName: "doc.on.doc")
  4732. if countSelected == 0 {
  4733. button.tintColor = .gray
  4734. } else {
  4735. button.tintColor = .mainColor
  4736. }
  4737. } else if forwardSession {
  4738. button.image = UIImage(systemName: "arrowshape.turn.up.right")
  4739. if countSelected == 0 {
  4740. button.tintColor = .gray
  4741. } else {
  4742. button.tintColor = .mainColor
  4743. }
  4744. } else if deleteSession {
  4745. button.image = UIImage(systemName: "trash")
  4746. if countSelected == 0 {
  4747. button.tintColor = .gray
  4748. } else {
  4749. button.tintColor = .red
  4750. }
  4751. }
  4752. let buttonGesture = UITapGestureRecognizer(target: self, action: #selector(sessionAction))
  4753. button.isUserInteractionEnabled = true
  4754. button.addGestureRecognizer(buttonGesture)
  4755. let selectedMessage = dataMessages.filter({ $0["isSelected"] as? Bool == true })
  4756. if selectedMessage.count > 0 {
  4757. for i in 0..<selectedMessage.count {
  4758. if let isGroupingImages = groupImages[selectedMessage[i]["message_id"] as? String ?? ""] {
  4759. title.text = "\(countSelected + (isGroupingImages.count - 1)) " + "Selected".localized()
  4760. }
  4761. }
  4762. }
  4763. } else {
  4764. buttonUp = UIButton()
  4765. container.addSubview(buttonUp)
  4766. buttonUp.translatesAutoresizingMaskIntoConstraints = false
  4767. NSLayoutConstraint.activate([
  4768. buttonUp.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10),
  4769. buttonUp.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4770. buttonUp.widthAnchor.constraint(equalToConstant: 30),
  4771. buttonUp.heightAnchor.constraint(equalToConstant: 30),
  4772. ])
  4773. buttonUp.addTarget(self, action: #selector(upSearchText), for: .touchUpInside)
  4774. buttonDown = UIButton()
  4775. container.addSubview(buttonDown)
  4776. buttonDown.translatesAutoresizingMaskIntoConstraints = false
  4777. NSLayoutConstraint.activate([
  4778. buttonDown.leadingAnchor.constraint(equalTo: buttonUp.trailingAnchor, constant: 15),
  4779. buttonDown.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4780. buttonDown.widthAnchor.constraint(equalToConstant: 30),
  4781. buttonDown.heightAnchor.constraint(equalToConstant: 30),
  4782. ])
  4783. buttonDown.addTarget(self, action: #selector(downSearchText), for: .touchUpInside)
  4784. buttonUp.setImage(UIImage(systemName: "chevron.up"), for: .normal)
  4785. buttonUp.tintColor = .gray
  4786. buttonDown.setImage(UIImage(systemName: "chevron.down"), for: .normal)
  4787. buttonDown.tintColor = .gray
  4788. titleSearchMatches = UILabel()
  4789. container.addSubview(titleSearchMatches)
  4790. titleSearchMatches.translatesAutoresizingMaskIntoConstraints = false
  4791. NSLayoutConstraint.activate([
  4792. titleSearchMatches.centerXAnchor.constraint(equalTo: container.centerXAnchor),
  4793. titleSearchMatches.centerYAnchor.constraint(equalTo:container.centerYAnchor),
  4794. ])
  4795. titleSearchMatches.textColor = .mainColor
  4796. titleSearchMatches.font = UIFont.systemFont(ofSize: 15.0).bold
  4797. titleSearchMatches.isHidden = true
  4798. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
  4799. self.searchBar.becomeFirstResponder()
  4800. })
  4801. }
  4802. }
  4803. @objc func upSearchText() {
  4804. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch + 1)
  4805. }
  4806. @objc func downSearchText() {
  4807. scrollToFirstSearchMessage(indexScroll: lastScrollIdxSearch - 1)
  4808. }
  4809. @objc func sessionAction() {
  4810. if copySession {
  4811. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as? Bool == true })
  4812. let countSelected = dataMessages.count
  4813. if countSelected == 0 {
  4814. return
  4815. }
  4816. var text = ""
  4817. for i in 0..<countSelected {
  4818. let stringDate = (dataMessages[i]["server_date"] as? String ?? "")
  4819. let date = Date(milliseconds: Int64(stringDate)!)
  4820. let formatterDate = DateFormatter()
  4821. let formatterTime = DateFormatter()
  4822. formatterDate.dateFormat = "dd/MM/yy"
  4823. formatterDate.locale = NSLocale(localeIdentifier: "id") as Locale?
  4824. formatterTime.dateFormat = "HH:mm"
  4825. formatterTime.locale = NSLocale(localeIdentifier: "id") as Locale?
  4826. let dataProfile = getDataProfile(message_id: dataMessages[i]["message_id"] as? String ?? "")
  4827. let textCopied = (dataMessages[i]["message_text"] as? String ?? "").richText(isEditing: true)
  4828. if text.isEmpty {
  4829. text = "*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  4830. } else {
  4831. text = text + "\n\n*[\(formatterDate.string(from: date as Date)) \(formatterTime.string(from: date as Date))] \(dataProfile["name"]!):*\n\(textCopied.string)"
  4832. }
  4833. }
  4834. text = text + "\n\n\nchat " + "Powered by Nexilis".localized()
  4835. DispatchQueue.main.async {
  4836. UIPasteboard.general.string = text
  4837. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  4838. }
  4839. cancelAction()
  4840. } else if forwardSession {
  4841. var dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  4842. let countSelected = dataMessages.count
  4843. if countSelected == 0 {
  4844. return
  4845. }
  4846. for i in 0..<countSelected {
  4847. if groupImages[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  4848. var tempData = dataMessages
  4849. tempData.remove(at: 0)
  4850. let dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  4851. tempData.insert(contentsOf: dataMessageInGrouping, at: i)
  4852. dataMessages = tempData
  4853. }
  4854. }
  4855. contactChatNav.modalPresentationStyle = .custom
  4856. contactChatNav.navigationBar.tintColor = .white
  4857. contactChatNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  4858. contactChatNav.navigationBar.isTranslucent = false
  4859. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  4860. contactChatNav.navigationBar.titleTextAttributes = textAttributes
  4861. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  4862. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  4863. contactChatNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  4864. if let controller = contactChatNav.viewControllers.first as? ContactChatViewController {
  4865. controller.isChooser = { [weak self] scope, pin in
  4866. if scope == MessageScope.WHISPER {
  4867. let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
  4868. editorPersonalVC.unique_l_pin = pin
  4869. editorPersonalVC.dataMessageForward = dataMessages
  4870. self?.navigationController?.replaceAllViewController(with: editorPersonalVC, animated: true)
  4871. } else {
  4872. let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup
  4873. editorGroupVC.unique_l_pin = pin
  4874. editorGroupVC.dataMessageForward = dataMessages
  4875. self?.navigationController?.replaceAllViewController(with: editorGroupVC, animated: true)
  4876. }
  4877. }
  4878. }
  4879. self.present(contactChatNav, animated: true, completion: nil)
  4880. } else if deleteSession {
  4881. let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
  4882. var countSelected = dataMessages.count
  4883. if countSelected == 0 {
  4884. return
  4885. }
  4886. for i in 0..<countSelected {
  4887. if let isGroupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  4888. countSelected += (isGroupingImages.count - 1)
  4889. }
  4890. }
  4891. let alertController = LibAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  4892. if let action = self.actionDelete(for: "me", title: "Delete".localized() + " \(countSelected) " + "For Me".localized(), dataMessages: dataMessages) {
  4893. alertController.addAction(action)
  4894. }
  4895. let idMe = User.getMyPin() as String?
  4896. let dataFilterFpin = dataMessages.filter({ $0["l_pin"] as? String == idMe})
  4897. let dataFilterLock = dataMessages.filter({ $0["lock"] as? String == "1" || $0["lock"] as? String == "2" })
  4898. let dataFilterCall = dataMessages.filter({ $0[TypeDataMessage.message_scope_id] as? String == MessageScope.CALL || $0[TypeDataMessage.message_scope_id] as? String == MessageScope.MISSED_CALL })
  4899. // let statusDataRead = dataMessages.filter({ Int($0["status"] as? String ?? "")! >= 4})
  4900. let statusFailed = dataMessages.filter({ Int($0["status"] as? String ?? "")! == 0})
  4901. if dataFilterFpin.count == 0 && dataFilterLock.count == 0 && statusFailed.count == 0 && dataFilterCall.count == 0 {
  4902. if let action = self.actionDelete(for: "everyone", title: "Delete".localized() + " \(countSelected) " + "For Everyone".localized(), dataMessages: dataMessages) {
  4903. alertController.addAction(action)
  4904. }
  4905. }
  4906. alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
  4907. self.present(alertController, animated: true)
  4908. }
  4909. }
  4910. private func getDataProfile(message_id: String) -> [String: String]{
  4911. var data: [String: String] = [:]
  4912. Database().database?.inTransaction({ fmdb, rollback in
  4913. if let c = Database().getRecords(fmdb: fmdb, query: "select f_display_name from MESSAGE where message_id = '\(message_id)'"), c.next() {
  4914. data["name"] = c.string(forColumnIndex: 0)!
  4915. c.close()
  4916. } else {
  4917. data["name"] = "Unknown".localized()
  4918. data["image_id"] = ""
  4919. }
  4920. })
  4921. return data
  4922. }
  4923. private func deleteMessage(l_pin: String, message_id: String, scope: String, type: String, chat: String) {
  4924. let tmessage = CoreMessage_TMessageBank.deleteMessage(l_pin: l_pin, messageId: message_id, scope: scope, type: type, chat: chat)
  4925. Nexilis.deleteQueueMessage(message: tmessage)
  4926. }
  4927. private func queryMessageReply(message_id: String) -> [String: Any?] {
  4928. var dataQuery: [String: Any] = [:]
  4929. Database().database?.inTransaction({ fmdb, rollback in
  4930. 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() {
  4931. dataQuery["message_id"] = c.string(forColumnIndex: 0) ?? ""
  4932. dataQuery["f_pin"] = c.string(forColumnIndex: 1) ?? ""
  4933. dataQuery["message_text"] = c.string(forColumnIndex: 2) ?? ""
  4934. dataQuery["attachment_flag"] = c.string(forColumnIndex: 3) ?? ""
  4935. dataQuery["thumb_id"] = c.string(forColumnIndex: 4) ?? ""
  4936. dataQuery["image_id"] = c.string(forColumnIndex: 5) ?? ""
  4937. dataQuery["video_id"] = c.string(forColumnIndex: 6) ?? ""
  4938. dataQuery["file_id"] = c.string(forColumnIndex: 7) ?? ""
  4939. c.close()
  4940. }
  4941. })
  4942. return dataQuery
  4943. }
  4944. @objc func segmentedControlValueChanged(_ sender: segmentedControllerObject) {
  4945. switch sender.selectedSegmentIndex {
  4946. case 0:
  4947. sender.navigation.viewControllers[0].children[1].view.isHidden = true
  4948. break;
  4949. case 1:
  4950. sender.navigation.viewControllers[0].children[1].view.isHidden = false
  4951. break;
  4952. default:
  4953. break;
  4954. }
  4955. }
  4956. private func copyOption(indexPath: IndexPath) -> UIMenu {
  4957. var ratingButtonTitles = ["Text".localized(), "Image".localized()]
  4958. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  4959. ratingButtonTitles = ["Image".localized()]
  4960. }
  4961. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  4962. let copyActions = ratingButtonTitles
  4963. .enumerated()
  4964. .map { index, title in
  4965. return UIAction(
  4966. title: title,
  4967. identifier: nil,
  4968. handler: {(_) in
  4969. if (dataMessages[indexPath.row]["message_text"] as? String ?? "").isEmpty {
  4970. DispatchQueue.main.async {
  4971. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  4972. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  4973. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  4974. if let dirPath = paths.first {
  4975. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  4976. if FileManager.default.fileExists(atPath: imageURL.path) {
  4977. let image = UIImage(contentsOfFile: imageURL.path)
  4978. UIPasteboard.general.image = image
  4979. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4980. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  4981. do {
  4982. if var imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  4983. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  4984. if dataDecrypt != nil {
  4985. imageData = dataDecrypt!
  4986. }
  4987. let image = UIImage(data: imageData)
  4988. UIPasteboard.general.image = image
  4989. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  4990. }
  4991. } catch {
  4992. }
  4993. }
  4994. }
  4995. }
  4996. return
  4997. }
  4998. if (index == 0) {
  4999. DispatchQueue.main.async {
  5000. UIPasteboard.general.string = dataMessages[indexPath.row]["message_text"] as? String
  5001. self.view.makeToast("Text coppied to clipboard".localized(), duration: 3)
  5002. }
  5003. } else {
  5004. DispatchQueue.main.async {
  5005. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5006. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5007. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5008. if let dirPath = paths.first {
  5009. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(dataMessages[indexPath.row]["image_id"] as? String ?? "")
  5010. if FileManager.default.fileExists(atPath: imageURL.path) {
  5011. let image = UIImage(contentsOfFile: imageURL.path)
  5012. UIPasteboard.general.image = image
  5013. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  5014. } else if FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  5015. do {
  5016. if var imageData = try FileEncryption.shared.readSecure(filename: imageURL.lastPathComponent) {
  5017. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  5018. if dataDecrypt != nil {
  5019. imageData = dataDecrypt!
  5020. }
  5021. let image = UIImage(data: imageData)
  5022. UIPasteboard.general.image = image
  5023. self.view.makeToast("Image coppied to clipboard".localized(), duration: 3)
  5024. }
  5025. } catch {
  5026. }
  5027. }
  5028. }
  5029. }
  5030. }
  5031. self.dismissKeyboard()
  5032. })
  5033. }
  5034. return UIMenu(
  5035. title: "Copy".localized(),
  5036. image: UIImage(systemName: "doc.on.doc.fill"),
  5037. children: copyActions)
  5038. }
  5039. private func actionDelete(for type: String, title: String, dataMessages: [[String: Any?]]) -> UIAlertAction? {
  5040. return UIAlertAction(title: title, style: .destructive) { [unowned self] _ in
  5041. for i in 0..<dataMessages.count {
  5042. if (type == "me") {
  5043. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  5044. for i in 0..<groupingImages.count {
  5045. self.deleteMessage(l_pin: groupingImages[i].lPin, message_id: groupingImages[i].messageId, scope: MessageScope.WHISPER, type: "1", chat: "")
  5046. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId })
  5047. if idx != nil {
  5048. self.dataMessages.remove(at: idx!)
  5049. if (idx == self.dataMessages.count - 1) {
  5050. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  5051. }
  5052. for i in 0..<dataDates.count {
  5053. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  5054. dataDates.remove(at: i)
  5055. }
  5056. }
  5057. }
  5058. }
  5059. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  5060. } else {
  5061. self.deleteMessage(l_pin: dataMessages[i]["l_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: MessageScope.WHISPER, type: "1", chat: "")
  5062. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  5063. if idx != nil {
  5064. self.dataMessages.remove(at: idx!)
  5065. if (idx == self.dataMessages.count - 1) {
  5066. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  5067. }
  5068. for i in 0..<dataDates.count {
  5069. if self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[i] }).count == 0 {
  5070. dataDates.remove(at: i)
  5071. }
  5072. }
  5073. }
  5074. }
  5075. } else {
  5076. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  5077. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  5078. imageView.tintColor = .white
  5079. 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)
  5080. banner.show()
  5081. } else {
  5082. if let groupingImages = groupImages[dataMessages[i]["message_id"] as? String ?? ""] {
  5083. for i in 0..<groupingImages.count {
  5084. self.deleteMessage(l_pin: groupingImages[i].lPin, message_id: groupingImages[i].messageId, scope: MessageScope.WHISPER, type: "2", chat: "")
  5085. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[i].messageId})
  5086. if idx != nil {
  5087. self.dataMessages[idx!]["lock"] = "1"
  5088. self.dataMessages[idx!]["attachment_flag"] = "0"
  5089. self.dataMessages[idx!]["reff_id"] = ""
  5090. }
  5091. }
  5092. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == groupingImages[0].messageId}) {
  5093. var dataMessageInGrouping = (groupImages[dataMessages[i]["message_id"] as? String ?? ""]!).map({ $0.dataMessage })
  5094. dataMessageInGrouping.remove(at: 0)
  5095. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx+1)
  5096. self.groupImages.removeValue(forKey: groupingImages[0].messageId)
  5097. }
  5098. } else {
  5099. self.deleteMessage(l_pin: dataMessages[i]["l_pin"] as? String ?? "", message_id: dataMessages[i]["message_id"] as? String ?? "", scope: MessageScope.WHISPER, type: "2", chat: "")
  5100. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[i]["message_id"] as? String})
  5101. if idx != nil {
  5102. self.dataMessages[idx!]["lock"] = "1"
  5103. self.dataMessages[idx!]["attachment_flag"] = "0"
  5104. self.dataMessages[idx!]["reff_id"] = ""
  5105. }
  5106. }
  5107. }
  5108. }
  5109. if self.listTimerCredential[dataMessages[i]["message_id"] as? String ?? ""] != nil {
  5110. self.listTimerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  5111. self.timerCredential[dataMessages[i]["message_id"] as? String ?? ""]?.invalidate()
  5112. self.timerCredential.removeValue(forKey: dataMessages[i]["message_id"] as? String ?? "")
  5113. }
  5114. }
  5115. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
  5116. cancelAction()
  5117. }
  5118. }
  5119. private func updateProfile() {
  5120. let idMe = User.getMyPin() as String?
  5121. DispatchQueue.global().async {
  5122. let message = CoreMessage_TMessageBank.getBatchBuddiesInfos(p_f_pin: idMe!, last_update: 0)
  5123. let _ = Nexilis.write(message: message)
  5124. }
  5125. }
  5126. private func generateQRCode(from string: String) -> UIImage? {
  5127. let data = string.data(using: String.Encoding.ascii)
  5128. if let filter = CIFilter(name: "CIQRCodeGenerator") {
  5129. filter.setValue(data, forKey: "inputMessage")
  5130. let transform = CGAffineTransform(scaleX: 3, y: 3)
  5131. if let output = filter.outputImage?.transformed(by: transform) {
  5132. return UIImage(ciImage: output)
  5133. }
  5134. }
  5135. return nil
  5136. }
  5137. @objc func deleteReplyView() {
  5138. if self.containerPreviewReply.isDescendant(of: self.viewTextfield) {
  5139. self.containerPreviewReply.subviews.forEach { $0.removeFromSuperview() }
  5140. self.containerPreviewReply.removeConstraints(self.containerPreviewReply.constraints)
  5141. self.containerPreviewReply.removeFromSuperview()
  5142. self.reffId = nil
  5143. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  5144. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 50
  5145. }, completion: nil)
  5146. }
  5147. }
  5148. @objc func removeLinkPreviewUntilEmptyTextView() {
  5149. isAlwaysHideLinkPreview = true
  5150. deleteLinkPreview()
  5151. }
  5152. @objc func deleteLinkPreview() {
  5153. if self.containerLink.isDescendant(of: self.viewTextfield) {
  5154. self.containerLink.subviews.forEach { $0.removeFromSuperview() }
  5155. self.containerLink.removeConstraints(self.containerLink.constraints)
  5156. self.containerLink.removeFromSuperview()
  5157. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  5158. self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
  5159. }, completion: nil)
  5160. self.showingLink = ""
  5161. }
  5162. if self.reffId != nil {
  5163. self.bottomAnchorPreviewReply.isActive = false
  5164. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  5165. self.bottomAnchorPreviewReply.isActive = true
  5166. }
  5167. }
  5168. }
  5169. //ECL
  5170. extension EditorPersonal: UICollectionViewDelegate, UICollectionViewDataSource {
  5171. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  5172. return 76
  5173. }
  5174. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  5175. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellSticker", for: indexPath)
  5176. if (cell.contentView.subviews.count > 0) {
  5177. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  5178. }
  5179. let imageSticker = UIImageView()
  5180. cell.contentView.addSubview(imageSticker)
  5181. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  5182. NSLayoutConstraint.activate([
  5183. imageSticker.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
  5184. imageSticker.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor),
  5185. imageSticker.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor),
  5186. imageSticker.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor)
  5187. ])
  5188. var imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5189. if imageStickerBundle == nil {
  5190. imageStickerBundle = UIImage(named: stickers[indexPath.row], in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  5191. }
  5192. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  5193. return cell
  5194. }
  5195. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  5196. sendChat(message_text: "sticker/\(stickers[indexPath.row])", attachment_flag: "11", viewController: self)
  5197. constraintBottomAttachment.constant = 0.0
  5198. self.viewSticker.removeConstraints(self.viewSticker.constraints)
  5199. self.viewSticker.removeFromSuperview()
  5200. }
  5201. }
  5202. //ETB
  5203. extension EditorPersonal: UITableViewDelegate, UITableViewDataSource, AVAudioPlayerDelegate {
  5204. // public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  5205. // checkNewMessage(tableView: tableView)
  5206. // }
  5207. public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  5208. if tableChatView.alpha != 1 {
  5209. DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
  5210. UIView.animate(withDuration: 0.5, animations: {
  5211. self.tableChatView.alpha = 1.0
  5212. })
  5213. })
  5214. }
  5215. }
  5216. public func scrollViewDidScroll(_ scrollView: UIScrollView) {
  5217. lastY = scrollView.contentOffset.y
  5218. DispatchQueue.main.async { [self] in
  5219. if tableChatView.alpha != 1 {
  5220. return
  5221. }
  5222. checkNewMessage(tableView: self.tableChatView)
  5223. }
  5224. }
  5225. public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  5226. if isContactCenter && indexPath.row == 0 && isRequestContactCenter {
  5227. return
  5228. }
  5229. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section] })
  5230. if copySession || forwardSession || deleteSession {
  5231. if (dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" != "0" || dataMessages[indexPath.row]["lock"] as? String == "1") && !forwardSession && !deleteSession {
  5232. return
  5233. }
  5234. 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 {
  5235. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  5236. if file.isEmpty {
  5237. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  5238. if file.isEmpty {
  5239. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  5240. }
  5241. }
  5242. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5243. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5244. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5245. if let dirPath = paths.first {
  5246. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  5247. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  5248. return
  5249. }
  5250. }
  5251. }
  5252. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String})
  5253. if idx != nil {
  5254. self.dataMessages[idx!]["isSelected"] = !(self.dataMessages[idx!]["isSelected"] as! Bool)
  5255. self.tableChatView.reloadRows(at: [indexPath], with: .none)
  5256. }
  5257. containerMultpileSelectSession.subviews.forEach({ $0.removeFromSuperview() })
  5258. addSubviewMultipleSession()
  5259. return
  5260. }
  5261. let message = dataMessages[indexPath.row]
  5262. if let attachmentFlag = message["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  5263. if attachmentFlag == "27" || attachmentFlag == "26" {
  5264. let streamingController = (attachmentFlag == "27") ? QmeraCreateStreamingViewController() : CreateSeminarViewController()
  5265. if let messageText = message["message_text"],
  5266. let messageText = messageText as? String,
  5267. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5268. if json["blog"] == nil {
  5269. json["blog"] = message["blog_id"] ?? nil
  5270. }
  5271. switch(attachmentFlag){
  5272. case "27":
  5273. (streamingController as! QmeraCreateStreamingViewController).data = json
  5274. default:
  5275. (streamingController as! CreateSeminarViewController).data = json
  5276. }
  5277. if json["by"] as? String != User.getMyPin() as String? {
  5278. switch(attachmentFlag){
  5279. case "27":
  5280. (streamingController as! QmeraCreateStreamingViewController).isJoin = true
  5281. default:
  5282. (streamingController as! CreateSeminarViewController).isJoin = true
  5283. }
  5284. }
  5285. }
  5286. let streamingNav = CustomNavigationController(rootViewController: streamingController)
  5287. streamingNav.modalPresentationStyle = .custom
  5288. streamingNav.navigationBar.tintColor = .white
  5289. streamingNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5290. streamingNav.navigationBar.isTranslucent = false
  5291. streamingNav.navigationBar.overrideUserInterfaceStyle = .dark
  5292. streamingNav.navigationBar.barStyle = .black
  5293. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  5294. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  5295. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  5296. streamingNav.navigationBar.titleTextAttributes = textAttributes
  5297. streamingNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5298. streamingNav.navigationBar.isTranslucent = false
  5299. navigationController?.present(streamingNav, animated: true, completion: nil)
  5300. } else if attachmentFlag == "25" {
  5301. let conferenceController = CreateSeminarViewController()
  5302. if let messageText = message["message_text"],
  5303. let messageText = messageText as? String,
  5304. var json = try! JSONSerialization.jsonObject(with: messageText.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5305. if json["blog"] == nil {
  5306. json["blog"] = message["blog"] ?? nil
  5307. }
  5308. json["participant"] = message["members"]
  5309. let start = json["time"] as? Int64 ?? 0
  5310. json["start"] = String(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))
  5311. conferenceController.data = json
  5312. conferenceController.isJoin = true
  5313. }
  5314. let conferenceNav = CustomNavigationController(rootViewController: conferenceController)
  5315. conferenceNav.modalPresentationStyle = .custom
  5316. conferenceNav.navigationBar.tintColor = .white
  5317. conferenceNav.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5318. conferenceNav.navigationBar.isTranslucent = false
  5319. conferenceNav.navigationBar.overrideUserInterfaceStyle = .dark
  5320. conferenceNav.navigationBar.barStyle = .black
  5321. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  5322. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  5323. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  5324. conferenceNav.navigationBar.titleTextAttributes = textAttributes
  5325. conferenceNav.view.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  5326. conferenceNav.navigationBar.isTranslucent = false
  5327. navigationController?.present(conferenceNav, animated: true, completion: nil)
  5328. } else if message["message_scope_id"] as? String == "18" {
  5329. let formView = FormEditor()
  5330. let messageText = message["message_text"] as? String ?? ""
  5331. formView.jsonData = messageText
  5332. formView.dataMessage = message
  5333. formView.dataPerson = self.dataPerson
  5334. formView.modalPresentationStyle = .custom
  5335. formView.modalTransitionStyle = .crossDissolve
  5336. formView.view.backgroundColor = .black.withAlphaComponent(0.2)
  5337. self.present(formView, animated: true, completion: nil)
  5338. }
  5339. }
  5340. }
  5341. public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  5342. let containerView = UIView()
  5343. containerView.backgroundColor = .clear
  5344. let dateView = UIView()
  5345. containerView.addSubview(dateView)
  5346. dateView.translatesAutoresizingMaskIntoConstraints = false
  5347. var topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor)
  5348. topAnchor = dateView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10.0)
  5349. NSLayoutConstraint.activate([
  5350. topAnchor,
  5351. dateView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10.0),
  5352. dateView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
  5353. // dateView.heightAnchor.constraint(equalToConstant: 30),
  5354. dateView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  5355. ])
  5356. dateView.backgroundColor = .orangeColor
  5357. dateView.layer.cornerRadius = 15.0
  5358. dateView.clipsToBounds = true
  5359. let labelDate = UILabel()
  5360. dateView.addSubview(labelDate)
  5361. labelDate.translatesAutoresizingMaskIntoConstraints = false
  5362. NSLayoutConstraint.activate([
  5363. labelDate.centerYAnchor.constraint(equalTo: dateView.centerYAnchor),
  5364. labelDate.centerXAnchor.constraint(equalTo: dateView.centerXAnchor),
  5365. labelDate.leadingAnchor.constraint(equalTo: dateView.leadingAnchor, constant: 10),
  5366. labelDate.trailingAnchor.constraint(equalTo: dateView.trailingAnchor, constant: -10),
  5367. ])
  5368. labelDate.textAlignment = .center
  5369. labelDate.textColor = .secondaryColor
  5370. labelDate.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  5371. labelDate.text = dataDates[section]
  5372. if listViewOnSection.count == 0 || listViewOnSection.count - 1 < section {
  5373. listViewOnSection.append(containerView)
  5374. } else {
  5375. listViewOnSection.remove(at: section)
  5376. listViewOnSection.insert(containerView, at: section)
  5377. }
  5378. return containerView
  5379. }
  5380. public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  5381. return 50
  5382. }
  5383. public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  5384. let idMe = User.getMyPin() as String?
  5385. let dataMessages = dataMessages.filter({$0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  5386. let profileMessage = UIImageView()
  5387. let cell = tableView.dequeueReusableCell(withIdentifier: "cellEditorPersonal", for: indexPath as IndexPath)
  5388. cell.contentView.subviews.forEach({ $0.removeConstraints($0.constraints) })
  5389. cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
  5390. if isContactCenter && isRequestContactCenter && dataMessages[indexPath.row]["category_cc"] != nil {
  5391. cell.backgroundColor = .clear
  5392. cell.selectionStyle = .none
  5393. if dataMessages[indexPath.row]["category_cc"] is [CategoryCC] {
  5394. let category_cc = dataMessages[indexPath.row]["category_cc"] as! [CategoryCC]
  5395. profileMessage.frame.size = CGSize(width: 35, height: 35)
  5396. cell.contentView.addSubview(profileMessage)
  5397. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  5398. profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5399. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5400. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  5401. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  5402. profileMessage.circle()
  5403. profileMessage.clipsToBounds = true
  5404. profileMessage.backgroundColor = .lightGray
  5405. profileMessage.image = UIImage(systemName: "person")
  5406. profileMessage.tintColor = .white
  5407. profileMessage.contentMode = .scaleAspectFit
  5408. getImage(name: dataPerson["picture"]!!, placeholderImage: UIImage(systemName: "person.circle.fill")!) { result, isDownloaded, image in
  5409. profileMessage.image = image
  5410. }
  5411. profileMessage.contentMode = .scaleAspectFill
  5412. let containerMessage = UIView()
  5413. cell.contentView.addSubview(containerMessage)
  5414. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  5415. containerMessage.topAnchor.constraint(equalTo: profileMessage.bottomAnchor).isActive = true
  5416. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 5).isActive = true
  5417. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
  5418. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5419. // containerMessage.backgroundColor = .grayColor
  5420. // containerMessage.layer.cornerRadius = 10.0
  5421. // containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  5422. // containerMessage.clipsToBounds = true
  5423. // let timeMessage = UILabel()
  5424. // cell.contentView.addSubview(timeMessage)
  5425. // timeMessage.translatesAutoresizingMaskIntoConstraints = false
  5426. // timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  5427. let messageText = UILabel()
  5428. containerMessage.addSubview(messageText)
  5429. messageText.translatesAutoresizingMaskIntoConstraints = false
  5430. messageText.numberOfLines = 0
  5431. messageText.lineBreakMode = .byWordWrapping
  5432. containerMessage.addSubview(messageText)
  5433. messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 5).isActive = true
  5434. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5435. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5436. if category_cc[0].id.contains("level0_") || dataMessages[indexPath.row]["attachment_flag"] != nil && dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "503" {
  5437. messageText.text = "Welcome to".localized() + " " + dataPerson["name"]!! + " " + "Contact Center".localized()
  5438. + "\n" + "Please choose your desired communication method...".localized()
  5439. } else if category_cc[0].id.contains("level1_") {
  5440. messageText.text = "Please select your Consultation Topic:".localized()
  5441. } else if !category_cc[0].id.contains("level1_") && dataMessages[indexPath.row]["attachment_flag"] == nil {
  5442. messageText.text = "Please select the type of topic that you chosen".localized()
  5443. } else if dataMessages[indexPath.row]["attachment_flag"] != nil && dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "502" {
  5444. messageText.text = "Please select the information option:".localized()
  5445. } else {
  5446. 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()
  5447. }
  5448. messageText.font = UIFont.systemFont(ofSize: 14 + offset(), weight: .medium)
  5449. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5450. // let date = Date()
  5451. // let formatter = DateFormatter()
  5452. // formatter.dateFormat = "HH:mm"
  5453. // formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  5454. // timeMessage.text = formatter.string(from: date as Date)
  5455. // timeMessage.font = UIFont.systemFont(ofSize: 10, weight: .medium)
  5456. // timeMessage.textColor = .lightGray
  5457. let containerButton = UIView()
  5458. cell.contentView.addSubview(containerButton)
  5459. containerButton.translatesAutoresizingMaskIntoConstraints = false
  5460. containerButton.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  5461. containerButton.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  5462. containerButton.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5463. containerButton.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width * 0.9).isActive = true
  5464. containerButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 55).isActive = true
  5465. containerButton.backgroundColor = .clear
  5466. for i in 0..<category_cc.count {
  5467. let buttonChat = UIButton(type: .custom)
  5468. containerButton.addSubview(buttonChat)
  5469. buttonChat.translatesAutoresizingMaskIntoConstraints = false
  5470. buttonChat.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width * 0.9 / 2 - 5).isActive = true
  5471. buttonChat.heightAnchor.constraint(greaterThanOrEqualToConstant: 55).isActive = true
  5472. if i % 2 == 0 {
  5473. if i / 2 + 1 == 1 {
  5474. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: 5).isActive = true
  5475. } else {
  5476. var constantTop = (i / 2 - 1) * 50
  5477. if constantTop == 0 {
  5478. constantTop = 55
  5479. } else {
  5480. constantTop = constantTop + 55
  5481. }
  5482. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: CGFloat(constantTop)).isActive = true
  5483. }
  5484. if i == category_cc.count - 1 {
  5485. buttonChat.bottomAnchor.constraint(equalTo: containerButton.bottomAnchor, constant: -5).isActive = true
  5486. }
  5487. buttonChat.leadingAnchor.constraint(equalTo: containerButton.leadingAnchor, constant: 5).isActive = true
  5488. } else {
  5489. let newi = i - 1
  5490. if newi / 2 + 1 == 1 {
  5491. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: 5).isActive = true
  5492. } else {
  5493. var constantTop = (newi / 2 - 1) * 50
  5494. if constantTop == 0 {
  5495. constantTop = 55
  5496. } else {
  5497. constantTop = constantTop + 55
  5498. }
  5499. buttonChat.topAnchor.constraint(equalTo: containerButton.topAnchor, constant: CGFloat(constantTop)).isActive = true
  5500. }
  5501. if i == category_cc.count - 1 {
  5502. buttonChat.bottomAnchor.constraint(equalTo: containerButton.bottomAnchor, constant: -5).isActive = true
  5503. }
  5504. buttonChat.trailingAnchor.constraint(equalTo: containerButton.trailingAnchor, constant: -5).isActive = true
  5505. }
  5506. if category_cc[i].isActive {
  5507. buttonChat.backgroundColor = .orangeBNI
  5508. }
  5509. var nameImage = "pb_cc_bg_messaging"
  5510. if i == 1 {
  5511. nameImage = "pb_cc_bg_sms"
  5512. } else if i == 2 {
  5513. nameImage = "pb_cc_bg_voip"
  5514. } else if i == 3 {
  5515. nameImage = "pb_cc_bg_email"
  5516. } else if i == 4 {
  5517. nameImage = "pb_cc_bg_videocall"
  5518. } else if i == 5 {
  5519. nameImage = "pb_cc_bg_gsmcall"
  5520. } else if i == 6 {
  5521. nameImage = "pb_cc_bg_gptchatbot"
  5522. } else if i == 7 {
  5523. nameImage = "pb_cc_bg_whatsapp"
  5524. }
  5525. 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)
  5526. // buttonChat.setTitle(category_cc[i].service_name.localized(), for: .normal)
  5527. // buttonChat.setTitleColor(.black, for: .normal)
  5528. // 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)
  5529. // buttonChat.contentHorizontalAlignment = .left
  5530. // buttonChat.imageEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) // Adjust left inset for the image
  5531. // buttonChat.titleEdgeInsets = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) // Adjust left inset for the title
  5532. // buttonChat.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
  5533. // buttonChat.titleLabel?.numberOfLines = 0
  5534. // buttonChat.layer.borderWidth = 2
  5535. // buttonChat.layer.borderColor = UIColor.white.cgColor
  5536. // buttonChat.backgroundColor = .grayColor
  5537. // buttonChat.layer.cornerRadius = 8.0
  5538. // buttonChat.clipsToBounds = true
  5539. buttonChat.restorationIdentifier = "\(category_cc[i].id),\(category_cc[i].service_id)"
  5540. if dataMessages[indexPath.row]["attachment_flag"] != nil {
  5541. buttonChat.tag = Int(dataMessages[indexPath.row]["attachment_flag"] as? String ?? "")!
  5542. }
  5543. buttonChat.addTarget(self, action: #selector(ccAction(sender:)), for: .touchUpInside)
  5544. }
  5545. } else {
  5546. let messageWait = UILabel()
  5547. cell.contentView.addSubview(messageWait)
  5548. messageWait.translatesAutoresizingMaskIntoConstraints = false
  5549. messageWait.topAnchor.constraint(equalTo: cell.contentView.topAnchor).isActive = true
  5550. messageWait.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor).isActive = true
  5551. messageWait.leftAnchor.constraint(equalTo: cell.contentView.leftAnchor, constant: 10).isActive = true
  5552. messageWait.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor, constant: -10).isActive = true
  5553. messageWait.text = dataMessages[indexPath.row]["category_cc"] as? String ?? dataMessages[indexPath.row]["message_text"] as? String ?? ""
  5554. messageWait.numberOfLines = 0
  5555. messageWait.font = UIFont.systemFont(ofSize: 12 + offset())
  5556. messageWait.textColor = .gray
  5557. messageWait.textAlignment = .center
  5558. }
  5559. return cell
  5560. }
  5561. let messageIdChat = (dataMessages[indexPath.row]["message_id"] as? String) ?? ""
  5562. let thumbChat = (dataMessages[indexPath.row]["thumb_id"] as? String) ?? ""
  5563. let imageChat = (dataMessages[indexPath.row]["image_id"] as? String) ?? ""
  5564. let videoChat = (dataMessages[indexPath.row]["video_id"] as? String) ?? ""
  5565. let fileChat = (dataMessages[indexPath.row]["file_id"] as? String) ?? ""
  5566. let reffChat = (dataMessages[indexPath.row]["reff_id"] as? String) ?? ""
  5567. let audioChat = (dataMessages[indexPath.row]["audio_id"] as? String) ?? ""
  5568. let gifChat = (dataMessages[indexPath.row]["gif_id"] as? String) ?? ""
  5569. let dataTimer = listTimerCredential[(dataMessages[indexPath.row]["message_id"] as? String ?? "")]
  5570. cell.backgroundColor = .clear
  5571. cell.selectionStyle = .none
  5572. let nameSender = UILabel()
  5573. if isContactCenter {
  5574. profileMessage.frame.size = CGSize(width: 35, height: 35)
  5575. cell.contentView.addSubview(profileMessage)
  5576. profileMessage.translatesAutoresizingMaskIntoConstraints = false
  5577. profileMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5578. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5579. profileMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
  5580. } else {
  5581. if copySession || forwardSession || deleteSession {
  5582. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
  5583. } else {
  5584. profileMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5585. }
  5586. }
  5587. profileMessage.heightAnchor.constraint(equalToConstant: 37).isActive = true
  5588. profileMessage.widthAnchor.constraint(equalToConstant: 35).isActive = true
  5589. profileMessage.circle()
  5590. profileMessage.clipsToBounds = true
  5591. profileMessage.backgroundColor = .lightGray
  5592. profileMessage.image = UIImage(systemName: "person")
  5593. profileMessage.tintColor = .white
  5594. profileMessage.contentMode = .scaleAspectFit
  5595. let user = User.getData(pin: dataMessages[indexPath.row]["f_pin"] as? String)
  5596. getImage(name: user?.thumb ?? "", placeholderImage: UIImage(systemName: "person.circle.fill")!, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in
  5597. profileMessage.image = image
  5598. }
  5599. profileMessage.contentMode = .scaleAspectFill
  5600. cell.contentView.addSubview(nameSender)
  5601. nameSender.translatesAutoresizingMaskIntoConstraints = false
  5602. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  5603. nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
  5604. } else {
  5605. nameSender.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5606. }
  5607. nameSender.font = UIFont.systemFont(ofSize: 12 + offset(), weight: UIFont.Weight(800))
  5608. nameSender.text = user?.fullName ?? ""
  5609. nameSender.textAlignment = .right
  5610. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5611. nameSender.trailingAnchor.constraint(equalTo:profileMessage.leadingAnchor, constant: -5).isActive = true
  5612. nameSender.textColor = .systemBlue
  5613. } else {
  5614. nameSender.leadingAnchor.constraint(equalTo:profileMessage.trailingAnchor, constant: 5).isActive = true
  5615. nameSender.textColor = .orangeColor
  5616. }
  5617. }
  5618. let containerMessage = UIView()
  5619. cell.contentView.addSubview(containerMessage)
  5620. containerMessage.translatesAutoresizingMaskIntoConstraints = false
  5621. let timeMessage = UILabel()
  5622. timeMessage.numberOfLines = 0
  5623. cell.contentView.addSubview(timeMessage)
  5624. timeMessage.translatesAutoresizingMaskIntoConstraints = false
  5625. if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5626. timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
  5627. } else {
  5628. timeMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  5629. }
  5630. let statusMessage = UIImageView()
  5631. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "0" && dataMessages[indexPath.row]["lock"] as? String != "1") || forwardSession || deleteSession {
  5632. var showSelectedImage = true
  5633. if (!imageChat.isEmpty || !videoChat.isEmpty || !fileChat.isEmpty) && forwardSession {
  5634. var file = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  5635. if file.isEmpty {
  5636. file = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  5637. if file.isEmpty {
  5638. file = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  5639. }
  5640. }
  5641. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  5642. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  5643. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  5644. if let dirPath = paths.first {
  5645. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(file)
  5646. if !FileManager.default.fileExists(atPath: fileURL.path) && !FileEncryption.shared.isSecureExists(filename: fileURL.lastPathComponent) {
  5647. showSelectedImage = false
  5648. }
  5649. }
  5650. }
  5651. if showSelectedImage {
  5652. let selectedImage = UIImageView()
  5653. cell.contentView.addSubview(selectedImage)
  5654. selectedImage.translatesAutoresizingMaskIntoConstraints = false
  5655. selectedImage.frame.size = CGSize(width: 20, height: 20)
  5656. var leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: -20)
  5657. selectedImage.isHidden = true
  5658. if copySession || forwardSession || deleteSession {
  5659. leading = selectedImage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15)
  5660. selectedImage.isHidden = false
  5661. }
  5662. NSLayoutConstraint.activate([
  5663. leading,
  5664. selectedImage.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor),
  5665. selectedImage.widthAnchor.constraint(equalToConstant: 20),
  5666. selectedImage.heightAnchor.constraint(equalToConstant: 20)
  5667. ])
  5668. selectedImage.circle()
  5669. selectedImage.layer.borderWidth = 2
  5670. selectedImage.layer.borderColor = UIColor.mainColor.cgColor
  5671. if dataMessages[indexPath.row]["isSelected"] as! Bool {
  5672. selectedImage.image = UIImage(systemName: "checkmark.circle.fill")
  5673. }
  5674. selectedImage.tintColor = .mainColor
  5675. }
  5676. }
  5677. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5678. containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cell.contentView.leadingAnchor, constant: 60).isActive = true
  5679. if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5680. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
  5681. } else {
  5682. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  5683. }
  5684. if isContactCenter {
  5685. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  5686. containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
  5687. } else {
  5688. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5689. containerMessage.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -15).isActive = true
  5690. }
  5691. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5692. if (dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "") {
  5693. containerMessage.backgroundColor = .clear
  5694. } else {
  5695. containerMessage.backgroundColor = .blueBubbleColor
  5696. }
  5697. containerMessage.layer.cornerRadius = 10.0
  5698. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner, .layerMinXMinYCorner]
  5699. containerMessage.clipsToBounds = true
  5700. timeMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  5701. 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 {
  5702. cell.contentView.addSubview(statusMessage)
  5703. statusMessage.translatesAutoresizingMaskIntoConstraints = false
  5704. statusMessage.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  5705. statusMessage.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  5706. statusMessage.widthAnchor.constraint(equalToConstant: 15).isActive = true
  5707. statusMessage.heightAnchor.constraint(equalToConstant: 15).isActive = true
  5708. if dataMessages[indexPath.row]["status"]! as? String ?? "" == "0" {
  5709. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  5710. } else if dataMessages[indexPath.row]["status"]! as? String ?? "" == "1" {
  5711. statusMessage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal)
  5712. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "2" ) {
  5713. statusMessage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  5714. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "3") {
  5715. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
  5716. } else if (dataMessages[indexPath.row]["status"]! as? String ?? "" == "8") {
  5717. statusMessage.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5718. } else {
  5719. statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  5720. }
  5721. }
  5722. } else {
  5723. if markerCounter != nil && dataMessages[indexPath.row]["message_id"] as? String == markerCounter {
  5724. if isContactCenter {
  5725. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  5726. } else {
  5727. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 35).isActive = true
  5728. }
  5729. let newMessagesView = UIView()
  5730. cell.contentView.addSubview(newMessagesView)
  5731. newMessagesView.translatesAutoresizingMaskIntoConstraints = false
  5732. NSLayoutConstraint.activate([
  5733. newMessagesView.topAnchor.constraint(equalTo: newMessagesView.topAnchor),
  5734. newMessagesView.bottomAnchor.constraint(equalTo: containerMessage.topAnchor),
  5735. newMessagesView.centerXAnchor.constraint(equalTo: cell.contentView.centerXAnchor),
  5736. newMessagesView.heightAnchor.constraint(equalToConstant: 30),
  5737. newMessagesView.widthAnchor.constraint(greaterThanOrEqualToConstant: 60)
  5738. ])
  5739. newMessagesView.backgroundColor = .greenColor
  5740. newMessagesView.layer.cornerRadius = 15.0
  5741. newMessagesView.clipsToBounds = true
  5742. let labelNewMessages = UILabel()
  5743. newMessagesView.addSubview(labelNewMessages)
  5744. labelNewMessages.translatesAutoresizingMaskIntoConstraints = false
  5745. NSLayoutConstraint.activate([
  5746. labelNewMessages.centerYAnchor.constraint(equalTo: newMessagesView.centerYAnchor),
  5747. labelNewMessages.centerXAnchor.constraint(equalTo: newMessagesView.centerXAnchor),
  5748. labelNewMessages.leadingAnchor.constraint(equalTo: newMessagesView.leadingAnchor, constant: 10),
  5749. labelNewMessages.trailingAnchor.constraint(equalTo: newMessagesView.trailingAnchor, constant: -10),
  5750. ])
  5751. labelNewMessages.textAlignment = .center
  5752. labelNewMessages.textColor = .secondaryColor
  5753. labelNewMessages.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  5754. labelNewMessages.text = "Unread Messages".localized()
  5755. } else {
  5756. if isContactCenter {
  5757. containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
  5758. } else {
  5759. containerMessage.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 5).isActive = true
  5760. }
  5761. }
  5762. if isContactCenter {
  5763. containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
  5764. } else {
  5765. if copySession || forwardSession || deleteSession {
  5766. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 50).isActive = true
  5767. } else {
  5768. containerMessage.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 15).isActive = true
  5769. }
  5770. }
  5771. if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" || ((dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  5772. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -40).isActive = true
  5773. } else {
  5774. containerMessage.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -5).isActive = true
  5775. }
  5776. containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cell.contentView.trailingAnchor, constant: -60).isActive = true
  5777. containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
  5778. 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" {
  5779. containerMessage.backgroundColor = .clear
  5780. } else {
  5781. containerMessage.backgroundColor = .whiteBubbleColor
  5782. }
  5783. containerMessage.layer.cornerRadius = 10.0
  5784. containerMessage.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMinYCorner, .layerMaxXMaxYCorner]
  5785. containerMessage.clipsToBounds = true
  5786. timeMessage.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  5787. }
  5788. let imageStared = UIImageView()
  5789. if dataMessages[indexPath.row]["is_stared"] as? String == "1" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" == "0") {
  5790. cell.contentView.addSubview(imageStared)
  5791. imageStared.translatesAutoresizingMaskIntoConstraints = false
  5792. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5793. imageStared.bottomAnchor.constraint(equalTo: statusMessage.topAnchor).isActive = true
  5794. imageStared.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: -8).isActive = true
  5795. } else {
  5796. imageStared.bottomAnchor.constraint(equalTo: timeMessage.topAnchor).isActive = true
  5797. imageStared.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: 8).isActive = true
  5798. }
  5799. imageStared.widthAnchor.constraint(equalToConstant: 15).isActive = true
  5800. imageStared.heightAnchor.constraint(equalToConstant: 15).isActive = true
  5801. imageStared.image = UIImage(systemName: "star.fill")
  5802. imageStared.backgroundColor = .clear
  5803. imageStared.tintColor = .systemYellow
  5804. }
  5805. if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
  5806. let imageAckView = UIImageView()
  5807. var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5808. if dataMessages[indexPath.row]["status"] as? String == "8" {
  5809. imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5810. }
  5811. imageAckView.image = imageAck
  5812. cell.contentView.addSubview(imageAckView)
  5813. imageAckView.translatesAutoresizingMaskIntoConstraints = false
  5814. imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5815. imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5816. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5817. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5818. imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  5819. } else {
  5820. imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5821. imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  5822. let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
  5823. tap.indexPath = indexPath
  5824. imageAckView.addGestureRecognizer(tap)
  5825. imageAckView.isUserInteractionEnabled = true
  5826. }
  5827. }
  5828. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && (dataMessages[indexPath.row]["lock"] as? String) != "2" {
  5829. let imageCredentialView = UIImageView()
  5830. let imageCredential = UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
  5831. imageCredentialView.image = imageCredential
  5832. cell.contentView.addSubview(imageCredentialView)
  5833. imageCredentialView.translatesAutoresizingMaskIntoConstraints = false
  5834. imageCredentialView.widthAnchor.constraint(equalToConstant: 30).isActive = true
  5835. imageCredentialView.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5836. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5837. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5838. imageCredentialView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
  5839. } else {
  5840. imageCredentialView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
  5841. imageCredentialView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
  5842. }
  5843. }
  5844. // if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
  5845. // let editedText = UILabel()
  5846. // editedText.text = "Edited".localized()
  5847. // editedText.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  5848. // editedText.textColor = .lightGray
  5849. // cell.contentView.addSubview(editedText)
  5850. // editedText.translatesAutoresizingMaskIntoConstraints = false
  5851. // if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5852. // editedText.trailingAnchor.constraint(equalTo: timeMessage.leadingAnchor, constant: -2).isActive = true
  5853. // } else {
  5854. // editedText.leadingAnchor.constraint(equalTo: timeMessage.trailingAnchor, constant: 2).isActive = true
  5855. // }
  5856. // editedText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor).isActive = true
  5857. // }
  5858. let messageText = UITextView()
  5859. messageText.isEditable = false
  5860. messageText.isSelectable = true
  5861. messageText.dataDetectorTypes = [.link]
  5862. messageText.backgroundColor = .clear
  5863. messageText.isScrollEnabled = false
  5864. messageText.textContainerInset = UIEdgeInsets.zero
  5865. messageText.contentInset = UIEdgeInsets.zero
  5866. messageText.textDragInteraction?.isEnabled = false
  5867. containerMessage.addSubview(messageText)
  5868. messageText.translatesAutoresizingMaskIntoConstraints = false
  5869. let topMarginText = messageText.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15)
  5870. messageText.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  5871. messageText.font = .systemFont(ofSize: 12 + offset())
  5872. var textChat = (dataMessages[indexPath.row]["message_text"] as? String) ?? ""
  5873. var messageRequestFriend: String!
  5874. if dataMessages[indexPath.row]["attachment_flag"] as? String == "27" || dataMessages[indexPath.row]["attachment_flag"] as? String == "26" ||
  5875. dataMessages[indexPath.row]["attachment_flag"] as? String == "25" || dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
  5876. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 85).isActive = true
  5877. let imageLS = UIImageView()
  5878. containerMessage.addSubview(imageLS)
  5879. imageLS.translatesAutoresizingMaskIntoConstraints = false
  5880. NSLayoutConstraint.activate([
  5881. imageLS.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15.0),
  5882. imageLS.trailingAnchor.constraint(equalTo: messageText.leadingAnchor, constant: -10.0),
  5883. imageLS.centerYAnchor.constraint(equalTo: containerMessage.centerYAnchor),
  5884. imageLS.heightAnchor.constraint(equalToConstant: 60.0)
  5885. ])
  5886. if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "26" {
  5887. imageLS.image = UIImage(named: "pb_seminar_wpr", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5888. } else if dataMessages[indexPath.row]["attachment_flag"] as? String ?? "" == "27" {
  5889. imageLS.image = UIImage(named: "pb_live_tv", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5890. } else if dataMessages[indexPath.row]["attachment_flag"] as! String == "25" {
  5891. imageLS.image = UIImage(named: "pb_vroom", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  5892. } else if dataMessages[indexPath.row]["message_scope_id"] as? String == "18" {
  5893. imageLS.image = UIImage(systemName: "doc.richtext.fill")
  5894. imageLS.tintColor = .mainColor
  5895. }
  5896. } else if !audioChat.isEmpty {
  5897. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 60).isActive = true
  5898. } else {
  5899. messageText.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5900. }
  5901. 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") {
  5902. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -115).isActive = true
  5903. let imageQR = UIImageView()
  5904. containerMessage.addSubview(imageQR)
  5905. imageQR.translatesAutoresizingMaskIntoConstraints = false
  5906. NSLayoutConstraint.activate([
  5907. imageQR.centerXAnchor.constraint(equalTo: containerMessage.centerXAnchor),
  5908. imageQR.topAnchor.constraint(equalTo: messageText.bottomAnchor),
  5909. imageQR.widthAnchor.constraint(equalToConstant: 100.0),
  5910. imageQR.heightAnchor.constraint(equalToConstant: 100.0)
  5911. ])
  5912. imageQR.image = generateQRCode(from: dataMessages[indexPath.row]["blog_id"] as? String ?? "")
  5913. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "61" {
  5914. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -50).isActive = true
  5915. let fPinFriend = dataMessages[indexPath.row]["blog_id"] as? String ?? ""
  5916. let buttonAccept = UIButton(type: .custom)
  5917. buttonAccept.setTitle("Accept".localized(), for: .normal)
  5918. buttonAccept.setBackgroundImage(UIImage(color: UIColor.clear), for: .normal)
  5919. buttonAccept.setBackgroundImage(UIImage(color: UIColor.blueBubbleColor), for: .highlighted)
  5920. buttonAccept.setTitleColor(.black, for: .normal)
  5921. buttonAccept.titleLabel?.font = UIFont.systemFont(ofSize: 12)
  5922. buttonAccept.layer.borderWidth = 2.0
  5923. buttonAccept.layer.borderColor = UIColor.blueBubbleColor.cgColor
  5924. buttonAccept.layer.cornerRadius = 8.0
  5925. buttonAccept.tag = 0
  5926. buttonAccept.restorationIdentifier = "\(fPinFriend),\(messageIdChat)"
  5927. buttonAccept.clipsToBounds = true
  5928. containerMessage.addSubview(buttonAccept)
  5929. buttonAccept.translatesAutoresizingMaskIntoConstraints = false
  5930. buttonAccept.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  5931. buttonAccept.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  5932. buttonAccept.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width/5).isActive = true
  5933. buttonAccept.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5934. buttonAccept.addTarget(self, action: #selector(addFriendReqAction), for: .touchUpInside)
  5935. let buttonDecline = UIButton(type: .custom)
  5936. buttonDecline.setTitle("Decline".localized(), for: .normal)
  5937. buttonDecline.setBackgroundImage(UIImage(color: UIColor.clear), for: .normal)
  5938. buttonDecline.setBackgroundImage(UIImage(color: UIColor.blueBubbleColor), for: .highlighted)
  5939. buttonDecline.setTitleColor(.black, for: .normal)
  5940. buttonDecline.titleLabel?.font = UIFont.systemFont(ofSize: 12)
  5941. buttonDecline.layer.borderWidth = 2.0
  5942. buttonDecline.tag = 1
  5943. buttonDecline.restorationIdentifier = "\(fPinFriend),\(messageIdChat)"
  5944. buttonDecline.layer.borderColor = UIColor.blueBubbleColor.cgColor
  5945. buttonDecline.layer.cornerRadius = 8.0
  5946. buttonDecline.clipsToBounds = true
  5947. containerMessage.addSubview(buttonDecline)
  5948. buttonDecline.translatesAutoresizingMaskIntoConstraints = false
  5949. buttonDecline.leadingAnchor.constraint(equalTo: buttonAccept.trailingAnchor, constant: 10).isActive = true
  5950. buttonDecline.topAnchor.constraint(equalTo: messageText.bottomAnchor, constant: 5).isActive = true
  5951. buttonDecline.widthAnchor.constraint(equalToConstant: self.view!.frame.size.width/5).isActive = true
  5952. buttonDecline.heightAnchor.constraint(equalToConstant: 30).isActive = true
  5953. buttonDecline.addTarget(self, action: #selector(addFriendReqAction), for: .touchUpInside)
  5954. let textName = textChat.components(separatedBy: "~")[0]
  5955. let textAfterName = textChat.components(separatedBy: "~")[1]
  5956. messageRequestFriend = textName + " " + textAfterName.localized()
  5957. } else {
  5958. messageText.bottomAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: -15).isActive = true
  5959. }
  5960. messageText.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  5961. let originalMessageText = textChat
  5962. if (dataMessages[indexPath.row]["lock"] != nil && (dataMessages[indexPath.row]["lock"])! as? String == "1") {
  5963. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  5964. textChat = "🚫 _"+"You were deleted this message".localized()+"_"
  5965. } else {
  5966. textChat = "🚫 _"+"This message was deleted".localized()+"_"
  5967. }
  5968. }
  5969. if dataMessages[indexPath.row]["lock"] as? String == "2" {
  5970. textChat = "🚫 _"+"Message has expired".localized()+"_"
  5971. }
  5972. if !audioChat.isEmpty {
  5973. textChat = textChat.components(separatedBy: "|")[0]
  5974. }
  5975. let imageSticker = UIImageView()
  5976. var stringLS = ""
  5977. if let attachmentFlag = dataMessages[indexPath.row]["attachment_flag"], let attachmentFlag = attachmentFlag as? String {
  5978. if attachmentFlag == "27" || attachmentFlag == "26" { // live streaming
  5979. let data = textChat
  5980. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  5981. let title = json["title"] as? String ?? ""
  5982. let description = json["description"] as? String ?? ""
  5983. let start = json["time"] as? Int64 ?? 0
  5984. let by = json["by"] as? String ?? ""
  5985. let textLS = "Live Streaming".localized()
  5986. var type = "*\(textLS)*"
  5987. if attachmentFlag == "26" {
  5988. let textSeminar = "Seminar".localized()
  5989. type = "*\(textSeminar)*"
  5990. }
  5991. if let c = User.getData(pin: by) {
  5992. let name = c.fullName
  5993. stringLS = "\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nBroadcaster: \(name)"
  5994. } else {
  5995. stringLS = ("\(type) \nTitle: \(title) \nDescription: \(description) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm"))")
  5996. }
  5997. messageText.attributedText = stringLS.richText()
  5998. messageText.isUserInteractionEnabled = false
  5999. }
  6000. }
  6001. else if attachmentFlag == "25" {
  6002. let data = textChat
  6003. if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  6004. let title = json["title"] as? String ?? ""
  6005. let blog = json["blog"] as? String ?? ""
  6006. let by = json["by"] as? String ?? ""
  6007. let start = json["time"] as? Int64 ?? 0
  6008. let textVCR = "Video Conference Room".localized()
  6009. var type = "*\(textVCR)*"
  6010. if let c = User.getData(pin: by) {
  6011. let name = c.fullName
  6012. stringLS = "\(type) \nTitle: \(title) \nStart: \(Date(milliseconds: start).format(dateFormat: "dd/MM/yyyy HH:mm")) \nInitiator: \(name) \n\n*^Room ID: ^*\n*^\(blog)^*"
  6013. }
  6014. messageText.attributedText = stringLS.richText()
  6015. messageText.isUserInteractionEnabled = false
  6016. }
  6017. }
  6018. else if attachmentFlag == "61" {
  6019. messageText.attributedText = messageRequestFriend.richText()
  6020. messageText.isUserInteractionEnabled = false
  6021. }
  6022. else if attachmentFlag == "11" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  6023. messageText.text = ""
  6024. topMarginText.constant = topMarginText.constant + 100
  6025. containerMessage.addSubview(imageSticker)
  6026. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  6027. let data = queryMessageReply(message_id: reffChat)
  6028. if reffChat.isEmpty || data.count == 0 {
  6029. imageSticker.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6030. imageSticker.widthAnchor.constraint(equalToConstant: 80).isActive = true
  6031. } else {
  6032. imageSticker.widthAnchor.constraint(greaterThanOrEqualToConstant: 80).isActive = true
  6033. }
  6034. imageSticker.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6035. imageSticker.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6036. imageSticker.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6037. var imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  6038. if imageStickerBundle == nil {
  6039. imageStickerBundle = UIImage(named: (textChat.components(separatedBy: "/")[1]), in: Bundle.resourcesMediaBundle(for: Nexilis.self), with: nil)
  6040. }
  6041. imageSticker.image = imageStickerBundle //resourcesMediaBundle
  6042. imageSticker.contentMode = .scaleAspectFit
  6043. } else if dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" == "18" {
  6044. let data = textChat
  6045. if let jsonForm = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  6046. let form_title = jsonForm["form_title"] as? String ?? ""
  6047. let club_type = jsonForm["club_type"] as? String ?? ""
  6048. let province = jsonForm["province"] as? String ?? ""
  6049. let club = jsonForm["club"] as? String ?? ""
  6050. messageText.attributedText = "*\(form_title.replacingOccurrences(of: "+", with: " "))* \nClub Type: \(club_type) \nProvince: \(province) \nClub Name: \(club) ".richText()
  6051. messageText.isUserInteractionEnabled = false
  6052. }
  6053. }
  6054. else {
  6055. messageText.attributedText = textChat.richText()
  6056. modifyText()
  6057. }
  6058. } else {
  6059. messageText.attributedText = textChat.richText()
  6060. modifyText()
  6061. }
  6062. func modifyText() {
  6063. if !textChat.isEmpty {
  6064. if textChat.contains("■"){
  6065. textChat = textChat.components(separatedBy: "■")[0]
  6066. textChat = textChat.trimmingCharacters(in: .whitespacesAndNewlines)
  6067. }
  6068. if !fileChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String != "1" && dataMessages[indexPath.row]["lock"] as? String != "2" {
  6069. textChat = textChat.components(separatedBy: "|")[1]
  6070. }
  6071. let finalAtribute = textChat.richText()
  6072. textChat = finalAtribute.string
  6073. let urlPattern = "(https?://|www\\.)\\S+"
  6074. if let regex = try? NSRegularExpression(pattern: urlPattern, options: []) {
  6075. let matches = regex.matches(in: textChat, options: [], range: NSRange(textChat.startIndex..., in: textChat))
  6076. for match in matches {
  6077. if let range = Range(match.range, in: textChat) {
  6078. let linkText = String(textChat[range])
  6079. let nsRange = NSRange(range, in: textChat)
  6080. finalAtribute.addAttribute(.link, value: linkText, range: nsRange)
  6081. finalAtribute.addAttribute(.foregroundColor, value: UIColor.blue, range: nsRange)
  6082. finalAtribute.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: nsRange)
  6083. }
  6084. }
  6085. }
  6086. messageText.attributedText = finalAtribute
  6087. messageText.delegate = self
  6088. }
  6089. }
  6090. if dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String == MessageScope.CALL || dataMessages[indexPath.row][TypeDataMessage.message_scope_id] as? String == MessageScope.MISSED_CALL{
  6091. messageText.removeFromSuperview()
  6092. let containerCall = UIButton(type: .custom)
  6093. containerCall.backgroundColor = .white.withAlphaComponent(0.3)
  6094. containerMessage.addSubview(containerCall)
  6095. containerCall.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, height: 60)
  6096. containerCall.layer.cornerRadius = 5
  6097. containerCall.clipsToBounds = true
  6098. var imageCall = "phone.fill.arrow.up.right"
  6099. var textCall = "Audio call".localized()
  6100. let isVideo = textChat.lowercased().contains("video")
  6101. let isMissedCall = textChat.lowercased().contains("missed")
  6102. let isImageLeft = textChat.lowercased().contains("incoming") || isMissedCall
  6103. let longCall = textChat.components(separatedBy: " at ")[1]
  6104. var subTextCall = longCall
  6105. let contIconCall = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
  6106. containerCall.addSubview(contIconCall)
  6107. contIconCall.anchor(top: containerCall.topAnchor, left: containerCall.leftAnchor, bottom: containerCall.bottomAnchor, paddingTop: 10, paddingLeft: 10, paddingBottom: 10, width: 40, height: 40)
  6108. contIconCall.circle()
  6109. if isImageLeft {
  6110. contIconCall.backgroundColor = .white
  6111. } else {
  6112. contIconCall.backgroundColor = .black.withAlphaComponent(0.6)
  6113. }
  6114. if isVideo && isImageLeft {
  6115. imageCall = "arrow.down.left.video.fill"
  6116. if isMissedCall {
  6117. textCall = "Missed video call".localized()
  6118. subTextCall = "Tap to call back".localized()
  6119. } else {
  6120. textCall = "Video call".localized()
  6121. }
  6122. } else if isVideo {
  6123. imageCall = "arrow.up.right.video.fill"
  6124. textCall = "Video call".localized()
  6125. if longCall.trimmingCharacters(in: .whitespaces) == "0" {
  6126. subTextCall = "No answer".localized()
  6127. }
  6128. } else if isImageLeft {
  6129. imageCall = "phone.fill.arrow.down.left"
  6130. if isMissedCall {
  6131. textCall = "Missed audio call".localized()
  6132. subTextCall = "Tap to call back".localized()
  6133. }
  6134. } else if longCall.trimmingCharacters(in: .whitespaces) == "0" {
  6135. subTextCall = "No answer".localized()
  6136. }
  6137. let iconCall = UIImageView()
  6138. iconCall.image = UIImage(systemName: imageCall, withConfiguration: UIImage.SymbolConfiguration(pointSize: 18))
  6139. contIconCall.addSubview(iconCall)
  6140. if isMissedCall {
  6141. iconCall.tintColor = .red
  6142. }else if isImageLeft {
  6143. iconCall.tintColor = .black
  6144. } else {
  6145. iconCall.tintColor = .white
  6146. }
  6147. iconCall.anchor(centerX: contIconCall.centerXAnchor, centerY: contIconCall.centerYAnchor)
  6148. let titleCall = UILabel()
  6149. containerCall.addSubview(titleCall)
  6150. titleCall.anchor(top: containerCall.topAnchor, left: contIconCall.rightAnchor, right: containerCall.rightAnchor, paddingTop: 10, paddingLeft: 10, paddingRight: 10)
  6151. titleCall.text = textCall
  6152. titleCall.font = .systemFont(ofSize: 14)
  6153. let subtitleCall = UILabel()
  6154. containerCall.addSubview(subtitleCall)
  6155. subtitleCall.anchor(top: titleCall.bottomAnchor, left: contIconCall.rightAnchor, right: containerCall.rightAnchor, paddingLeft: 10, paddingRight: 10)
  6156. subtitleCall.text = subTextCall
  6157. subtitleCall.font = .systemFont(ofSize: 13)
  6158. subtitleCall.textColor = .gray
  6159. }
  6160. if !copySession && !forwardSession && !deleteSession && !self.removed {
  6161. let interaction = UIContextMenuInteraction(delegate: self)
  6162. containerMessage.addInteraction(interaction)
  6163. containerMessage.isUserInteractionEnabled = true
  6164. }
  6165. 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 {
  6166. messageText.attributedText = messageRequestFriend != nil ? messageRequestFriend.richText(isSearching: true, textSearch: textSearch) : stringLS.isEmpty ? textChat.richText(isSearching: true, textSearch: textSearch) : stringLS.richText(isSearching: true, textSearch: textSearch)
  6167. }
  6168. let stringDate = (dataMessages[indexPath.row]["server_date"] as? String) ?? ""
  6169. if !stringDate.isEmpty {
  6170. if (dataMessages[indexPath.row]["credential"] as? String) == "1" && dataMessages[indexPath.row]["lock"] as? String != "2" && dataMessages[indexPath.row]["lock"] as? String != "1" {
  6171. if dataTimer != nil {
  6172. if dataTimer! >= 10 {
  6173. timeMessage.text = "00:\(dataTimer!)"
  6174. } else {
  6175. timeMessage.text = "00:0\(dataTimer!)"
  6176. }
  6177. timeMessage.textColor = .systemRed
  6178. }
  6179. } else {
  6180. let date = Date(milliseconds: Int64(stringDate) ?? 100)
  6181. let formatter = DateFormatter()
  6182. formatter.dateFormat = "HH:mm"
  6183. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  6184. timeMessage.text = formatter.string(from: date as Date)
  6185. timeMessage.textColor = .lightGray
  6186. }
  6187. timeMessage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  6188. if dataMessages[indexPath.row][TypeDataMessage.last_edit] != nil && dataMessages[indexPath.row][TypeDataMessage.last_edit] as! Int64 != 0 {
  6189. timeMessage.text = (timeMessage.text ?? "") + "\n" + "Edited".localized()
  6190. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6191. timeMessage.textAlignment = .right
  6192. }
  6193. }
  6194. }
  6195. let imageThumb = UIImageView()
  6196. let containerViewFile = UIView()
  6197. let imageGif = SDAnimatedImageView()
  6198. if !audioChat.isEmpty {
  6199. messageText.isHidden = true
  6200. let imageAudio = UIImageView()
  6201. imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35))
  6202. containerMessage.addSubview(imageAudio)
  6203. imageAudio.anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingTop: 15, paddingLeft: 15, paddingBottom: 15, centerY: containerMessage.centerYAnchor)
  6204. imageAudio.tintColor = .mainColor
  6205. let playButtonAudio = UIButton(type: .system)
  6206. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  6207. playButtonAudio.tintColor = .gray
  6208. containerMessage.addSubview(playButtonAudio)
  6209. playButtonAudio.anchor(left: containerMessage.leftAnchor, paddingLeft: 60, centerY: containerMessage.centerYAnchor, width: 20, height: 20)
  6210. let progressSliderAudio = UISlider()
  6211. progressSliderAudio.minimumValue = 0
  6212. progressSliderAudio.maximumValue = 1
  6213. let thumbImage = UIImage(systemName: "circle.fill")?.withTintColor(UIColor.mainColor)
  6214. .resize(target: CGSize(width: 15, height: 15))
  6215. progressSliderAudio.setThumbImage(thumbImage, for: .normal)
  6216. containerMessage.addSubview(progressSliderAudio)
  6217. progressSliderAudio.anchor(left: playButtonAudio.rightAnchor, right: containerMessage.rightAnchor, paddingLeft: 10, paddingRight: 15, centerY: containerMessage.centerYAnchor, height: 15)
  6218. let timeLabelAudio = UILabel()
  6219. timeLabelAudio.text = "0:00"
  6220. timeLabelAudio.font = .systemFont(ofSize: 10 + offset())
  6221. timeLabelAudio.textColor = .gray
  6222. containerMessage.addSubview(timeLabelAudio)
  6223. timeLabelAudio.anchor(top: playButtonAudio.bottomAnchor, left: playButtonAudio.rightAnchor, paddingLeft: 10, width: 100, height: 12)
  6224. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6225. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6226. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6227. if let dirPath = paths.first {
  6228. let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(audioChat)
  6229. var url = audioURL
  6230. if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: audioChat) {
  6231. let activityIndicator = UIActivityIndicatorView(style: .medium)
  6232. activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  6233. activityIndicator.startAnimating()
  6234. playButtonAudio.setImage(nil, for: .normal)
  6235. playButtonAudio.addSubview(activityIndicator)
  6236. NSLayoutConstraint.activate([
  6237. activityIndicator.centerXAnchor.constraint(equalTo: playButtonAudio.centerXAnchor),
  6238. activityIndicator.centerYAnchor.constraint(equalTo: playButtonAudio.centerYAnchor)
  6239. ])
  6240. Download().startHTTP(forKey: audioChat, isImage: false) { (name, progress) in
  6241. guard progress == 100 else {
  6242. return
  6243. }
  6244. tableView.reloadRows(at: [indexPath], with: .none)
  6245. }
  6246. } else {
  6247. if !FileManager.default.fileExists(atPath: audioURL.path) {
  6248. do {
  6249. if var audioData = try FileEncryption.shared.readSecure(filename: audioChat) {
  6250. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: audioData)
  6251. if dataDecrypt != nil {
  6252. audioData = dataDecrypt!
  6253. }
  6254. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  6255. let tempPath = cachesDirectory.appendingPathComponent(audioChat)
  6256. try audioData.write(to: tempPath)
  6257. url = tempPath
  6258. }
  6259. } catch {
  6260. }
  6261. }
  6262. if audioPlayers[indexPath] == nil {
  6263. do {
  6264. let audioPlayer = try AVAudioPlayer(contentsOf: url)
  6265. audioPlayers[indexPath] = audioPlayer
  6266. audioPlayer.delegate = self
  6267. progressSliderAudio.maximumValue = Float(audioPlayer.duration)
  6268. timeLabelAudio.text = formatTime(audioPlayer.duration)
  6269. } catch {
  6270. print("Error loading audio: \(error)")
  6271. }
  6272. }
  6273. let audioPlayer = audioPlayers[indexPath]
  6274. if playingIndexPath == indexPath, let player = audioPlayer, player.isPlaying {
  6275. playButtonAudio.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  6276. } else {
  6277. playButtonAudio.setImage(UIImage(systemName: "play.fill"), for: .normal)
  6278. }
  6279. // Play/Pause Button Action
  6280. playButtonAudio.addAction(UIAction { _ in
  6281. self.playPauseAudio(indexPath: indexPath, playButton: playButtonAudio, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  6282. }, for: .touchUpInside)
  6283. progressSliderAudio.addAction(UIAction { _ in
  6284. self.sliderChanged(indexPath: indexPath, progressSlider: progressSliderAudio, timeLabel: timeLabelAudio)
  6285. }, for: .valueChanged)
  6286. }
  6287. }
  6288. }
  6289. if (!thumbChat.isEmpty && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  6290. if let listImages = groupImages[messageIdChat] {
  6291. timeMessage.isHidden = true
  6292. statusMessage.isHidden = true
  6293. imageStared.isHidden = true
  6294. topMarginText.constant = topMarginText.constant + 225
  6295. let listImageThumb: [UIImageView] = [UIImageView(), UIImageView(), UIImageView(), UIImageView()]
  6296. for i in 0..<4 {
  6297. containerMessage.addSubview(listImageThumb[i])
  6298. listImageThumb[i].layer.cornerRadius = 5.0
  6299. listImageThumb[i].clipsToBounds = true
  6300. listImageThumb[i].contentMode = .scaleAspectFill
  6301. let widthHeightImage: CGFloat = 120
  6302. switch i {
  6303. case 0:
  6304. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: containerMessage.leftAnchor, paddingTop: 5, paddingLeft: 5, width: widthHeightImage, height: widthHeightImage)
  6305. case 1:
  6306. listImageThumb[i].anchor(top: containerMessage.topAnchor, left: listImageThumb[0].rightAnchor, right: containerMessage.rightAnchor, paddingTop: 5, paddingLeft: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  6307. case 2:
  6308. listImageThumb[i].anchor(left: containerMessage.leftAnchor, bottom: containerMessage.bottomAnchor, paddingLeft: 5, paddingBottom: 5, width: widthHeightImage, height: widthHeightImage)
  6309. default:
  6310. listImageThumb[i].anchor(left: listImageThumb[2].rightAnchor, bottom: containerMessage.bottomAnchor, right: containerMessage.rightAnchor, paddingLeft: 5, paddingBottom: 5, paddingRight: 5, width: widthHeightImage, height: widthHeightImage)
  6311. }
  6312. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6313. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6314. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6315. if let dirPath = paths.first {
  6316. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].thumbId)
  6317. if FileManager.default.fileExists(atPath: thumbURL.path) {
  6318. DispatchQueue.main.async {
  6319. let image : UIImage? = {
  6320. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  6321. return img
  6322. }
  6323. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6324. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  6325. return img
  6326. }
  6327. return nil
  6328. }()
  6329. imageThumb.image = image
  6330. }
  6331. } else if FileEncryption.shared.isSecureExists(filename: listImages[i].thumbId) {
  6332. do {
  6333. if var data = try FileEncryption.shared.readSecure(filename: listImages[i].thumbId) {
  6334. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  6335. if dataDecrypt != nil {
  6336. data = dataDecrypt!
  6337. }
  6338. DispatchQueue.main.async {
  6339. let image : UIImage? = {
  6340. if let img = Nexilis.imageCache.object(forKey: listImages[i].thumbId as NSString) {
  6341. return img
  6342. }
  6343. else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
  6344. Nexilis.imageCache.setObject(img, forKey: listImages[i].thumbId as NSString)
  6345. return img
  6346. }
  6347. return nil
  6348. }()
  6349. imageThumb.image = image
  6350. }
  6351. }
  6352. } catch {
  6353. }
  6354. }
  6355. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(listImages[i].imageId)
  6356. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  6357. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  6358. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  6359. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[i].frame.size.width, height: listImageThumb[i].frame.size.height)
  6360. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6361. listImageThumb[i].addSubview(blurEffectView)
  6362. }
  6363. }
  6364. let containerTimeStatus = UIView()
  6365. listImageThumb[i].addSubview(containerTimeStatus)
  6366. containerTimeStatus.anchor(bottom: listImageThumb[i].bottomAnchor, right: listImageThumb[i].rightAnchor, height: 15)
  6367. let widthcontainerTimeStatus = containerTimeStatus.widthAnchor.constraint(equalToConstant: 50)
  6368. widthcontainerTimeStatus.isActive = true
  6369. containerTimeStatus.layer.cornerRadius = 5.0
  6370. containerTimeStatus.layer.masksToBounds = true
  6371. containerTimeStatus.backgroundColor = .black.withAlphaComponent(0.15)
  6372. let timeInImage = UILabel()
  6373. containerTimeStatus.addSubview(timeInImage)
  6374. let date = Date(milliseconds: Int64(listImages[i].time) ?? 100)
  6375. let formatter = DateFormatter()
  6376. formatter.dateFormat = "HH:mm"
  6377. formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
  6378. timeInImage.text = formatter.string(from: date as Date)
  6379. timeInImage.textColor = .white
  6380. timeInImage.font = UIFont.systemFont(ofSize: 10 + offset(), weight: .medium)
  6381. 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) {
  6382. let statusInImage = UIImageView()
  6383. containerTimeStatus.addSubview(statusInImage)
  6384. statusInImage.anchor(right: containerTimeStatus.rightAnchor, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  6385. if listImages[i].status == "0" {
  6386. statusMessage.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal)
  6387. } else if listImages[i].status == "1" {
  6388. statusInImage.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.white, renderingMode: .alwaysOriginal)
  6389. } else if listImages[i].status == "2" {
  6390. statusInImage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  6391. } else if listImages[i].status == "3" {
  6392. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.white)
  6393. } else {
  6394. statusInImage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
  6395. }
  6396. timeInImage.anchor(right: statusInImage.leftAnchor, centerY: containerTimeStatus.centerYAnchor, height: 15)
  6397. } else {
  6398. timeInImage.anchor(right: containerTimeStatus.rightAnchor, paddingRight: 5, centerY: containerTimeStatus.centerYAnchor, height: 15)
  6399. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant - 10
  6400. }
  6401. if listImages[i].dataMessage["is_stared"] as? String == "1" {
  6402. let iconStar = UIImageView()
  6403. containerTimeStatus.addSubview(iconStar)
  6404. iconStar.anchor(right: timeInImage.leftAnchor, paddingRight: 2, centerY: containerTimeStatus.centerYAnchor, width: 15, height: 15)
  6405. widthcontainerTimeStatus.constant = widthcontainerTimeStatus.constant + 15
  6406. iconStar.image = UIImage(systemName: "star.fill")
  6407. iconStar.tintColor = .white
  6408. }
  6409. if !copySession && !forwardSession && !deleteSession {
  6410. let objectTap = ObjectGesture(target: self, action: #selector(imageGroupingTapped(_:)))
  6411. listImageThumb[i].isUserInteractionEnabled = true
  6412. listImageThumb[i].addGestureRecognizer(objectTap)
  6413. objectTap.indexImageTapped = i
  6414. objectTap.listImageFromGrouping = listImages
  6415. objectTap.isInitiator = dataMessages[indexPath.row]["f_pin"] as? String == idMe
  6416. }
  6417. }
  6418. if listImages.count > 4 {
  6419. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.dark)
  6420. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  6421. blurEffectView.frame = CGRect(x: 0, y: 0, width: listImageThumb[3].frame.size.width, height: listImageThumb[3].frame.size.height)
  6422. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6423. listImageThumb[3].addSubview(blurEffectView)
  6424. let countRestImages = UILabel()
  6425. listImageThumb[3].addSubview(countRestImages)
  6426. countRestImages.anchor(centerX: listImageThumb[3].centerXAnchor, centerY: listImageThumb[3].centerYAnchor)
  6427. countRestImages.font = UIFont.systemFont(ofSize: 30, weight: .medium)
  6428. countRestImages.text = "+\(listImages.count - 3)"
  6429. countRestImages.textColor = .white
  6430. }
  6431. } else {
  6432. let getHeightImage: CGFloat = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.height
  6433. let getWidthImage: CGFloat = ListGroupImages.getImageSize(image: thumbChat, screenWidth: self.view.frame.size.width * 0.6, screenHeight: 305)!.width
  6434. topMarginText.constant = topMarginText.constant + (getHeightImage < 40 ? 45 : getHeightImage + 5)
  6435. containerMessage.addSubview(imageThumb)
  6436. imageThumb.frame = CGRect(x: 0, y: 0, width: getWidthImage, height: getHeightImage)
  6437. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  6438. let data = queryMessageReply(message_id: reffChat)
  6439. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  6440. imageThumb.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6441. }
  6442. imageThumb.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6443. imageThumb.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6444. imageThumb.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6445. imageThumb.widthAnchor.constraint(equalToConstant: getWidthImage).isActive = true
  6446. imageThumb.layer.cornerRadius = 5.0
  6447. imageThumb.clipsToBounds = true
  6448. imageThumb.contentMode = .scaleAspectFill
  6449. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6450. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6451. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6452. if let dirPath = paths.first {
  6453. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
  6454. if FileManager.default.fileExists(atPath: thumbURL.path) {
  6455. DispatchQueue.main.async {
  6456. let image : UIImage? = {
  6457. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  6458. return img
  6459. }
  6460. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6461. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  6462. return img
  6463. }
  6464. return nil
  6465. }()
  6466. imageThumb.image = image
  6467. }
  6468. } else if FileEncryption.shared.isSecureExists(filename: thumbChat) {
  6469. do {
  6470. if var data = try FileEncryption.shared.readSecure(filename: thumbChat) {
  6471. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  6472. if dataDecrypt != nil {
  6473. data = dataDecrypt!
  6474. }
  6475. DispatchQueue.main.async {
  6476. let image : UIImage? = {
  6477. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  6478. return img
  6479. }
  6480. else if let img = UIImage(data: data)?.resize(target: CGSize(width: 500, height: 500)) {
  6481. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  6482. return img
  6483. }
  6484. return nil
  6485. }()
  6486. imageThumb.image = image
  6487. }
  6488. }
  6489. } catch {
  6490. }
  6491. }
  6492. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imageChat)
  6493. if !FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent) {
  6494. let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light)
  6495. let blurEffectView = UIVisualEffectView(effect: blurEffect)
  6496. blurEffectView.frame = CGRect(x: 0, y: 0, width: imageThumb.frame.size.width, height: imageThumb.frame.size.height)
  6497. blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  6498. imageThumb.addSubview(blurEffectView)
  6499. if !imageChat.isEmpty {
  6500. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default)))
  6501. imageThumb.addSubview(imageDownload)
  6502. imageDownload.tintColor = .black.withAlphaComponent(0.3)
  6503. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  6504. imageDownload.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  6505. imageDownload.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  6506. }
  6507. }
  6508. }
  6509. if (videoChat != "" && gifChat.isEmpty) {
  6510. 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))
  6511. imagePlay.circle()
  6512. imageThumb.addSubview(imagePlay)
  6513. imagePlay.backgroundColor = .black.withAlphaComponent(0.3)
  6514. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  6515. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  6516. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  6517. } else if !gifChat.isEmpty {
  6518. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6519. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6520. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6521. if let dirPath = paths.first {
  6522. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifChat)
  6523. if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifChat) {
  6524. Download().startHTTP(forKey: gifChat, isImage: false) { (name, progress) in
  6525. guard progress == 100 else {
  6526. return
  6527. }
  6528. tableView.reloadRows(at: [indexPath], with: .none)
  6529. }
  6530. } else {
  6531. imageThumb.addSubview(imageGif)
  6532. imageGif.translatesAutoresizingMaskIntoConstraints = false
  6533. imageGif.anchor(top: imageThumb.topAnchor, left: imageThumb.leftAnchor, bottom: imageThumb.bottomAnchor, right: imageThumb.rightAnchor)
  6534. if FileManager.default.fileExists(atPath: gifURL.path) {
  6535. imageGif.image = SDAnimatedImage(contentsOfFile: gifURL.path)
  6536. // imageGif.shouldCustomLoopCount = true
  6537. // imageGif.animationRepeatCount = 4
  6538. } else if FileEncryption.shared.isSecureExists(filename: gifChat){
  6539. do {
  6540. if var data = try FileEncryption.shared.readSecure(filename: gifChat) {
  6541. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  6542. if dataDecrypt != nil {
  6543. data = dataDecrypt!
  6544. }
  6545. if let imageData = SDAnimatedImage(data: data) {
  6546. imageGif.image = imageData
  6547. // imageGif.shouldCustomLoopCount = true
  6548. // imageGif.animationRepeatCount = 4
  6549. }
  6550. }
  6551. }
  6552. catch {
  6553. print("Error reading secure file")
  6554. }
  6555. }
  6556. }
  6557. }
  6558. }
  6559. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0 && dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  6560. let container = UIView()
  6561. imageThumb.addSubview(container)
  6562. container.translatesAutoresizingMaskIntoConstraints = false
  6563. container.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  6564. container.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  6565. container.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6566. container.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6567. container.backgroundColor = .white.withAlphaComponent(0.1)
  6568. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 10, y: 20), radius: 15, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  6569. let trackShape = CAShapeLayer()
  6570. trackShape.path = circlePath.cgPath
  6571. trackShape.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
  6572. trackShape.lineWidth = 3
  6573. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  6574. container.backgroundColor = .clear
  6575. container.layer.addSublayer(trackShape)
  6576. let shapeLoading = CAShapeLayer()
  6577. shapeLoading.path = circlePath.cgPath
  6578. shapeLoading.fillColor = UIColor.clear.cgColor
  6579. shapeLoading.lineWidth = 3
  6580. shapeLoading.strokeEnd = 0
  6581. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  6582. container.layer.addSublayer(shapeLoading)
  6583. let imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6584. imageupload.tintColor = .white
  6585. container.addSubview(imageupload)
  6586. imageupload.translatesAutoresizingMaskIntoConstraints = false
  6587. imageupload.bottomAnchor.constraint(equalTo: imageThumb.bottomAnchor, constant: -10).isActive = true
  6588. imageupload.leadingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: 10).isActive = true
  6589. imageupload.widthAnchor.constraint(equalToConstant: 20).isActive = true
  6590. imageupload.heightAnchor.constraint(equalToConstant: 20).isActive = true
  6591. }
  6592. if !copySession && !forwardSession && !deleteSession {
  6593. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  6594. imageThumb.isUserInteractionEnabled = true
  6595. imageThumb.addGestureRecognizer(objectTap)
  6596. objectTap.image_id = imageChat
  6597. objectTap.video_id = videoChat
  6598. objectTap.gif_id = gifChat
  6599. objectTap.imageView = imageThumb
  6600. objectTap.indexPath = indexPath
  6601. }
  6602. }
  6603. }
  6604. if (fileChat != "" && dataMessages[indexPath.row]["lock"] as? String ?? "" != "1" && dataMessages[indexPath.row]["lock"] as? String != "2") {
  6605. topMarginText.constant = topMarginText.constant + 55
  6606. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6607. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6608. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6609. let arrExtFile = (originalMessageText.components(separatedBy: "|")[0]).split(separator: ".")
  6610. let finalExtFile = arrExtFile[arrExtFile.count - 1]
  6611. if let dirPath = paths.first {
  6612. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileChat)
  6613. if FileManager.default.fileExists(atPath: fileURL.path) {
  6614. if let dataFile = try? Data(contentsOf: fileURL), textChat.isEmpty {
  6615. var sizeOfFile = Int(dataFile.count / 1000000)
  6616. if (sizeOfFile < 1) {
  6617. sizeOfFile = Int(dataFile.count / 1000)
  6618. if (finalExtFile.count > 4) {
  6619. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  6620. }else {
  6621. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  6622. }
  6623. } else {
  6624. if (finalExtFile.count > 4) {
  6625. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  6626. }else {
  6627. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  6628. }
  6629. }
  6630. }
  6631. }
  6632. else if FileEncryption.shared.isSecureExists(filename: fileChat) {
  6633. if var dataFile = try? FileEncryption.shared.readSecure(filename: fileChat), textChat.isEmpty {
  6634. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: dataFile)
  6635. if dataDecrypt != nil {
  6636. dataFile = dataDecrypt!
  6637. }
  6638. var sizeOfFile = Int(dataFile.count / 1000000)
  6639. if (sizeOfFile < 1) {
  6640. sizeOfFile = Int(dataFile.count / 1000)
  6641. if (finalExtFile.count > 4) {
  6642. messageText.text = "\(sizeOfFile) kB \u{2022} TXT"
  6643. }else {
  6644. messageText.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())"
  6645. }
  6646. } else {
  6647. if (finalExtFile.count > 4) {
  6648. messageText.text = "\(sizeOfFile) MB \u{2022} TXT"
  6649. }else {
  6650. messageText.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())"
  6651. }
  6652. }
  6653. }
  6654. }
  6655. }
  6656. containerMessage.addSubview(containerViewFile)
  6657. containerViewFile.translatesAutoresizingMaskIntoConstraints = false
  6658. let data = queryMessageReply(message_id: reffChat)
  6659. if (reffChat.isEmpty || data.count == 0) && (dataMessages[indexPath.row][TypeDataMessage.is_forwarded] == nil || dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int == 0) {
  6660. containerViewFile.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6661. }
  6662. containerViewFile.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6663. containerViewFile.bottomAnchor.constraint(equalTo:messageText.topAnchor, constant: -5).isActive = true
  6664. containerViewFile.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6665. // containerViewFile.heightAnchor.constraint(equalToConstant: 50).isActive = true
  6666. containerViewFile.backgroundColor = .black.withAlphaComponent(0.2)
  6667. containerViewFile.layer.cornerRadius = 5.0
  6668. containerViewFile.clipsToBounds = true
  6669. let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .bold, scale: .default)))
  6670. containerViewFile.addSubview(imageFile)
  6671. let nameFile = UILabel()
  6672. containerViewFile.addSubview(nameFile)
  6673. imageFile.translatesAutoresizingMaskIntoConstraints = false
  6674. imageFile.leadingAnchor.constraint(equalTo: containerViewFile.leadingAnchor, constant: 5).isActive = true
  6675. imageFile.trailingAnchor.constraint(equalTo: nameFile.leadingAnchor, constant: -5).isActive = true
  6676. imageFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  6677. imageFile.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6678. imageFile.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6679. imageFile.tintColor = .docColor
  6680. nameFile.translatesAutoresizingMaskIntoConstraints = false
  6681. nameFile.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  6682. nameFile.widthAnchor.constraint(lessThanOrEqualToConstant: 200).isActive = true
  6683. nameFile.font = UIFont.systemFont(ofSize: 12 + offset(), weight: .medium)
  6684. nameFile.textColor = .white
  6685. nameFile.text = originalMessageText.components(separatedBy: "|")[0]
  6686. if (dataMessages[indexPath.row]["progress"] as! Double != 100.0) {
  6687. let containerLoading = UIView()
  6688. containerViewFile.addSubview(containerLoading)
  6689. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  6690. containerLoading.centerYAnchor.constraint(equalTo: containerViewFile.centerYAnchor).isActive = true
  6691. containerLoading.leadingAnchor.constraint(equalTo: nameFile.trailingAnchor, constant: 5).isActive = true
  6692. containerLoading.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  6693. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  6694. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  6695. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  6696. let trackShape = CAShapeLayer()
  6697. trackShape.path = circlePath.cgPath
  6698. trackShape.fillColor = UIColor.clear.cgColor
  6699. trackShape.lineWidth = 5
  6700. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  6701. containerLoading.layer.addSublayer(trackShape)
  6702. let shapeLoading = CAShapeLayer()
  6703. shapeLoading.path = circlePath.cgPath
  6704. shapeLoading.fillColor = UIColor.clear.cgColor
  6705. shapeLoading.lineWidth = 3
  6706. shapeLoading.strokeEnd = 0
  6707. shapeLoading.strokeColor = UIColor.secondaryColor.cgColor
  6708. containerLoading.layer.addSublayer(shapeLoading)
  6709. var imageupload = UIImageView(image: UIImage(systemName: "arrow.up", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6710. if dataMessages[indexPath.row]["f_pin"] as? String != idMe {
  6711. imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  6712. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  6713. }
  6714. imageupload.tintColor = .white
  6715. containerLoading.addSubview(imageupload)
  6716. imageupload.translatesAutoresizingMaskIntoConstraints = false
  6717. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  6718. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  6719. } else {
  6720. nameFile.trailingAnchor.constraint(equalTo: containerViewFile.trailingAnchor, constant: -5).isActive = true
  6721. }
  6722. if !copySession && !forwardSession && !deleteSession {
  6723. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  6724. containerViewFile.addGestureRecognizer(objectTap)
  6725. objectTap.containerFile = containerViewFile
  6726. objectTap.labelFile = nameFile
  6727. objectTap.file_id = fileChat
  6728. objectTap.indexPath = indexPath
  6729. }
  6730. }
  6731. let containerLinkMessage = UIView()
  6732. var isLoadingShowLink = false
  6733. if thumbChat.isEmpty && fileChat.isEmpty && !textChat.isEmpty {
  6734. var text = ""
  6735. let listTextSplitBreak = textChat.components(separatedBy: "\n")
  6736. let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
  6737. if indexFirstLinkSplitBreak != nil {
  6738. let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
  6739. 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) })
  6740. if indexFirstLinkSplitSpace != nil {
  6741. text = listTextSplitSpace[indexFirstLinkSplitSpace!]
  6742. }
  6743. }
  6744. if !text.isEmpty {
  6745. isLoadingShowLink = true
  6746. func showLink() {
  6747. if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
  6748. let title = data["title"] as? String ?? ""
  6749. let description = data["description"] as? String ?? ""
  6750. let imageUrl = data["imageUrl"] as? String
  6751. let link = data["link"] as? String ?? ""
  6752. topMarginText.constant = topMarginText.constant + 85
  6753. containerMessage.addSubview(containerLinkMessage)
  6754. containerLinkMessage.translatesAutoresizingMaskIntoConstraints = false
  6755. containerLinkMessage.leadingAnchor.constraint(equalTo:containerMessage.leadingAnchor, constant: 15).isActive = true
  6756. containerLinkMessage.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6757. containerLinkMessage.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6758. containerLinkMessage.heightAnchor.constraint(equalToConstant: 80.0).isActive = true
  6759. containerLinkMessage.backgroundColor = .gray.withAlphaComponent(0.2)
  6760. let imagePreview = UIImageView()
  6761. if imageUrl != nil {
  6762. containerLinkMessage.addSubview(imagePreview)
  6763. imagePreview.translatesAutoresizingMaskIntoConstraints = false
  6764. imagePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor).isActive = true
  6765. imagePreview.bottomAnchor.constraint(equalTo: containerLinkMessage.bottomAnchor).isActive = true
  6766. imagePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor).isActive = true
  6767. imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
  6768. imagePreview.loadImageAsync(with: imageUrl)
  6769. imagePreview.contentMode = .scaleToFill
  6770. }
  6771. let titlePreview = UILabel()
  6772. containerLinkMessage.addSubview(titlePreview)
  6773. titlePreview.translatesAutoresizingMaskIntoConstraints = false
  6774. if imageUrl != nil {
  6775. titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  6776. } else {
  6777. titlePreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  6778. }
  6779. titlePreview.topAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: 10.0).isActive = true
  6780. titlePreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  6781. titlePreview.text = title
  6782. titlePreview.font = UIFont.systemFont(ofSize: 14.0 + offset(), weight: .bold)
  6783. titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  6784. let descPreview = UILabel()
  6785. containerLinkMessage.addSubview(descPreview)
  6786. descPreview.translatesAutoresizingMaskIntoConstraints = false
  6787. if imageUrl != nil {
  6788. descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  6789. } else {
  6790. descPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  6791. }
  6792. descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true
  6793. descPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  6794. descPreview.text = description
  6795. descPreview.font = UIFont.systemFont(ofSize: 12.0 + offset())
  6796. descPreview.textColor = .gray
  6797. descPreview.numberOfLines = 1
  6798. let linkPreview = UILabel()
  6799. containerLinkMessage.addSubview(linkPreview)
  6800. linkPreview.translatesAutoresizingMaskIntoConstraints = false
  6801. if imageUrl != nil {
  6802. linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true
  6803. } else {
  6804. linkPreview.leadingAnchor.constraint(equalTo: containerLinkMessage.leadingAnchor, constant: 5.0).isActive = true
  6805. }
  6806. linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true
  6807. linkPreview.trailingAnchor.constraint(equalTo: containerLinkMessage.trailingAnchor, constant: -5.0).isActive = true
  6808. linkPreview.text = link
  6809. linkPreview.font = UIFont.systemFont(ofSize: 10.0 + offset())
  6810. linkPreview.textColor = .gray
  6811. linkPreview.numberOfLines = 1
  6812. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 {
  6813. showForwardedSign()
  6814. }
  6815. if !copySession && !forwardSession && !deleteSession {
  6816. let objectTap = ObjectGesture(target: self, action: #selector(tapMessageText(_:)))
  6817. objectTap.message_id = text
  6818. containerLinkMessage.addGestureRecognizer(objectTap)
  6819. }
  6820. }
  6821. }
  6822. var dataURL = ""
  6823. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  6824. do {
  6825. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() {
  6826. if let data = cursor.string(forColumnIndex: 0) {
  6827. dataURL = data
  6828. }
  6829. cursor.close()
  6830. }
  6831. } catch {
  6832. rollback.pointee = true
  6833. print("Access database error: \(error.localizedDescription)")
  6834. }
  6835. })
  6836. if dataURL.isEmpty {
  6837. let urlConfig = URLSessionConfiguration.default
  6838. let sessionDelegate = SelfSignedURLSessionDelegate()
  6839. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  6840. let slp = SwiftLinkPreview(session: session,
  6841. workQueue: SwiftLinkPreview.defaultWorkQueue,
  6842. responseQueue: DispatchQueue.main,
  6843. cache: DisabledCache.instance)
  6844. let preview = slp.preview(text,
  6845. onSuccess: { result in
  6846. let title = result.title ?? "No Title"
  6847. let description = text.contains("google.com") ? "" : result.description
  6848. let imageUrl = result.icon
  6849. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  6850. do {
  6851. var dataJson: [String: Any] = [:]
  6852. dataJson["title"] = title
  6853. dataJson["description"] = description
  6854. dataJson["imageUrl"] = imageUrl
  6855. dataJson["link"] = text
  6856. guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
  6857. return
  6858. }
  6859. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
  6860. "id" : "\(Date().currentTimeMillis().toHex())",
  6861. "link" : text,
  6862. "data_link" : json,
  6863. "retry": 0
  6864. ], replace: true)
  6865. dataURL = json
  6866. showLink()
  6867. DispatchQueue.main.async {
  6868. tableView.reloadRows(at: [indexPath], with: .none)
  6869. }
  6870. } catch {
  6871. rollback.pointee = true
  6872. print("Access database error: \(error.localizedDescription)")
  6873. }
  6874. })
  6875. }, onError: { error in
  6876. })
  6877. } else {
  6878. showLink()
  6879. }
  6880. }
  6881. }
  6882. if (reffChat != "" && dataMessages[indexPath.row]["message_scope_id"] as? String ?? "" != "18") {
  6883. let data = queryMessageReply(message_id: reffChat)
  6884. if data.count != 0 {
  6885. topMarginText.constant = topMarginText.constant + 55
  6886. let containerReply = UIView()
  6887. containerMessage.addSubview(containerReply)
  6888. containerReply.translatesAutoresizingMaskIntoConstraints = false
  6889. containerReply.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  6890. containerReply.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  6891. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6892. containerReply.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  6893. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6894. containerReply.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  6895. } else if containerMessage.subviews.contains(containerLinkMessage) {
  6896. containerReply.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  6897. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  6898. containerReply.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  6899. } else {
  6900. containerReply.bottomAnchor.constraint(equalTo: messageText.topAnchor, constant: -5).isActive = true
  6901. }
  6902. containerReply.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  6903. containerReply.heightAnchor.constraint(equalToConstant: 50).isActive = true
  6904. containerReply.backgroundColor = .black.withAlphaComponent(0.2)
  6905. containerReply.layer.cornerRadius = 5
  6906. containerReply.clipsToBounds = true
  6907. let leftReply = UIView()
  6908. containerReply.addSubview(leftReply)
  6909. leftReply.translatesAutoresizingMaskIntoConstraints = false
  6910. leftReply.leadingAnchor.constraint(equalTo: containerReply.leadingAnchor).isActive = true
  6911. leftReply.topAnchor.constraint(equalTo: containerReply.topAnchor).isActive = true
  6912. leftReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor).isActive = true
  6913. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  6914. leftReply.layer.cornerRadius = 5
  6915. leftReply.clipsToBounds = true
  6916. leftReply.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
  6917. let titleReply = UILabel()
  6918. containerReply.addSubview(titleReply)
  6919. titleReply.translatesAutoresizingMaskIntoConstraints = false
  6920. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  6921. titleReply.topAnchor.constraint(equalTo: containerReply.topAnchor, constant: 10).isActive = true
  6922. titleReply.trailingAnchor.constraint(lessThanOrEqualTo: containerReply.trailingAnchor, constant: -20).isActive = true
  6923. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  6924. if (data["f_pin"] as? String == idMe) {
  6925. titleReply.text = "You".localized()
  6926. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  6927. titleReply.textColor = .white
  6928. leftReply.backgroundColor = .white
  6929. } else {
  6930. titleReply.textColor = .mainColor
  6931. leftReply.backgroundColor = .mainColor
  6932. }
  6933. } else {
  6934. if isContactCenter {
  6935. let user: [User] = users.filter({$0.pin == data["f_pin"] as? String})
  6936. titleReply.text = user.first!.fullName
  6937. } else {
  6938. titleReply.text = self.dataPerson["name"]!!
  6939. }
  6940. if dataMessages[indexPath.row]["f_pin"] as? String == idMe {
  6941. titleReply.textColor = .white
  6942. leftReply.backgroundColor = .white
  6943. } else {
  6944. titleReply.textColor = .mainColor
  6945. leftReply.backgroundColor = .mainColor
  6946. }
  6947. }
  6948. let contentReply = UILabel()
  6949. containerReply.addSubview(contentReply)
  6950. contentReply.translatesAutoresizingMaskIntoConstraints = false
  6951. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  6952. contentReply.bottomAnchor.constraint(equalTo: containerReply.bottomAnchor, constant: -10).isActive = true
  6953. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  6954. let message_text = data["message_text"] as? String ?? ""
  6955. let attachment_flag = data["attachment_flag"] as? String ?? ""
  6956. let thumb_chat = data["thumb_id"] as? String ?? ""
  6957. let image_chat = data["image_id"] as? String ?? ""
  6958. let video_chat = data["video_id"] as? String ?? ""
  6959. let file_chat = data["file_id"] as? String ?? ""
  6960. if (attachment_flag == "0" && thumb_chat == "") {
  6961. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  6962. contentReply.attributedText = message_text.richText()
  6963. } else if (attachment_flag == "1" || image_chat != "") {
  6964. if (message_text == "") {
  6965. contentReply.text = "📷 Photo".localized()
  6966. } else {
  6967. contentReply.attributedText = message_text.richText()
  6968. }
  6969. } else if (attachment_flag == "2" || video_chat != "") {
  6970. if (message_text == "") {
  6971. contentReply.text = "📹 Video".localized()
  6972. } else {
  6973. contentReply.attributedText = message_text.richText()
  6974. }
  6975. } else if (attachment_flag == "6" || file_chat != ""){
  6976. contentReply.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -20).isActive = true
  6977. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  6978. } else if (attachment_flag == "11") {
  6979. contentReply.text = "❤️ Sticker"
  6980. }
  6981. contentReply.textColor = .white.withAlphaComponent(0.8)
  6982. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  6983. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  6984. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  6985. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  6986. if let dirPath = paths.first {
  6987. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  6988. DispatchQueue.main.async {
  6989. let image : UIImage? = {
  6990. if let img = Nexilis.imageCache.object(forKey: thumbChat as NSString) {
  6991. return img
  6992. }
  6993. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  6994. Nexilis.imageCache.setObject(img, forKey: thumbChat as NSString)
  6995. return img
  6996. }
  6997. return nil
  6998. }()
  6999. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  7000. let imageThumb = UIImageView(image: image)
  7001. containerReply.addSubview(imageThumb)
  7002. imageThumb.layer.cornerRadius = 2.0
  7003. imageThumb.clipsToBounds = true
  7004. imageThumb.contentMode = .scaleAspectFill
  7005. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  7006. imageThumb.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  7007. imageThumb.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  7008. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7009. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7010. if (attachment_flag == "2") {
  7011. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  7012. imageThumb.addSubview(imagePlay)
  7013. imagePlay.clipsToBounds = true
  7014. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  7015. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  7016. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  7017. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  7018. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  7019. imagePlay.tintColor = .white
  7020. }
  7021. titleReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  7022. contentReply.trailingAnchor.constraint(equalTo: imageThumb.leadingAnchor, constant: -20).isActive = true
  7023. }
  7024. }
  7025. }
  7026. if (attachment_flag == "11" && message_text.components(separatedBy: "/").count > 1) {
  7027. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  7028. containerReply.addSubview(imageSticker)
  7029. imageSticker.layer.cornerRadius = 2.0
  7030. imageSticker.clipsToBounds = true
  7031. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  7032. imageSticker.trailingAnchor.constraint(equalTo: containerReply.trailingAnchor, constant: -10).isActive = true
  7033. imageSticker.centerYAnchor.constraint(equalTo: containerReply.centerYAnchor).isActive = true
  7034. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7035. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7036. titleReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  7037. contentReply.trailingAnchor.constraint(equalTo: imageSticker.leadingAnchor, constant: -20).isActive = true
  7038. }
  7039. if !copySession && !forwardSession && !deleteSession {
  7040. let objectTap = ObjectGesture(target: self, action: #selector(contentMessageTapped(_:)))
  7041. containerReply.addGestureRecognizer(objectTap)
  7042. objectTap.indexPath = indexPath
  7043. objectTap.message_id = data["message_id"] as? String ?? ""
  7044. }
  7045. }
  7046. }
  7047. if dataMessages[indexPath.row][TypeDataMessage.is_forwarded] != nil && dataMessages[indexPath.row][TypeDataMessage.is_forwarded] as! Int != 0 && !isLoadingShowLink {
  7048. showForwardedSign()
  7049. }
  7050. func showForwardedSign() {
  7051. topMarginText.constant = topMarginText.constant + 20
  7052. let containerForwarded = UIView()
  7053. containerMessage.addSubview(containerForwarded)
  7054. containerForwarded.translatesAutoresizingMaskIntoConstraints = false
  7055. containerForwarded.leadingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 15).isActive = true
  7056. containerForwarded.topAnchor.constraint(equalTo: containerMessage.topAnchor, constant: 15).isActive = true
  7057. containerForwarded.trailingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -15).isActive = true
  7058. containerForwarded.heightAnchor.constraint(equalToConstant: 20).isActive = true
  7059. if thumbChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  7060. containerForwarded.bottomAnchor.constraint(equalTo: imageThumb.topAnchor, constant: -5).isActive = true
  7061. } else if fileChat != "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  7062. containerForwarded.bottomAnchor.constraint(equalTo: containerViewFile.topAnchor, constant: -5).isActive = true
  7063. } else if containerMessage.subviews.contains(containerLinkMessage) {
  7064. containerForwarded.bottomAnchor.constraint(equalTo: containerLinkMessage.topAnchor, constant: -5).isActive = true
  7065. } else if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as? String ?? "" != "1") {
  7066. containerForwarded.bottomAnchor.constraint(equalTo: imageSticker.topAnchor, constant: -5).isActive = true
  7067. }
  7068. let imageForwarded = UIImageView()
  7069. containerForwarded.addSubview(imageForwarded)
  7070. imageForwarded.anchor(top: containerForwarded.topAnchor, left: containerForwarded.leftAnchor, width: 15, height: 15)
  7071. imageForwarded.image = UIImage(systemName: "arrowshape.turn.up.right.fill")
  7072. imageForwarded.tintColor = .gray
  7073. let titleForwarded = UILabel()
  7074. containerForwarded.addSubview(titleForwarded)
  7075. titleForwarded.anchor(top: containerForwarded.topAnchor, left: imageForwarded.rightAnchor, right: containerForwarded.rightAnchor, height: 15)
  7076. titleForwarded.font = .systemFont(ofSize: 15)
  7077. let textForwarded = "Forwarded".localized()
  7078. titleForwarded.attributedText = " $\(textForwarded)$".richText()
  7079. }
  7080. if messageText.isDescendant(of: containerMessage) {
  7081. topMarginText.isActive = true
  7082. }
  7083. // let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureCellAction))
  7084. // panGestureRecognizer.delegate = self
  7085. // cellMessage.addGestureRecognizer(panGestureRecognizer)
  7086. return cell
  7087. }
  7088. func playPauseAudio(indexPath: IndexPath, playButton: UIButton, progressSlider: UISlider, timeLabel: UILabel) {
  7089. guard let audioPlayer = audioPlayers[indexPath] else { return }
  7090. if audioPlayer.isPlaying {
  7091. // Pause Audio
  7092. audioPlayer.pause()
  7093. playButton.setImage(UIImage(systemName: "play.fill"), for: .normal)
  7094. timers[indexPath]?.invalidate()
  7095. } else {
  7096. // Stop other players if one is already playing
  7097. if let currentPlayingIndexPath = playingIndexPath, let currentAudioPlayer = audioPlayers[currentPlayingIndexPath] {
  7098. if currentPlayingIndexPath != indexPath {
  7099. currentAudioPlayer.pause()
  7100. timers[currentPlayingIndexPath]?.invalidate()
  7101. timers[currentPlayingIndexPath] = nil
  7102. audioPlayers[currentPlayingIndexPath] = nil
  7103. tableChatView.reloadRows(at: [currentPlayingIndexPath], with: .none)
  7104. }
  7105. }
  7106. do {
  7107. try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
  7108. try AVAudioSession.sharedInstance().setActive(true)
  7109. } catch {
  7110. }
  7111. // Play new audio
  7112. audioPlayer.play()
  7113. playButton.setImage(UIImage(systemName: "pause.fill"), for: .normal)
  7114. playingIndexPath = indexPath
  7115. // Start timer to update progress
  7116. timers[indexPath] = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
  7117. progressSlider.value = Float(audioPlayer.currentTime)
  7118. timeLabel.text = self.formatTime(audioPlayer.currentTime)
  7119. }
  7120. }
  7121. }
  7122. func sliderChanged(indexPath: IndexPath, progressSlider: UISlider, timeLabel: UILabel) {
  7123. guard let audioPlayer = audioPlayers[indexPath] else { return }
  7124. audioPlayer.currentTime = TimeInterval(progressSlider.value)
  7125. timeLabel.text = formatTime(audioPlayer.currentTime)
  7126. }
  7127. func formatTime(_ time: TimeInterval) -> String {
  7128. let roundedTime = time.rounded(.up)
  7129. let minutes = Int(roundedTime) / 60
  7130. let seconds = Int(roundedTime) % 60
  7131. return String(format: "%d:%02d", minutes, seconds)
  7132. }
  7133. public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
  7134. if let finishedIndexPath = audioPlayers.first(where: { $0.value == player })?.key {
  7135. DispatchQueue.main.async {
  7136. self.timers[finishedIndexPath]?.invalidate()
  7137. self.timers[finishedIndexPath] = nil
  7138. self.playingIndexPath = nil
  7139. self.audioPlayers[finishedIndexPath] = nil
  7140. self.tableChatView.reloadRows(at: [finishedIndexPath], with: .none)
  7141. }
  7142. }
  7143. }
  7144. @objc func imageGroupingTapped(_ sender: ObjectGesture) {
  7145. let listGroupingImages = ListGroupImages()
  7146. listGroupingImages.imageTapped = sender.indexImageTapped
  7147. listGroupingImages.listGroupingImages = sender.listImageFromGrouping
  7148. listGroupingImages.titleName = titleText
  7149. listGroupingImages.isInitiator = sender.isInitiator
  7150. listGroupingImages.updateEditor = { [self] updatedData, replyData, isUpdateDelete in
  7151. if replyData.count == 0 {
  7152. if updatedData.count != 0 && !isUpdateDelete {
  7153. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  7154. } else if updatedData.count > 0 {
  7155. let deletedForEveryoneData = updatedData.filter({ $0.dataMessage["lock"] as? String == "1" })
  7156. if deletedForEveryoneData.count != 0 {
  7157. if groupImages[sender.listImageFromGrouping[0].messageId] != nil {
  7158. var dataWillEmpty = updatedData
  7159. while dataWillEmpty.count > 0 {
  7160. if let lastIdx = dataWillEmpty.lastIndex(where: { $0.dataMessage["lock"] as? String == "1" }) {
  7161. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  7162. if dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId {
  7163. self.dataMessages.remove(at: idx)
  7164. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx)
  7165. } else {
  7166. self.dataMessages.insert(dataWillEmpty[lastIdx].dataMessage, at: idx + 1)
  7167. }
  7168. let subData = Array(updatedData[lastIdx+1..<dataWillEmpty.count])
  7169. if subData.count >= 4 {
  7170. groupImages[subData[0].messageId] = subData
  7171. self.dataMessages.insert(subData[0].dataMessage, at: lastIdx + 1)
  7172. } else {
  7173. if subData.count > 0 {
  7174. self.dataMessages.insert(contentsOf: subData.map({ $0.dataMessage }), at: idx + (dataWillEmpty[lastIdx].messageId == sender.listImageFromGrouping[0].messageId ? 1 : 2))
  7175. }
  7176. }
  7177. }
  7178. dataWillEmpty.removeSubrange(lastIdx..<dataWillEmpty.count)
  7179. } else if dataWillEmpty.count >= 4 {
  7180. groupImages[dataWillEmpty[0].messageId] = dataWillEmpty
  7181. dataWillEmpty.removeAll()
  7182. } else {
  7183. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  7184. self.dataMessages.remove(at: idx)
  7185. self.dataMessages.insert(contentsOf: dataWillEmpty.map({ $0.dataMessage }), at: idx)
  7186. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  7187. }
  7188. dataWillEmpty.removeAll()
  7189. }
  7190. }
  7191. } else {
  7192. }
  7193. } else {
  7194. if updatedData.count >= 4 {
  7195. if updatedData[0].messageId == sender.listImageFromGrouping[0].messageId {
  7196. groupImages[sender.listImageFromGrouping[0].messageId] = updatedData
  7197. } else {
  7198. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  7199. self.dataMessages.remove(at: idx)
  7200. self.dataMessages.insert(updatedData[0].dataMessage, at: idx)
  7201. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  7202. groupImages[updatedData[0].messageId] = updatedData
  7203. }
  7204. }
  7205. } else {
  7206. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  7207. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  7208. self.dataMessages.remove(at: idx)
  7209. let dataMessageInGrouping = updatedData.map({ $0.dataMessage })
  7210. self.dataMessages.insert(contentsOf: dataMessageInGrouping, at: idx)
  7211. }
  7212. }
  7213. }
  7214. } else {
  7215. groupImages.removeValue(forKey: sender.listImageFromGrouping[0].messageId)
  7216. if let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String ?? "" == sender.listImageFromGrouping[0].messageId }) {
  7217. self.dataMessages.remove(at: idx)
  7218. }
  7219. }
  7220. DispatchQueue.main.async { [self] in
  7221. tableChatView.reloadData()
  7222. }
  7223. } else if replyData.count != 0 {
  7224. handleReply(indexPath: IndexPath(row: 0, section: 0), dataMessagesImage: replyData)
  7225. }
  7226. }
  7227. self.navigationController?.pushViewController(listGroupingImages, animated: true)
  7228. }
  7229. @objc func tapAck(_ sender: ObjectGesture) {
  7230. if blocking == "1" {
  7231. self.view.makeToast("You blocked this user".localized(), duration: 3)
  7232. return
  7233. }
  7234. if blocking == "-1" {
  7235. self.view.makeToast("You have been blocked by this user".localized(), duration: 3)
  7236. return
  7237. }
  7238. let indexPath = sender.indexPath
  7239. let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  7240. if dataMessages[indexPath.row]["status"] as? String ?? "" == "8" {
  7241. return
  7242. }
  7243. if !CheckConnection.isConnectedToNetwork() || API.nGetCLXConnState() == 0 {
  7244. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  7245. imageView.tintColor = .white
  7246. 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)
  7247. banner.show()
  7248. return
  7249. }
  7250. DispatchQueue.global().async {
  7251. 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: ""))
  7252. if result != nil {
  7253. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  7254. do {
  7255. _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
  7256. "status" : "8"
  7257. ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as? String ?? "")'")
  7258. } catch {
  7259. rollback.pointee = true
  7260. print("Access database error: \(error.localizedDescription)")
  7261. }
  7262. })
  7263. DispatchQueue.main.async {
  7264. if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
  7265. self.dataMessages[index]["status"] = "8"
  7266. let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"] as? String ?? "")
  7267. 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 ?? ""})
  7268. if row != nil && section != nil {
  7269. self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
  7270. }
  7271. self.view.makeToast("Confirmation Success.".localized(), duration: 3)
  7272. }
  7273. }
  7274. }
  7275. }
  7276. }
  7277. // public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
  7278. // let velocity : CGPoint = gestureRecognizer.location(in: tableChatView)
  7279. // if velocity.x < 0 {
  7280. // return false
  7281. // }
  7282. // return abs(Float(velocity.x)) > abs(Float(velocity.y))
  7283. // }
  7284. //
  7285. // @objc func panGestureCellAction(recognizer: UIPanGestureRecognizer) {
  7286. // let translation = recognizer.translation(in: tableChatView)
  7287. // let x = recognizer.view?.frame.origin.x ?? 0
  7288. // if x >= -(recognizer.view?.frame.size.width ?? 0) * 0.05 {
  7289. // recognizer.view?.center = CGPoint(
  7290. // x: (recognizer.view?.center.x ?? 0) + translation.x,
  7291. // y: (recognizer.view?.center.y ?? 0))
  7292. // recognizer.setTranslation(CGPoint(x: 0, y: 0), in: view)
  7293. // if (recognizer.view?.frame.origin.x ?? 0) > UIScreen.main.bounds.size.width * 0.9 {
  7294. // UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
  7295. // 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)
  7296. // })
  7297. // }
  7298. // }
  7299. // if x <= -(recognizer.view?.frame.size.width ?? 0) * 0.05 {
  7300. // let idMe = User.getMyPin() as String?
  7301. // let indexPath = self.tableChatView.indexPath(for: recognizer.view! as! UITableViewCell)
  7302. // let dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath!.section]})
  7303. // if (dataMessages[indexPath!.row]["f_pin"] as? String == idMe) {
  7304. // let messageInfoVC = MessageInfo()
  7305. // messageInfoVC.data = dataMessages[indexPath!.row]
  7306. // self.navigationController?.pushViewController(messageInfoVC, animated: true)
  7307. // return
  7308. // }
  7309. // }
  7310. // if x >= ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  7311. // if !hapticSwipeLeft {
  7312. // UINotificationFeedbackGenerator().notificationOccurred(.success)
  7313. // }
  7314. // hapticSwipeLeft = true
  7315. // } else if x < ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  7316. // hapticSwipeLeft = false
  7317. // }
  7318. // if recognizer.state == .ended {
  7319. // UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
  7320. // 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)
  7321. // } completion: { (finished) in
  7322. // if x > ((recognizer.view?.frame.size.width ?? 0) * 0.2) {
  7323. // self.hapticSwipeLeft = false
  7324. //
  7325. // }
  7326. // }
  7327. // }
  7328. // }
  7329. public func numberOfSections(in tableView: UITableView) -> Int {
  7330. dataDates.count
  7331. }
  7332. public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  7333. let count = dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[section] }).count
  7334. return count
  7335. }
  7336. @objc func contentMessageTapped(_ sender: ObjectGesture) {
  7337. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7338. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7339. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7340. if (sender.image_id != "") {
  7341. if let dirPath = paths.first {
  7342. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  7343. if FileManager.default.fileExists(atPath: imageURL.path) {
  7344. let image = UIImage(contentsOfFile: imageURL.path)
  7345. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  7346. previewImageVC.image = image
  7347. previewImageVC.isHiddenTextField = true
  7348. previewImageVC.modalPresentationStyle = .custom
  7349. previewImageVC.modalTransitionStyle = .crossDissolve
  7350. self.present(previewImageVC, animated: true, completion: nil)
  7351. } else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
  7352. do {
  7353. if var data = try FileEncryption.shared.readSecure(filename: sender.image_id) {
  7354. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  7355. if dataDecrypt != nil {
  7356. data = dataDecrypt!
  7357. }
  7358. let image = UIImage(data: data)
  7359. let previewImageVC = PreviewAttachmentImageVideo(nibName: "PreviewAttachmentImageVideo", bundle: Bundle.resourceBundle(for: Nexilis.self))
  7360. previewImageVC.image = image
  7361. previewImageVC.isHiddenTextField = true
  7362. previewImageVC.modalPresentationStyle = .custom
  7363. previewImageVC.modalTransitionStyle = .crossDissolve
  7364. self.present(previewImageVC, animated: true, completion: nil)
  7365. }
  7366. }
  7367. catch {
  7368. print("Error reading secure file")
  7369. }
  7370. } else {
  7371. for view in sender.imageView.subviews {
  7372. if view is UIImageView {
  7373. view.removeFromSuperview()
  7374. }
  7375. }
  7376. let activityIndicator = UIActivityIndicatorView(style: .large)
  7377. activityIndicator.color = .mainColor
  7378. activityIndicator.hidesWhenStopped = true
  7379. activityIndicator.center = CGPoint(x:sender.imageView.frame.width/2,
  7380. y: sender.imageView.frame.height/2)
  7381. activityIndicator.startAnimating()
  7382. sender.imageView.addSubview(activityIndicator)
  7383. Download().startHTTP(forKey: sender.image_id) { (name, progress) in
  7384. guard progress == 100 else {
  7385. return
  7386. }
  7387. do {
  7388. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7389. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7390. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7391. if let dirPath = paths.first {
  7392. let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.image_id)
  7393. if FileManager.default.fileExists(atPath: imageURL.path) {
  7394. let image = UIImage(contentsOfFile: imageURL.path)
  7395. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7396. if save {
  7397. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  7398. }
  7399. }
  7400. else if FileEncryption.shared.isSecureExists(filename: sender.image_id) {
  7401. if var secureData = try FileEncryption.shared.readSecure(filename: sender.image_id) {
  7402. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  7403. if dataDecrypt != nil {
  7404. secureData = dataDecrypt!
  7405. }
  7406. let image = UIImage(data: secureData)
  7407. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7408. if save {
  7409. UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
  7410. }
  7411. }
  7412. }
  7413. }
  7414. } catch {
  7415. }
  7416. DispatchQueue.main.async {
  7417. activityIndicator.stopAnimating()
  7418. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  7419. }
  7420. }
  7421. }
  7422. }
  7423. } else if (sender.gif_id != "") {
  7424. if let dirPath = paths.first {
  7425. let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.gif_id)
  7426. if FileManager.default.fileExists(atPath: gifURL.path) {
  7427. do {
  7428. let data = try Data(contentsOf: gifURL)
  7429. APIS.openImageNexilis(image: UIImage(), data: data, isGIF: true)
  7430. } catch {
  7431. }
  7432. } else if FileEncryption.shared.isSecureExists(filename: sender.gif_id) {
  7433. do {
  7434. if var secureData = try FileEncryption.shared.readSecure(filename: sender.gif_id) {
  7435. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  7436. if dataDecrypt != nil {
  7437. secureData = dataDecrypt!
  7438. }
  7439. APIS.openImageNexilis(image: UIImage(), data: secureData, isGIF: true)
  7440. }
  7441. } catch {
  7442. }
  7443. }
  7444. }
  7445. } else if (sender.video_id != "") {
  7446. if let dirPath = paths.first {
  7447. let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.video_id)
  7448. if FileManager.default.fileExists(atPath: videoURL.path) {
  7449. let player = AVPlayer(url: videoURL as URL)
  7450. let playerVC = AVPlayerViewController()
  7451. playerVC.modalPresentationStyle = .custom
  7452. playerVC.player = player
  7453. self.present(playerVC, animated: true, completion: nil)
  7454. } else if FileEncryption.shared.isSecureExists(filename: sender.video_id) {
  7455. do {
  7456. if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
  7457. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  7458. if dataDecrypt != nil {
  7459. secureData = dataDecrypt!
  7460. }
  7461. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7462. let tempPath = cachesDirectory.appendingPathComponent(sender.video_id)
  7463. try secureData.write(to: tempPath)
  7464. let player = AVPlayer(url: tempPath as URL)
  7465. let playerVC = AVPlayerViewController()
  7466. playerVC.modalPresentationStyle = .custom
  7467. playerVC.player = player
  7468. self.present(playerVC, animated: true, completion: nil)
  7469. }
  7470. } catch {
  7471. }
  7472. } else {
  7473. for view in sender.imageView.subviews {
  7474. if view is UIImageView {
  7475. view.removeFromSuperview()
  7476. }
  7477. }
  7478. let container = UIView()
  7479. sender.imageView.addSubview(container)
  7480. container.translatesAutoresizingMaskIntoConstraints = false
  7481. container.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  7482. container.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  7483. container.widthAnchor.constraint(equalToConstant: 50).isActive = true
  7484. container.heightAnchor.constraint(equalToConstant: 50).isActive = true
  7485. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 20, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  7486. let trackShape = CAShapeLayer()
  7487. trackShape.path = circlePath.cgPath
  7488. trackShape.fillColor = UIColor.clear.cgColor
  7489. trackShape.lineWidth = 10
  7490. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  7491. container.backgroundColor = .clear
  7492. container.layer.addSublayer(trackShape)
  7493. let shapeLoading = CAShapeLayer()
  7494. shapeLoading.path = circlePath.cgPath
  7495. shapeLoading.fillColor = UIColor.clear.cgColor
  7496. shapeLoading.lineWidth = 10
  7497. shapeLoading.strokeEnd = 0
  7498. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  7499. container.layer.addSublayer(shapeLoading)
  7500. let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  7501. imageDownload.tintColor = .white
  7502. container.addSubview(imageDownload)
  7503. imageDownload.translatesAutoresizingMaskIntoConstraints = false
  7504. imageDownload.centerXAnchor.constraint(equalTo: sender.imageView.centerXAnchor).isActive = true
  7505. imageDownload.centerYAnchor.constraint(equalTo: sender.imageView.centerYAnchor).isActive = true
  7506. imageDownload.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7507. imageDownload.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7508. Download().startHTTP(forKey: sender.video_id, isImage: false) { (name, progress) in
  7509. DispatchQueue.main.async {
  7510. guard progress == 100 else {
  7511. shapeLoading.strokeEnd = CGFloat(progress / 100)
  7512. return
  7513. }
  7514. do {
  7515. if var secureData = try FileEncryption.shared.readSecure(filename: sender.video_id) {
  7516. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  7517. if dataDecrypt != nil {
  7518. secureData = dataDecrypt!
  7519. }
  7520. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7521. let tempPath = cachesDirectory.appendingPathComponent(name)
  7522. try secureData.write(to: tempPath)
  7523. let save: Bool = SecureUserDefaults.shared.value(forKey: "saveToGallery") ?? false
  7524. if save {
  7525. PHPhotoLibrary.shared().performChanges({
  7526. PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: tempPath)
  7527. }) { saved, error in
  7528. }
  7529. }
  7530. }
  7531. } catch {
  7532. }
  7533. let idx = self.dataMessages.firstIndex(where: { $0["video_id"] as? String ?? "" == sender.video_id})
  7534. if idx != nil {
  7535. self.dataMessages[idx!]["progress"] = progress
  7536. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  7537. }
  7538. }
  7539. }
  7540. }
  7541. }
  7542. } else if (sender.file_id != "") {
  7543. if let dirPath = paths.first {
  7544. let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id)
  7545. if FileManager.default.fileExists(atPath: fileURL.path) {
  7546. self.previewItem = fileURL as NSURL
  7547. let previewController = CustomQLPreviewController()
  7548. // let rightBarButton = UIBarButtonItem()
  7549. // previewController.navigationItem.rightBarButtonItem = rightBarButton
  7550. previewController.dataSource = self
  7551. // previewController.modalPresentationStyle = .custom
  7552. self.present(previewController, animated: true)
  7553. } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) {
  7554. do {
  7555. if var docData = try FileEncryption.shared.readSecure(filename: sender.file_id) {
  7556. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: docData)
  7557. if dataDecrypt != nil {
  7558. docData = dataDecrypt!
  7559. }
  7560. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  7561. let tempPath = cachesDirectory.appendingPathComponent(sender.file_id)
  7562. try docData.write(to: tempPath)
  7563. self.previewItem = tempPath as NSURL
  7564. let previewController = CustomQLPreviewController()
  7565. // let rightBarButton = UIBarButtonItem()
  7566. // previewController.navigationItem.rightBarButtonItem = rightBarButton
  7567. previewController.dataSource = self
  7568. // previewController.modalPresentationStyle = .custom
  7569. self.present(previewController,animated: true)
  7570. }
  7571. }
  7572. catch {
  7573. }
  7574. } else {
  7575. for view in sender.containerFile.subviews {
  7576. if !(view is UIImageView) && !(view is UILabel) {
  7577. view.removeFromSuperview()
  7578. }
  7579. }
  7580. let containerLoading = UIView()
  7581. sender.containerFile.addSubview(containerLoading)
  7582. containerLoading.translatesAutoresizingMaskIntoConstraints = false
  7583. containerLoading.centerYAnchor.constraint(equalTo: sender.containerFile.centerYAnchor).isActive = true
  7584. containerLoading.leadingAnchor.constraint(equalTo: sender.labelFile.trailingAnchor, constant: 5).isActive = true
  7585. containerLoading.trailingAnchor.constraint(equalTo: sender.containerFile.trailingAnchor, constant: -5).isActive = true
  7586. containerLoading.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7587. containerLoading.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7588. let circlePath = UIBezierPath(arcCenter: CGPoint(x: 15, y: 15), radius: 10, startAngle: -(.pi / 2), endAngle: .pi * 2, clockwise: true)
  7589. let trackShape = CAShapeLayer()
  7590. trackShape.path = circlePath.cgPath
  7591. trackShape.fillColor = UIColor.clear.cgColor
  7592. trackShape.lineWidth = 5
  7593. trackShape.strokeColor = UIColor.blueBubbleColor.withAlphaComponent(0.3).cgColor
  7594. containerLoading.layer.addSublayer(trackShape)
  7595. let shapeLoading = CAShapeLayer()
  7596. shapeLoading.path = circlePath.cgPath
  7597. shapeLoading.fillColor = UIColor.clear.cgColor
  7598. shapeLoading.lineWidth = 3
  7599. shapeLoading.strokeEnd = 0
  7600. shapeLoading.strokeColor = UIColor.blueBubbleColor.cgColor
  7601. containerLoading.layer.addSublayer(shapeLoading)
  7602. let imageupload = UIImageView(image: UIImage(systemName: "arrow.down", withConfiguration: UIImage.SymbolConfiguration(pointSize: 10, weight: .bold, scale: .default)))
  7603. imageupload.tintColor = .white
  7604. containerLoading.addSubview(imageupload)
  7605. imageupload.translatesAutoresizingMaskIntoConstraints = false
  7606. imageupload.centerYAnchor.constraint(equalTo: containerLoading.centerYAnchor).isActive = true
  7607. imageupload.centerXAnchor.constraint(equalTo: containerLoading.centerXAnchor).isActive = true
  7608. Download().startHTTP(forKey: sender.file_id, isImage: false) { (name, progress) in
  7609. DispatchQueue.main.async {
  7610. guard progress == 100 else {
  7611. shapeLoading.strokeEnd = CGFloat(progress / 100)
  7612. return
  7613. }
  7614. let idx = self.dataMessages.firstIndex(where: { $0["file_id"] as? String ?? "" == sender.file_id})
  7615. if idx != nil {
  7616. self.dataMessages[idx!]["progress"] = progress
  7617. self.tableChatView.reloadRows(at: [sender.indexPath], with: .none)
  7618. }
  7619. }
  7620. }
  7621. }
  7622. }
  7623. } else {
  7624. DispatchQueue.main.async {
  7625. let idx = self.dataMessages.firstIndex(where: { $0["message_id"] as? String == sender.message_id})
  7626. if idx == nil {
  7627. return
  7628. }
  7629. let section = self.dataDates.firstIndex(of: self.dataMessages[idx!]["chat_date"] as? String ?? "")
  7630. if section == nil {
  7631. return
  7632. }
  7633. 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})
  7634. if row == nil {
  7635. return
  7636. }
  7637. let indexPath = IndexPath(row: row!, section: section!)
  7638. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  7639. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  7640. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  7641. let containerMessage = cell.contentView.subviews[0]
  7642. let idMe = User.getMyPin() as String?
  7643. if (self.dataMessages[idx!]["f_pin"] as? String == idMe) {
  7644. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  7645. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7646. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  7647. containerMessage.backgroundColor = .clear
  7648. } else {
  7649. containerMessage.backgroundColor = .blueBubbleColor
  7650. }
  7651. }
  7652. } else {
  7653. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  7654. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7655. if (self.dataMessages[idx!]["attachment_flag"] as? String == "11") {
  7656. containerMessage.backgroundColor = .clear
  7657. } else {
  7658. containerMessage.backgroundColor = .whiteBubbleColor
  7659. }
  7660. }
  7661. }
  7662. }
  7663. }
  7664. }
  7665. }
  7666. }
  7667. func highlightedText(for text: String, textView: UITextView) -> NSAttributedString {
  7668. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  7669. if let range = textView.attributedText.string.range(of: text) {
  7670. let nsRange = NSRange(range, in: textView.attributedText.string)
  7671. mutableAttributedString.addAttribute(.backgroundColor, value: UIColor.lightGray.withAlphaComponent(0.5), range: NSRange(range, in: text))
  7672. }
  7673. return mutableAttributedString
  7674. }
  7675. func removeHighlightedText(for text: String, textView: UITextView) -> NSAttributedString {
  7676. let mutableAttributedString = textView.attributedText!.mutableCopy() as! NSMutableAttributedString
  7677. if let range = textView.attributedText.string.range(of: text) {
  7678. let nsRange = NSRange(range, in: textView.attributedText.string)
  7679. mutableAttributedString.removeAttribute(.backgroundColor, range: NSRange(range, in: text))
  7680. }
  7681. return mutableAttributedString
  7682. }
  7683. @objc func tapMessageText(_ sender: ObjectGesture) {
  7684. var stringURl = sender.message_id
  7685. if stringURl.lowercased().starts(with: "www.") {
  7686. stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
  7687. }
  7688. if Nexilis.checkingAccess(key: "secure_browser") {
  7689. APIS.openUrl(url: stringURl)
  7690. } else {
  7691. guard let url = URL(string: stringURl) else { return }
  7692. UIApplication.shared.open(url)
  7693. }
  7694. }
  7695. // public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  7696. // if copySession || forwardSession || deleteSession {
  7697. // return nil
  7698. // }
  7699. // let idMe = User.getMyPin() as String?
  7700. // if (dataMessages[indexPath.row]["f_pin"] as? String != idMe) {
  7701. // return nil
  7702. // }
  7703. // let messageInfoVC = MessageInfo()
  7704. // messageInfoVC.data = dataMessages[indexPath.row]
  7705. // self.navigationController?.show(messageInfoVC, sender: nil)
  7706. // return UISwipeActionsConfiguration()
  7707. // }
  7708. //
  7709. // public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  7710. // if copySession || forwardSession || deleteSession {
  7711. // return nil
  7712. // }
  7713. // let action = UIContextualAction(style: .normal, title: "Reply") { [weak self] (action, view, completionHandler) in
  7714. // let feedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
  7715. // feedbackGenerator.impactOccurred()
  7716. //
  7717. // self?.handleReply(indexPath: indexPath)
  7718. // completionHandler(true)
  7719. // }
  7720. // action.title = nil
  7721. // action.backgroundColor = .white
  7722. // action.image = UIImage(systemName: "arrowshape.turn.up.left.circle.fill")?.withTintColor(.gray, renderingMode: .alwaysOriginal)
  7723. // let config = UISwipeActionsConfiguration(actions: [action])
  7724. // config.performsFirstActionWithFullSwipe = false
  7725. // return config
  7726. // }
  7727. private func handleReply(indexPath: IndexPath, dataMessagesImage: [String: Any?] = [:], reffId: String = "") {
  7728. var dataMessages = self.dataMessages.filter({ $0["chat_date"] as? String ?? "" == dataDates[indexPath.section]})
  7729. if reffId.isEmpty {
  7730. self.deleteReplyView()
  7731. if dataMessagesImage.count != 0 {
  7732. dataMessages = [dataMessagesImage]
  7733. } else {
  7734. self.textFieldSend.becomeFirstResponder()
  7735. }
  7736. self.reffId = dataMessages[indexPath.row]["message_id"] as? String
  7737. } else {
  7738. dataMessages = self.dataMessages.filter({ $0["message_id"] as? String ?? "" == reffId })
  7739. self.reffId = reffId
  7740. }
  7741. if dataMessages.count == 0 {
  7742. self.deleteReplyView()
  7743. return
  7744. }
  7745. UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
  7746. self.constraintTopTextField.constant = self.constraintTopTextField.constant + 50
  7747. }, completion: nil)
  7748. if (self.currentIndexpath != nil) {
  7749. DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
  7750. self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
  7751. }
  7752. } else {
  7753. self.tableChatView.scrollToBottom()
  7754. }
  7755. self.viewTextfield.addSubview(self.containerPreviewReply)
  7756. self.containerPreviewReply.translatesAutoresizingMaskIntoConstraints = false
  7757. self.containerPreviewReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  7758. self.containerPreviewReply.topAnchor.constraint(equalTo: self.viewTextfield.topAnchor).isActive = true
  7759. if !self.containerLink.isDescendant(of: self.viewTextfield) {
  7760. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.textFieldSend.topAnchor)
  7761. } else {
  7762. self.bottomAnchorPreviewReply = self.containerPreviewReply.bottomAnchor.constraint(equalTo: self.containerLink.topAnchor)
  7763. }
  7764. self.bottomAnchorPreviewReply.isActive = true
  7765. self.containerPreviewReply.trailingAnchor.constraint(equalTo: self.viewTextfield.trailingAnchor).isActive = true
  7766. self.containerPreviewReply.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .secondaryColor
  7767. let leftReply = UIView()
  7768. self.containerPreviewReply.addSubview(leftReply)
  7769. leftReply.translatesAutoresizingMaskIntoConstraints = false
  7770. leftReply.leadingAnchor.constraint(equalTo: self.viewTextfield.leadingAnchor).isActive = true
  7771. leftReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor).isActive = true
  7772. leftReply.bottomAnchor.constraint(equalTo: self.containerPreviewReply.bottomAnchor).isActive = true
  7773. leftReply.widthAnchor.constraint(equalToConstant: 3).isActive = true
  7774. leftReply.backgroundColor = .orangeColor
  7775. let titleReply = UILabel()
  7776. self.containerPreviewReply.addSubview(titleReply)
  7777. titleReply.translatesAutoresizingMaskIntoConstraints = false
  7778. titleReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  7779. titleReply.topAnchor.constraint(equalTo: self.containerPreviewReply.topAnchor, constant: 10).isActive = true
  7780. titleReply.font = UIFont.systemFont(ofSize: 12 + offset()).bold
  7781. let idMe = User.getMyPin() as String?
  7782. if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
  7783. titleReply.text = "You".localized()
  7784. } else {
  7785. if self.isContactCenter {
  7786. let user: [User] = self.users.filter({$0.pin == dataMessages[indexPath.row]["f_pin"] as? String})
  7787. titleReply.text = user.first!.fullName
  7788. } else {
  7789. titleReply.text = self.dataPerson["name"]!!
  7790. }
  7791. }
  7792. titleReply.textColor = .orangeColor
  7793. let contentReply = UILabel()
  7794. self.containerPreviewReply.addSubview(contentReply)
  7795. contentReply.translatesAutoresizingMaskIntoConstraints = false
  7796. contentReply.leadingAnchor.constraint(equalTo: leftReply.leadingAnchor, constant: 10).isActive = true
  7797. contentReply.topAnchor.constraint(equalTo: titleReply.bottomAnchor).isActive = true
  7798. contentReply.font = UIFont.systemFont(ofSize: 10 + offset())
  7799. let message_text = dataMessages[indexPath.row]["message_text"] as? String ?? ""
  7800. let attachment_flag = dataMessages[indexPath.row]["attachment_flag"] as? String ?? ""
  7801. let thumb_chat = dataMessages[indexPath.row]["thumb_id"] as? String ?? ""
  7802. let image_chat = dataMessages[indexPath.row]["image_id"] as? String ?? ""
  7803. let video_chat = dataMessages[indexPath.row]["video_id"] as? String ?? ""
  7804. let file_chat = dataMessages[indexPath.row]["file_id"] as? String ?? ""
  7805. if (attachment_flag == "0" && thumb_chat == "") {
  7806. contentReply.attributedText = message_text.richText()
  7807. } else if (attachment_flag == "1" || image_chat != "") {
  7808. if (message_text == "") {
  7809. contentReply.text = "📷 Photo".localized()
  7810. } else {
  7811. contentReply.attributedText = message_text.richText()
  7812. }
  7813. } else if (attachment_flag == "2" || video_chat != "") {
  7814. if (message_text == "") {
  7815. contentReply.text = "📹 Video".localized()
  7816. } else {
  7817. contentReply.attributedText = message_text.richText()
  7818. }
  7819. } else if (attachment_flag == "6" || file_chat != ""){
  7820. contentReply.text = "📄 \(message_text.components(separatedBy: "|")[0])"
  7821. } else if (attachment_flag == "11") {
  7822. contentReply.text = "❤️ Sticker"
  7823. }
  7824. contentReply.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .gray
  7825. let buttonCancelReply = UIButton(type: .custom)
  7826. self.containerPreviewReply.addSubview(buttonCancelReply)
  7827. buttonCancelReply.translatesAutoresizingMaskIntoConstraints = false
  7828. buttonCancelReply.trailingAnchor.constraint(equalTo: self.containerPreviewReply.trailingAnchor, constant: -10).isActive = true
  7829. buttonCancelReply.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  7830. buttonCancelReply.setImage(UIImage(systemName: "xmark.circle" , withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .regular, scale: .default)), for: .normal)
  7831. buttonCancelReply.addTarget(nil, action: #selector(self.deleteReplyView), for: .touchUpInside)
  7832. buttonCancelReply.backgroundColor = .clear
  7833. buttonCancelReply.tintColor = .mainColor
  7834. if (attachment_flag == "1" || attachment_flag == "2" || image_chat != "" || video_chat != "") {
  7835. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  7836. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  7837. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  7838. if let dirPath = paths.first {
  7839. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb_chat)
  7840. let image : UIImage? = {
  7841. if let img = Nexilis.imageCache.object(forKey: thumb_chat as NSString) {
  7842. return img
  7843. }
  7844. else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) {
  7845. Nexilis.imageCache.setObject(img, forKey: thumb_chat as NSString)
  7846. return img
  7847. }
  7848. return nil
  7849. }()
  7850. // let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
  7851. let imageThumb = UIImageView(image: image)
  7852. self.containerPreviewReply.addSubview(imageThumb)
  7853. imageThumb.layer.cornerRadius = 2.0
  7854. imageThumb.clipsToBounds = true
  7855. imageThumb.translatesAutoresizingMaskIntoConstraints = false
  7856. imageThumb.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  7857. imageThumb.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  7858. imageThumb.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7859. imageThumb.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7860. if (attachment_flag == "2") {
  7861. let imagePlay = UIImageView(image: UIImage(systemName: "play.circle.fill"))
  7862. imageThumb.addSubview(imagePlay)
  7863. imagePlay.clipsToBounds = true
  7864. imagePlay.translatesAutoresizingMaskIntoConstraints = false
  7865. imagePlay.centerYAnchor.constraint(equalTo: imageThumb.centerYAnchor).isActive = true
  7866. imagePlay.centerXAnchor.constraint(equalTo: imageThumb.centerXAnchor).isActive = true
  7867. imagePlay.widthAnchor.constraint(equalToConstant: 10).isActive = true
  7868. imagePlay.heightAnchor.constraint(equalToConstant: 10).isActive = true
  7869. imagePlay.tintColor = .white
  7870. }
  7871. }
  7872. }
  7873. if (attachment_flag == "11") {
  7874. let imageSticker = UIImageView(image: UIImage(named: (message_text.components(separatedBy: "/")[1]), in: Bundle.resourceBundle(for: Nexilis.self), with: nil))
  7875. self.containerPreviewReply.addSubview(imageSticker)
  7876. imageSticker.layer.cornerRadius = 2.0
  7877. imageSticker.clipsToBounds = true
  7878. imageSticker.translatesAutoresizingMaskIntoConstraints = false
  7879. imageSticker.trailingAnchor.constraint(equalTo: buttonCancelReply.leadingAnchor, constant: -10).isActive = true
  7880. imageSticker.centerYAnchor.constraint(equalTo: self.containerPreviewReply.centerYAnchor).isActive = true
  7881. imageSticker.widthAnchor.constraint(equalToConstant: 30).isActive = true
  7882. imageSticker.heightAnchor.constraint(equalToConstant: 30).isActive = true
  7883. }
  7884. }
  7885. func scrollToFirstSearchMessage(indexScroll: Int = 1) {
  7886. if textSearch.count < 2 {
  7887. return
  7888. }
  7889. var lastIndex = 0
  7890. let messageTextForSearch: [[String: Any?]] = self.dataMessages.reversed()
  7891. for idx in 0..<messageTextForSearch.count {
  7892. if (messageTextForSearch[idx]["message_text"] as? String ?? "").lowercased().contains(textSearch) {
  7893. lastIndex += 1
  7894. if lastIndex < indexScroll {
  7895. continue
  7896. }
  7897. lastScrollIdxSearch = lastIndex
  7898. let section = self.dataDates.firstIndex(of: messageTextForSearch[idx]["chat_date"] as? String ?? "")
  7899. if section == nil {
  7900. return
  7901. }
  7902. 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})
  7903. if row == nil {
  7904. return
  7905. }
  7906. let indexPath = IndexPath(row: row!, section: section!)
  7907. self.tableChatView.scrollToRow(at: indexPath, at: .middle, animated: true)
  7908. DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
  7909. if let cell = self.tableChatView.cellForRow(at: indexPath) {
  7910. let containerMessage = cell.contentView.subviews[0]
  7911. let idMe = User.getMyPin() as String?
  7912. if (messageTextForSearch[idx]["f_pin"] as? String == idMe) {
  7913. containerMessage.backgroundColor = .blueBubbleColor.withAlphaComponent(0.3)
  7914. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7915. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  7916. containerMessage.backgroundColor = .clear
  7917. } else {
  7918. containerMessage.backgroundColor = .blueBubbleColor
  7919. }
  7920. }
  7921. } else {
  7922. containerMessage.backgroundColor = .whiteBubbleColor.withAlphaComponent(0.3)
  7923. DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  7924. if (messageTextForSearch[idx]["attachment_flag"] as? String == "11") {
  7925. containerMessage.backgroundColor = .clear
  7926. } else {
  7927. containerMessage.backgroundColor = .whiteBubbleColor
  7928. }
  7929. }
  7930. }
  7931. }
  7932. }
  7933. titleSearchMatches.isHidden = false
  7934. if countMatchesSearch != 0 {
  7935. if countMatchesSearch > 1 {
  7936. titleSearchMatches.text = "\(lastScrollIdxSearch) " + "of".localized() + " \(countMatchesSearch) " + "matches".localized()
  7937. } else {
  7938. titleSearchMatches.text = "\(countMatchesSearch) " + "matches".localized()
  7939. }
  7940. } else {
  7941. titleSearchMatches.text = "Not found".localized()
  7942. }
  7943. if lastScrollIdxSearch == countMatchesSearch || countMatchesSearch == 0 {
  7944. buttonUp.isEnabled = false
  7945. buttonUp.tintColor = .gray
  7946. } else {
  7947. buttonUp.isEnabled = true
  7948. buttonUp.tintColor = .mainColor
  7949. }
  7950. if countMatchesSearch == 0 || lastScrollIdxSearch == 1 || countMatchesSearch == 1 {
  7951. buttonDown.isEnabled = false
  7952. buttonDown.tintColor = .gray
  7953. } else {
  7954. buttonDown.isEnabled = true
  7955. buttonDown.tintColor = .mainColor
  7956. }
  7957. break
  7958. }
  7959. }
  7960. }
  7961. public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
  7962. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  7963. if indexPath != nil {
  7964. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  7965. let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  7966. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && indexPath!.row > 0 {
  7967. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  7968. let headerView = listViewOnSection[sect]
  7969. headerView.isHidden = true
  7970. }
  7971. }
  7972. }
  7973. public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  7974. if !decelerate {
  7975. let indexPath = tableChatView.indexPathsForVisibleRows?.first
  7976. if indexPath != nil {
  7977. let headerRect = tableChatView.rectForHeader(inSection: indexPath!.section)
  7978. let isPinned = headerRect.origin.y <= scrollView.contentOffset.y
  7979. if listViewOnSection.count != 0 && listViewOnSection.count - 1 == indexPath!.section && isPinned {
  7980. let sect = listViewOnSection.count - 1 < currentIndexpath!.section ? listViewOnSection.count - 1 : currentIndexpath!.section
  7981. let headerView = listViewOnSection[sect]
  7982. headerView.isHidden = true
  7983. }
  7984. }
  7985. }
  7986. }
  7987. }
  7988. extension UITableView {
  7989. func scrollToBottom(isAnimated:Bool = true){
  7990. DispatchQueue.main.asyncAfter(deadline: .now() + (isAnimated ? 0 : 0.6)) { [weak self] in
  7991. guard let self = self, self.numberOfSections > 0 else { return }
  7992. let lastSection = self.numberOfSections - 1
  7993. let numberOfRows = self.numberOfRows(inSection: lastSection)
  7994. guard numberOfRows > 0 else { return }
  7995. let indexPath = IndexPath(row: numberOfRows - 1, section: lastSection)
  7996. self.scrollToRow(at: indexPath, at: .bottom, animated: isAnimated)
  7997. }
  7998. }
  7999. func scrollToTop(isAnimated:Bool = true) {
  8000. DispatchQueue.main.async {
  8001. let indexPath = IndexPath(row: 0, section: 0)
  8002. if indexPath.row != -1 {
  8003. self.scrollToRow(at: indexPath, at: .top, animated: isAnimated)
  8004. }
  8005. }
  8006. }
  8007. }
  8008. extension UIImage {
  8009. public func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
  8010. UIGraphicsBeginImageContextWithOptions(
  8011. CGSize(width: self.size.width + insets.left + insets.right,
  8012. height: self.size.height + insets.top + insets.bottom), false, self.scale)
  8013. let _ = UIGraphicsGetCurrentContext()
  8014. let origin = CGPoint(x: insets.left, y: insets.top)
  8015. self.draw(at: origin)
  8016. let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
  8017. UIGraphicsEndImageContext()
  8018. return imageWithInsets
  8019. }
  8020. }
  8021. extension EditorPersonal: UISearchBarDelegate {
  8022. public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
  8023. timerSearch?.invalidate()
  8024. if searchText.count > 1 {
  8025. timerSearch = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: {[self] _ in
  8026. textSearch = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
  8027. titleSearchMatches.isHidden = true
  8028. countMatchesSearch = Chat.getCountSearchMessage(key: textSearch, pin: unique_l_pin, isPersonal: true)
  8029. tableChatView.reloadData()
  8030. scrollToFirstSearchMessage()
  8031. })
  8032. }
  8033. }
  8034. }
  8035. public class ObjectGesture: UITapGestureRecognizer {
  8036. public var message_id = ""
  8037. public var image_id = ""
  8038. public var video_id = ""
  8039. public var file_id = ""
  8040. public var audio_id = ""
  8041. public var gif_id = ""
  8042. public var imageView = UIImageView()
  8043. public var containerFile = UIView()
  8044. public var labelFile = UILabel()
  8045. public var videoURL: NSURL?
  8046. public var indexPath = IndexPath()
  8047. public var indexImageTapped: Int!
  8048. public var listImageFromGrouping: [ImageGrouping]!
  8049. public var isInitiator: Bool!
  8050. }
  8051. class navigationQLPreviewDocument: UIBarButtonItem {
  8052. var navigation = UINavigationController()
  8053. }
  8054. class segmentedControllerObject: UISegmentedControl {
  8055. var navigation = UINavigationController()
  8056. }
  8057. public class ImageGrouping {
  8058. public var messageId = ""
  8059. public var thumbId = ""
  8060. public var imageId = ""
  8061. public var status = ""
  8062. public var time = ""
  8063. public var lPin = ""
  8064. public var dataMessage: [String: Any?] = [:]
  8065. public var dataPerson: [String: String?] = [:]
  8066. public var dataGroup: [String: Any?] = [:]
  8067. public var dataTopic: [String: Any?] = [:]
  8068. public var isSelected = false
  8069. 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?]) {
  8070. self.messageId = messageId
  8071. self.thumbId = thumbId
  8072. self.imageId = imageId
  8073. self.status = status
  8074. self.time = time
  8075. self.lPin = lPin
  8076. self.dataMessage = dataMessage
  8077. self.dataPerson = dataPerson
  8078. self.dataGroup = dataGroup
  8079. self.dataTopic = dataTopic
  8080. }
  8081. }
  8082. public class TypeDataMessage {
  8083. public static let message_id = "message_id"
  8084. public static let f_pin = "f_pin"
  8085. public static let l_pin = "l_pin"
  8086. public static let message_scope_id = "message_scope_id"
  8087. public static let server_date = "server_date"
  8088. public static let status = "status"
  8089. public static let message_text = "message_text"
  8090. public static let audio_id = "audio_id"
  8091. public static let video_id = "video_id"
  8092. public static let image_id = "image_id"
  8093. public static let thumb_id = "thumb_id"
  8094. public static let read_receipts = "read_receipts"
  8095. public static let chat_id = "chat_id"
  8096. public static let file_id = "file_id"
  8097. public static let attachment_flag = "attachment_flag"
  8098. public static let reff_id = "reff_id"
  8099. public static let lock = "lock"
  8100. public static let is_stared = "is_stared"
  8101. public static let blog_id = "blog_id"
  8102. public static let credential = "credential"
  8103. public static let progress = "progress"
  8104. public static let chat_date = "chat_date"
  8105. public static let is_call_center = "is_call_center"
  8106. public static let call_center_id = "call_center_id"
  8107. public static let opposite_pin = "opposite_pin"
  8108. public static let last_edit = "last_edit"
  8109. public static let gif_id = "gif_id"
  8110. public static let is_forwarded = "is_forwarded"
  8111. public static let is_secret = "is_secret"
  8112. }
  8113. public final class CustomQLPreviewController: QLPreviewController {
  8114. public override func viewDidLoad() {
  8115. super.viewDidLoad()
  8116. navigationItem.rightBarButtonItem = UIBarButtonItem()
  8117. }
  8118. public override func viewDidAppear(_ animated: Bool) {
  8119. super.viewDidAppear(animated)
  8120. (children[0] as? UINavigationController)?.setToolbarHidden(true, animated: false)
  8121. }
  8122. public override func viewDidLayoutSubviews() {
  8123. super.viewDidLayoutSubviews()
  8124. (children[0] as? UINavigationController)?.setToolbarHidden(true, animated: false)
  8125. }
  8126. }