Extension.swift 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209
  1. //
  2. // StringUtil.swift
  3. // Runner
  4. //
  5. // Created by Yayan Dwi on 20/04/20.
  6. // Copyright © 2020 The Chromium Authors. All rights reserved.
  7. //
  8. import Foundation
  9. import UIKit
  10. import SDWebImage
  11. import ImageIO
  12. import MobileCoreServices
  13. import CommonCrypto
  14. import ZIPFoundation
  15. import PDFKit
  16. import ObjectiveC
  17. import CoreLocation
  18. import CoreTelephony
  19. import Network
  20. import SystemConfiguration.CaptiveNetwork
  21. extension Date {
  22. public func currentTimeMillis() -> Int {
  23. return Int(self.timeIntervalSince1970 * 1000)
  24. }
  25. func format(dateFormat: String) -> String {
  26. let formatter = DateFormatter()
  27. formatter.dateFormat = dateFormat
  28. return formatter.string(from: self)
  29. }
  30. var millisecondsSince1970:Int64 {
  31. return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
  32. }
  33. public init(milliseconds:Int64) {
  34. self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
  35. }
  36. }
  37. extension String {
  38. func toNormalString() -> String {
  39. let _source = self.replacingOccurrences(of: "+", with: "%20")
  40. if var result = _source.removingPercentEncoding {
  41. result = result.replacingOccurrences(of: "<NL>", with: "\n")
  42. result = result.replacingOccurrences(of: "<CR>", with: "\r")
  43. return decrypt(source: result)
  44. }
  45. return self
  46. }
  47. func toStupidString() -> String {
  48. if var result = self.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
  49. result = result.replacingOccurrences(of: "\n", with: "<NL>")
  50. result = result.replacingOccurrences(of: "\r", with: "<CR>")
  51. result = result.replacingOccurrences(of: "+", with: "%2B")
  52. return result
  53. }
  54. return self
  55. }
  56. private func decrypt(source : String) -> String {
  57. if let result = source.removingPercentEncoding {
  58. return result
  59. }
  60. return source
  61. }
  62. public func matches(_ regex: String) -> Bool {
  63. return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
  64. }
  65. }
  66. extension Int {
  67. func toHex() -> String {
  68. return String(format: "%02X", self)
  69. }
  70. }
  71. extension UIApplication {
  72. public static var appVersion: String? {
  73. return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
  74. }
  75. var rootViewController: UIViewController? {
  76. return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
  77. }
  78. public var visibleViewController: UIViewController? {
  79. let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
  80. if var topController = keyWindow?.rootViewController {
  81. while let presentedViewController = topController.presentedViewController {
  82. topController = presentedViewController
  83. }
  84. return topController
  85. }
  86. return nil
  87. }
  88. }
  89. extension UIView {
  90. public func anchor(top: NSLayoutYAxisAnchor? = nil,
  91. left: NSLayoutXAxisAnchor? = nil,
  92. bottom: NSLayoutYAxisAnchor? = nil,
  93. right: NSLayoutXAxisAnchor? = nil,
  94. paddingTop: CGFloat = 0,
  95. paddingLeft: CGFloat = 0,
  96. paddingBottom: CGFloat = 0,
  97. paddingRight: CGFloat = 0,
  98. centerX: NSLayoutXAxisAnchor? = nil,
  99. centerY: NSLayoutYAxisAnchor? = nil,
  100. width: CGFloat = 0,
  101. height: CGFloat = 0,
  102. minHeight: CGFloat = 0,
  103. maxHeight: CGFloat = 0,
  104. minWidth: CGFloat = 0,
  105. maxWidth: CGFloat = 0,
  106. dynamicLeft: Bool = false,
  107. dynamicRight: Bool = false) {
  108. translatesAutoresizingMaskIntoConstraints = false
  109. if let top = top {
  110. topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
  111. }
  112. if let left = left {
  113. if dynamicLeft {
  114. leftAnchor.constraint(greaterThanOrEqualTo: left, constant: paddingLeft).isActive = true
  115. } else {
  116. leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
  117. }
  118. }
  119. if let right = right {
  120. if dynamicRight {
  121. leftAnchor.constraint(lessThanOrEqualTo: right, constant: -paddingRight).isActive = true
  122. } else {
  123. rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
  124. }
  125. }
  126. if let bottom = bottom {
  127. bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
  128. }
  129. if let centerX = centerX {
  130. centerXAnchor.constraint(equalTo: centerX).isActive = true
  131. }
  132. if let centerY = centerY {
  133. centerYAnchor.constraint(equalTo: centerY).isActive = true
  134. }
  135. if height != 0 || minHeight != 0 || maxHeight != 0 {
  136. if minHeight != 0 && maxHeight != 0 {
  137. heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
  138. heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
  139. } else if minHeight != 0 && maxHeight == 0 {
  140. heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight).isActive = true
  141. } else if minHeight == 0 && maxHeight != 0 {
  142. heightAnchor.constraint(lessThanOrEqualToConstant: maxHeight).isActive = true
  143. } else {
  144. heightAnchor.constraint(equalToConstant: height).isActive = true
  145. }
  146. }
  147. if width != 0 || minWidth != 0 || maxWidth != 0 {
  148. if minWidth != 0 && maxWidth != 0 {
  149. widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
  150. widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
  151. } else if minWidth != 0 && maxWidth == 0 {
  152. widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth).isActive = true
  153. } else if minWidth == 0 && maxWidth != 0 {
  154. widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true
  155. } else {
  156. widthAnchor.constraint(equalToConstant: width).isActive = true
  157. }
  158. }
  159. }
  160. public func addTopBorder(with color: UIColor?, andWidth borderWidth: CGFloat, view: UIView = UIView()) {
  161. let border = view
  162. border.backgroundColor = color
  163. border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
  164. border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: borderWidth)
  165. addSubview(border)
  166. }
  167. public func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat, x: CGFloat = 0, view: UIView = UIView()) {
  168. let border = view
  169. border.backgroundColor = color
  170. border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
  171. border.frame = CGRect(x: x, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
  172. addSubview(border)
  173. }
  174. public func addLeftBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
  175. let border = UIView()
  176. border.backgroundColor = color
  177. border.frame = CGRect(x: 0, y: 0, width: borderWidth, height: frame.size.height)
  178. border.autoresizingMask = [.flexibleHeight, .flexibleRightMargin]
  179. addSubview(border)
  180. }
  181. public func addRightBorder(with color: UIColor?, andWidth borderWidth: CGFloat) {
  182. let border = UIView()
  183. border.backgroundColor = color
  184. border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
  185. border.frame = CGRect(x: frame.size.width - borderWidth, y: 0, width: borderWidth, height: frame.size.height)
  186. addSubview(border)
  187. }
  188. public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
  189. UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
  190. image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
  191. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
  192. UIGraphicsEndImageContext()
  193. return newImage
  194. }
  195. }
  196. extension UIViewController {
  197. var previousViewController: UIViewController? {
  198. guard let navigationController = navigationController else { return nil }
  199. let count = navigationController.viewControllers.count
  200. return count < 2 ? nil : navigationController.viewControllers[count - 2]
  201. }
  202. }
  203. extension UIImage {
  204. class func gifImageWithData(_ data: Data) -> UIImage? {
  205. guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
  206. return nil
  207. }
  208. let frameCount = CGImageSourceGetCount(source)
  209. var images: [UIImage] = []
  210. for i in 0..<frameCount {
  211. if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
  212. let image = UIImage(cgImage: cgImage)
  213. images.append(image)
  214. }
  215. }
  216. return UIImage.animatedImage(with: images, duration: 0.0)
  217. }
  218. func gifData() -> Data? {
  219. guard let cgImages = self.images?.compactMap({ $0.cgImage }) else {
  220. return nil
  221. }
  222. let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: 0.1]] // Adjust delay time if necessary
  223. let destinationData = NSMutableData()
  224. guard let destination = CGImageDestinationCreateWithData(destinationData as CFMutableData, kUTTypeGIF, cgImages.count, nil) else {
  225. return nil
  226. }
  227. let framePropertiesPointer = UnsafeMutablePointer<NSDictionary>.allocate(capacity: cgImages.count)
  228. defer {
  229. framePropertiesPointer.deallocate()
  230. }
  231. for i in 0..<cgImages.count {
  232. framePropertiesPointer[i] = frameProperties as NSDictionary
  233. }
  234. CGImageDestinationSetProperties(destination, [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] as CFDictionary)
  235. for image in cgImages {
  236. CGImageDestinationAddImage(destination, image, framePropertiesPointer.pointee)
  237. }
  238. guard CGImageDestinationFinalize(destination) else {
  239. return nil
  240. }
  241. return destinationData as Data
  242. }
  243. public func resize(target: CGSize) -> UIImage {
  244. // Determine the scale factor that preserves aspect ratio
  245. let widthRatio = target.width / size.width
  246. let heightRatio = target.height / size.height
  247. let scaleFactor = min(widthRatio, heightRatio)
  248. // Compute the new image size that preserves aspect ratio
  249. let scaledImageSize = CGSize(
  250. width: size.width * scaleFactor,
  251. height: size.height * scaleFactor
  252. )
  253. // Draw and return the resized UIImage
  254. let renderer = UIGraphicsImageRenderer(
  255. size: scaledImageSize
  256. )
  257. let scaledImage = renderer.image { _ in
  258. self.draw(in: CGRect(
  259. origin: .zero,
  260. size: scaledImageSize
  261. ))
  262. }
  263. return scaledImage
  264. }
  265. static func imageWithColor(color: UIColor, size: CGSize) -> UIImage? {
  266. let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
  267. UIGraphicsBeginImageContextWithOptions(size, false, 0)
  268. color.setFill()
  269. UIRectFill(rect)
  270. guard let image: UIImage = UIGraphicsGetImageFromCurrentImageContext() else {
  271. return nil
  272. }
  273. UIGraphicsEndImageContext()
  274. return image
  275. }
  276. func mimeType() -> String? {
  277. guard let imageData = self.pngData() else {
  278. return nil
  279. }
  280. let byteArray = [UInt8](imageData)
  281. if byteArray.starts(with: [0xFF, 0xD8, 0xFF]) {
  282. return "image/jpeg"
  283. } else if byteArray.starts(with: [0x89, 0x50, 0x4E, 0x47]) {
  284. return "image/png"
  285. } else if byteArray.starts(with: [0x47, 0x49, 0x46, 0x38]) {
  286. return "image/gif"
  287. } else if byteArray.starts(with: [0x49, 0x49, 0x2A, 0x00]) || byteArray.starts(with: [0x4D, 0x4D, 0x00, 0x2A]) {
  288. return "image/tiff"
  289. }
  290. return nil
  291. }
  292. convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
  293. UIGraphicsBeginImageContext(size)
  294. guard let context = UIGraphicsGetCurrentContext() else { return nil }
  295. context.setFillColor(color.cgColor)
  296. context.fill(CGRect(origin: .zero, size: size))
  297. let image = UIGraphicsGetImageFromCurrentImageContext()
  298. UIGraphicsEndImageContext()
  299. guard let cgImage = image?.cgImage else { return nil }
  300. self.init(cgImage: cgImage)
  301. }
  302. }
  303. extension Data {
  304. func mimeType() -> String? {
  305. let byteArray = [UInt8](self)
  306. if byteArray.starts(with: [0xFF, 0xD8, 0xFF]) {
  307. return "image/jpeg"
  308. } else if byteArray.starts(with: [0x89, 0x50, 0x4E, 0x47]) {
  309. return "image/png"
  310. } else if byteArray.starts(with: [0x47, 0x49, 0x46, 0x38]) {
  311. return "image/gif"
  312. } else if byteArray.starts(with: [0x49, 0x49, 0x2A, 0x00]) || byteArray.starts(with: [0x4D, 0x4D, 0x00, 0x2A]) {
  313. return "image/tiff"
  314. } else if byteArray.starts(with: [0x25, 0x50, 0x44, 0x46]) {
  315. return "application/pdf"
  316. } else if byteArray.starts(with: [0x50, 0x4B, 0x03, 0x04]) {
  317. return "application/zip"
  318. }
  319. return nil
  320. }
  321. }
  322. extension UIImage {
  323. var isPortrait: Bool { size.height > size.width }
  324. var isLandscape: Bool { size.width > size.height }
  325. var breadth: CGFloat { min(size.width, size.height) }
  326. var breadthSize: CGSize { .init(width: breadth, height: breadth) }
  327. var breadthRect: CGRect { .init(origin: .zero, size: breadthSize) }
  328. public var circleMasked: UIImage? {
  329. guard let cgImage = cgImage?
  330. .cropping(to: .init(origin: .init(x: isLandscape ? ((size.width-size.height)/2).rounded(.down) : 0,
  331. y: isPortrait ? ((size.height-size.width)/2).rounded(.down) : 0),
  332. size: breadthSize)) else { return nil }
  333. let format = imageRendererFormat
  334. format.opaque = false
  335. return UIGraphicsImageRenderer(size: breadthSize, format: format).image { _ in
  336. UIBezierPath(ovalIn: breadthRect).addClip()
  337. UIImage(cgImage: cgImage, scale: format.scale, orientation: imageOrientation)
  338. .draw(in: .init(origin: .zero, size: breadthSize))
  339. }
  340. }
  341. public func createCustomIconWithText(text: String, color: UIColor = .black) -> UIImage {
  342. let size = CGSize(width: 60, height: 60)
  343. UIGraphicsBeginImageContextWithOptions(size, false, 0)
  344. defer { UIGraphicsEndImageContext() }
  345. let iconRect = CGRect(x: 15, y: 5, width: 30, height: 30)
  346. self.withRenderingMode(.alwaysTemplate).withTintColor(color).draw(in: iconRect)
  347. let paragraphStyle = NSMutableParagraphStyle()
  348. paragraphStyle.alignment = .center
  349. let attributes: [NSAttributedString.Key: Any] = [
  350. .font: UIFont.systemFont(ofSize: 14),
  351. .paragraphStyle: paragraphStyle,
  352. .foregroundColor: color
  353. ]
  354. let textRect = CGRect(x: 0, y: 38, width: 60, height: 20)
  355. text.draw(in: textRect, withAttributes: attributes)
  356. return UIGraphicsGetImageFromCurrentImageContext() ?? self
  357. }
  358. public func rotateImage(byDegrees degrees: CGFloat) -> UIImage {
  359. let radians = degrees * CGFloat.pi / 180
  360. let newSize = CGRect(origin: .zero, size: CGSize(width: 15, height: 15))
  361. .applying(CGAffineTransform(rotationAngle: radians))
  362. .integral.size
  363. UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
  364. guard let context = UIGraphicsGetCurrentContext() else { return self }
  365. // Move origin to center
  366. context.translateBy(x: newSize.width / 2, y: newSize.height / 2)
  367. // Rotate context
  368. context.rotate(by: radians)
  369. // Draw the image at the center
  370. self.draw(in: CGRect(x: -self.size.width / 2,
  371. y: -self.size.height / 2,
  372. width: self.size.width,
  373. height: self.size.height))
  374. let rotatedImage = UIGraphicsGetImageFromCurrentImageContext()
  375. UIGraphicsEndImageContext()
  376. return rotatedImage ?? self
  377. }
  378. }
  379. extension NSObject {
  380. private static var urlStore = [String:String]()
  381. public func getImage(name url: String, placeholderImage: UIImage? = nil, isCircle: Bool = false, tableView: UITableView? = nil, indexPath: IndexPath? = nil, completion: @escaping (Bool, Bool, UIImage?)->()) {
  382. let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
  383. type(of: self).urlStore[tmpAddress] = url
  384. if url.isEmpty {
  385. completion(false, false, placeholderImage)
  386. return
  387. }
  388. do {
  389. let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
  390. let file = documentDir.appendingPathComponent(url)
  391. if FileManager().fileExists(atPath: file.path) {
  392. let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  393. completion(true, false, isCircle ? image?.circleMasked : image)
  394. } else if var tempData = try FileEncryption.shared.readSecure(filename: url) {
  395. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: tempData)
  396. if dataDecrypt != nil {
  397. tempData = dataDecrypt!
  398. }
  399. let image = UIImage(data: tempData)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  400. // FileEncryption.shared.wipeData(&tempData)
  401. completion(true, true, isCircle ? image?.circleMasked : image)
  402. } else {
  403. // completion(false, false, placeholderImage)
  404. Download().startHTTP(forKey: url) { (name, progress) in
  405. guard progress == 100 else {
  406. return
  407. }
  408. DispatchQueue.main.async {
  409. if type(of: self).urlStore[tmpAddress] == name && tableView == nil {
  410. if FileManager().fileExists(atPath: file.path) {
  411. let image = UIImage(contentsOfFile: file.path)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  412. completion(true, true, isCircle ? image?.circleMasked : image)
  413. } else if FileEncryption.shared.isSecureExists(filename: url) {
  414. do {
  415. if var imageData = try FileEncryption.shared.readSecure(filename: url) {
  416. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData)
  417. if dataDecrypt != nil {
  418. imageData = dataDecrypt!
  419. }
  420. let image = UIImage(data: imageData)?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  421. completion(true, true, isCircle ? image?.circleMasked : image)
  422. }
  423. } catch {
  424. }
  425. }
  426. }
  427. }
  428. }
  429. }
  430. } catch {
  431. completion(false, false, placeholderImage)
  432. Download().startHTTP(forKey: url) { (name, progress) in
  433. guard progress == 100 else {
  434. return
  435. }
  436. DispatchQueue.main.async {
  437. guard let tableView = tableView else { return }
  438. tableView.reloadData()
  439. }
  440. }
  441. }
  442. }
  443. func loadImage(named: String, placeholderImage: UIImage?, completion: @escaping (UIImage?, Bool) -> ()) {
  444. guard !named.isEmpty else {
  445. completion(placeholderImage, true)
  446. return
  447. }
  448. SDWebImageManager.shared.loadImage(with: URL.palioImage(named: named), options: .highPriority, progress: .none) { image, data, error, type, finish, url in
  449. completion(image, finish)
  450. }
  451. }
  452. public func deleteAllRecordDatabase() {
  453. Database.shared.database?.inTransaction({ fmdb, rollback in
  454. do {
  455. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "BUDDY", _where: "")
  456. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
  457. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "")
  458. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
  459. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
  460. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "POST", _where: "")
  461. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_STATUS", _where: "")
  462. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "")
  463. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "OUTGOING", _where: "")
  464. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FOLLOW", _where: "")
  465. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_FAVORITE", _where: "")
  466. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "LINK_PREVIEW", _where: "")
  467. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PULL_DB", _where: "")
  468. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PREFS", _where: "")
  469. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", _where: "")
  470. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FORM_DATA", _where: "")
  471. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_PIC", _where: "")
  472. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_DETAIL", _where: "")
  473. } catch {
  474. rollback.pointee = true
  475. print("Access database error: \(error.localizedDescription)")
  476. }
  477. })
  478. Utils.setFinishInitPrefs(value: false)
  479. }
  480. }
  481. extension URL {
  482. static func palioImage(named: String) -> URL? {
  483. return URL(string: Utils.getURLBase() + "filepalio/image/\(named)")
  484. }
  485. struct FileTypeSignature {
  486. let magic: [String]
  487. let extensions: String
  488. }
  489. func detectFileType(from data: Data) -> FileTypeSignature? {
  490. let hexString = data.prefix(4).map { String(format: "%02X", $0) }.joined()
  491. let extUploadedFile = self.absoluteString.split(separator: ".").last ?? ""
  492. let dataPrefs = Utils.getWhitelistFileExt()
  493. // print("HOHOHO: \(extUploadedFile) <><>> \(hexString) <><><> \(dataPrefs)")
  494. guard !dataPrefs.isEmpty,
  495. let jsonData = dataPrefs.data(using: .utf8) else {
  496. return nil
  497. }
  498. do {
  499. if let jsonArray = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [[String: Any]] {
  500. guard let _ = jsonArray.firstIndex(where: { $0["ext"] as? String ?? "" == extUploadedFile.lowercased() }) else {
  501. return nil
  502. }
  503. if let idxRealFile = jsonArray.firstIndex(where: { $0["ext"] as? String == extUploadedFile.lowercased()} ) {
  504. let jsonRealFile = jsonArray[idxRealFile]
  505. let magic = jsonRealFile["magic"] as! [String]
  506. if magic.contains(hexString) || magic.contains(where: { $0.components(separatedBy: "~").contains(hexString) }) {
  507. return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
  508. } else if isMP4File(data) {
  509. return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
  510. }
  511. }
  512. }
  513. } catch {
  514. print("Error parsing JSON: \(error)")
  515. }
  516. return nil
  517. }
  518. func isMP4File(_ data: Data) -> Bool {
  519. guard data.count >= 12 else { return false }
  520. // Read bytes 4-7 (ftyp)
  521. let ftyp = String(data: data.subdata(in: 4..<8), encoding: .ascii)
  522. return ftyp == "ftyp"
  523. }
  524. func isEncryptedPDF(data: Data) -> Bool {
  525. guard let document = PDFDocument(data: data) else {
  526. return false
  527. }
  528. if document.isEncrypted {
  529. if document.isLocked {
  530. return true
  531. } else {
  532. return false
  533. }
  534. }
  535. return false
  536. }
  537. func isEncryptedOfficeFile(data: Data) -> Bool {
  538. if let archive = Archive(data: data, accessMode: .read) {
  539. if archive.contains(where: { $0.path.contains("EncryptedPackage") }) {
  540. return true
  541. }
  542. }
  543. // Check if it's OLE format (common for password-protected files)
  544. let oleMagic = data.prefix(4).map { String(format: "%02X", $0) }.joined()
  545. return oleMagic == "D0CF11E0"
  546. }
  547. func doesFileMatchExtension() -> (result: Int, realFileType: FileTypeSignature?) {
  548. guard let fileExtension = self.pathExtension.lowercased().split(separator: "?").first,
  549. let fileData = try? Data(contentsOf: self) else {
  550. return (-1, nil)
  551. }
  552. if let detected = detectFileType(from: fileData) {
  553. if detected.extensions == String(fileExtension) {
  554. return (1, detected)
  555. } else {
  556. return (-1, detected)
  557. }
  558. }
  559. return (0, nil)
  560. }
  561. func validateFile() -> Int {
  562. guard let fileData = try? Data(contentsOf: self) else {
  563. return -1
  564. }
  565. let matches = self.doesFileMatchExtension()
  566. func isDoc(type: String) -> Bool {
  567. return type == "ppt" || type == "doc" || type == "xls"
  568. }
  569. if (matches.result == -1 && matches.realFileType != nil) {
  570. if isDoc(type: matches.realFileType!.extensions) && isEncryptedOfficeFile(data: fileData) {
  571. return 1
  572. } else if matches.realFileType!.extensions == "pdf" && isEncryptedPDF(data: fileData) {
  573. return 1
  574. }
  575. }
  576. if matches.result != 1 {
  577. DispatchQueue.global().async {
  578. DataCaptured.sendErrorDLP(fileName: self.lastPathComponent, code: matches.result == -1 ? 22 : 21)
  579. }
  580. }
  581. return matches.result
  582. }
  583. }
  584. extension UIColor {
  585. public static var mainColor: UIColor {
  586. if Utils.getIsWATheme() {
  587. return whatsappGreenColor
  588. }
  589. return renderColor(hex: "#046cfc")
  590. }
  591. public static var borderTabColor: UIColor {
  592. return renderColor(hex: "#c4e4f4")
  593. }
  594. public static var nxColor: UIColor {
  595. return renderColor(hex: "#04ecfc")
  596. }
  597. public static var secondaryColor: UIColor {
  598. return renderColor(hex: "#FAFAFF")
  599. }
  600. public static var blackDarkMode: UIColor {
  601. return renderColor(hex: "#262626")
  602. }
  603. public static var orangeColor: UIColor {
  604. return renderColor(hex: "#FFA03E")
  605. }
  606. public static var orangeBNI: UIColor {
  607. return renderColor(hex: "#EE6600")
  608. }
  609. public static var greenColor: UIColor {
  610. return renderColor(hex: "#C7EA46")
  611. }
  612. public static var whiteBubbleColor: UIColor {
  613. if UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark {
  614. return blackDarkMode
  615. }
  616. return renderColor(hex: "#F5F5F5")
  617. }
  618. public static var grayTitleColor: UIColor {
  619. return renderColor(hex: "#5A5A5A")
  620. }
  621. public static var docColor: UIColor {
  622. return renderColor(hex: "#798F9A")
  623. }
  624. public static var mentionColor: UIColor {
  625. return UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? renderColor(hex: "#f6fcae") : renderColor(hex: "#FFA500")
  626. }
  627. public static var blueBubbleColor: UIColor {
  628. if UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark {
  629. return renderColor(hex: "#367dd9")
  630. }
  631. return renderColor(hex: "#C5D1E1")
  632. }
  633. public static var officialColor: UIColor {
  634. return renderColor(hex: "#4c87ef")
  635. }
  636. public static var verifiedColor: UIColor {
  637. return renderColor(hex: "#00b333")
  638. }
  639. public static var ccColor: UIColor {
  640. return renderColor(hex: "#FFF1A353")
  641. }
  642. public static var internalColor: UIColor {
  643. return renderColor(hex: "#ff0000")
  644. }
  645. public static var blueTextField: UIColor {
  646. return renderColor(hex: "#4c92d2")
  647. }
  648. public static var whatsappGreenColor: UIColor {
  649. return renderColor(hex: "#20A961")
  650. }
  651. public static var whatsappGreenTitleColor: UIColor {
  652. return renderColor(hex: "#295C3B")
  653. }
  654. public static var whatsappGreenLightColor: UIColor {
  655. return renderColor(hex: "#E5FCE4")
  656. }
  657. public static var whatsappGrayPPColor: UIColor {
  658. return renderColor(hex: "#a1a5b1")
  659. }
  660. public static var waGrayLight: UIColor {
  661. return renderColor(hex: "#eceaeb")
  662. }
  663. public static var waGrayFont: UIColor {
  664. return renderColor(hex: "#b7b5b6")
  665. }
  666. public class func renderColor(hex: String) -> UIColor {
  667. var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
  668. if (cString.hasPrefix("#")) {
  669. cString.remove(at: cString.startIndex)
  670. }
  671. if ((cString.count) != 6) {
  672. return UIColor.gray
  673. }
  674. var rgbValue:UInt64 = 0
  675. Scanner(string: cString).scanHexInt64(&rgbValue)
  676. return UIColor(
  677. red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
  678. green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
  679. blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
  680. alpha: CGFloat(1.0)
  681. )
  682. }
  683. }
  684. extension UIView {
  685. public func circle() {
  686. layer.cornerRadius = 0.5 * bounds.size.width
  687. clipsToBounds = true
  688. }
  689. public func maxCornerRadius() -> CGFloat {
  690. return (self.frame.width > self.frame.height) ? self.frame.height / 2 : self.frame.width / 2
  691. }
  692. public func currentFirstResponder() -> UIView? {
  693. if self.isFirstResponder {
  694. return self
  695. }
  696. for subview in self.subviews {
  697. if let responder = subview.currentFirstResponder() {
  698. return responder
  699. }
  700. }
  701. return nil
  702. }
  703. }
  704. extension String {
  705. public func localized(uppercased: Bool = false) -> String {
  706. if let _ : String = SecureUserDefaults.shared.value(forKey: "i18n_language") {} else {
  707. // we set a default, just in case
  708. let langDefault = UserDefaults.standard.stringArray(forKey: "AppleLanguages")
  709. if langDefault![0].contains("id") {
  710. SecureUserDefaults.shared.set("id", forKey: "i18n_language")
  711. } else {
  712. SecureUserDefaults.shared.set("en", forKey: "i18n_language")
  713. }
  714. }
  715. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? ""
  716. let bundle = Bundle.resourceBundle(for: Nexilis.self).path(forResource: lang, ofType: "lproj")
  717. let bundlePath = Bundle(path: bundle!)
  718. let result = NSLocalizedString(
  719. self,
  720. tableName: "Localizable",
  721. bundle: bundlePath!,
  722. value: self,
  723. comment: self)
  724. if uppercased {
  725. return result.uppercased()
  726. }
  727. return result
  728. }
  729. }
  730. extension UIViewController {
  731. public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
  732. UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
  733. image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
  734. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
  735. UIGraphicsEndImageContext()
  736. return newImage
  737. }
  738. }
  739. extension UITextView {
  740. enum ShouldChangeCursor {
  741. case incrementCursor
  742. case preserveCursor
  743. }
  744. func preserveCursorPosition(withChanges mutatingFunction: (UITextPosition?) -> (ShouldChangeCursor)) {
  745. //save the cursor positon
  746. var cursorPosition: UITextPosition? = nil
  747. if let selectedRange = self.selectedTextRange {
  748. let offset = self.offset(from: self.beginningOfDocument, to: selectedRange.start)
  749. cursorPosition = self.position(from: self.beginningOfDocument, offset: offset)
  750. }
  751. //make mutaing changes that may reset the cursor position
  752. let shouldChangeCursor = mutatingFunction(cursorPosition)
  753. //restore the cursor
  754. if var cursorPosition = cursorPosition {
  755. if shouldChangeCursor == .incrementCursor {
  756. cursorPosition = self.position(from: cursorPosition, offset: 1) ?? cursorPosition
  757. }
  758. if let range = self.textRange(from: cursorPosition, to: cursorPosition) {
  759. self.selectedTextRange = range
  760. }
  761. }
  762. }
  763. func rangesOfMentionText(withColor color: UIColor) -> [NSRange] {
  764. var ranges: [NSRange] = []
  765. guard let attributedText = self.attributedText else { return ranges }
  766. attributedText.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in
  767. if let foundColor = value as? UIColor, foundColor == color {
  768. ranges.append(range)
  769. }
  770. }
  771. return ranges
  772. }
  773. }
  774. extension String {
  775. public func substring(from: Int?, to: Int?) -> String {
  776. if let start = from {
  777. guard start < self.count else {
  778. return ""
  779. }
  780. }
  781. if let end = to {
  782. guard end >= 0 else {
  783. return ""
  784. }
  785. }
  786. if let start = from, let end = to {
  787. guard end - start >= 0 else {
  788. return ""
  789. }
  790. }
  791. let startIndex: String.Index
  792. if let start = from, start >= 0 {
  793. startIndex = self.index(self.startIndex, offsetBy: start)
  794. } else {
  795. startIndex = self.startIndex
  796. }
  797. let endIndex: String.Index
  798. if let end = to, end >= 0, end < self.count {
  799. endIndex = self.index(self.startIndex, offsetBy: end + 1)
  800. } else {
  801. endIndex = self.endIndex
  802. }
  803. return String(self[startIndex ..< endIndex])
  804. }
  805. static public func offset() -> CGFloat{
  806. guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
  807. return CGFloat(fontSize)
  808. }
  809. public func richText(
  810. fontSize: CGFloat = 12 + String.offset(),
  811. isEditing: Bool = false,
  812. isSearching: Bool = false,
  813. textSearch: String = "",
  814. group_id: String = "",
  815. listMentionInTextField: [User] = []
  816. ) -> NSMutableAttributedString {
  817. let font = UIFont.systemFont(ofSize: fontSize)
  818. let boldFont = UIFont.boldSystemFont(ofSize: fontSize)
  819. let italicFont = UIFont.italicSystemFont(ofSize: fontSize)
  820. let boldItalicFont = UIFont.systemFont(ofSize: fontSize, weight: .semibold)
  821. let textUTF8 = self
  822. let finalText = NSMutableAttributedString(string: textUTF8, attributes: [.font: font])
  823. let formattingRules: [(String, [NSAttributedString.Key: Any])] = [
  824. ("_", [.font: italicFont]), // Italic
  825. ("*", [.font: boldFont]), // Bold
  826. ("~", [.strikethroughStyle: NSUnderlineStyle.single.rawValue]),
  827. ("^", [.underlineStyle: NSUnderlineStyle.single.rawValue]),
  828. ("$", [.font: italicFont, .foregroundColor: UIColor.darkGray]) // Italic + Gray for $
  829. ]
  830. for (sign, attributes) in formattingRules {
  831. applyTextFormatting(to: finalText, sign: sign, attributes: attributes, isEditing: isEditing)
  832. }
  833. processMentions(in: finalText, groupID: group_id, isEditing: isEditing, listMentionInTextField: listMentionInTextField)
  834. if isSearching {
  835. highlightSearchText(in: finalText, searchText: textSearch)
  836. }
  837. return finalText
  838. }
  839. // MARK: - Helper Functions
  840. private func applyTextFormatting(
  841. to text: NSMutableAttributedString,
  842. sign: String,
  843. attributes: [NSAttributedString.Key: Any],
  844. isEditing: Bool
  845. ) {
  846. let escapedSign = NSRegularExpression.escapedPattern(for: sign)
  847. let pattern = "\(escapedSign)(.+?)\(escapedSign)" // Ensure sign is correctly escaped in regex
  848. guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return }
  849. let matches = regex.matches(in: text.string, options: [], range: NSRange(location: 0, length: text.length))
  850. for match in matches.reversed() { // Iterate in reverse to prevent index shifting
  851. let fullRange = match.range
  852. let textRange = match.range(at: 1)
  853. // Apply the desired formatting
  854. for (key, value) in attributes {
  855. text.addAttribute(key, value: value, range: textRange)
  856. }
  857. if !isEditing {
  858. // Remove formatting characters (signs)
  859. text.replaceCharacters(in: NSRange(location: fullRange.upperBound - sign.count, length: sign.count), with: "")
  860. text.replaceCharacters(in: NSRange(location: fullRange.lowerBound, length: sign.count), with: "")
  861. } else {
  862. // Change color of formatting characters (grayed out)
  863. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.lowerBound, length: sign.count))
  864. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.upperBound - sign.count, length: sign.count))
  865. }
  866. }
  867. }
  868. private func processMentions(in text: NSMutableAttributedString, groupID: String, isEditing: Bool, listMentionInTextField: [User] = []) {
  869. let regex = try? NSRegularExpression(pattern: "@(\\w+)", options: [])
  870. let matches = regex?.matches(in: text.string, options: [], range: NSRange(location: 0, length: text.length)) ?? []
  871. for match in matches.reversed() {
  872. let range = match.range(at: 1)
  873. let username = (text.string as NSString).substring(with: range)
  874. if isEditing && listMentionInTextField.count > 0 {
  875. for mention in listMentionInTextField {
  876. let upper = (Int(mention.ex_block ?? "0") ?? 0)
  877. let lower = upper - mention.fullName.count
  878. if lower >= 0 {
  879. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lower, length: 1))
  880. text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: lower + 1, length: mention.fullName.count))
  881. text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: lower, length: mention.fullName.count + 1))
  882. }
  883. }
  884. } else {
  885. if let member = Member.getMember(f_pin: username) {
  886. let fullName = "\(member.fullName)".trimmingCharacters(in: .whitespaces)
  887. text.replaceCharacters(in: range, with: fullName)
  888. if !groupID.isEmpty, Member.getMemberInGroup(f_pin: username, group_id: groupID) != nil {
  889. let newRange = (text.string as NSString).range(of: "@\(fullName)")
  890. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: newRange.lowerBound, length: 1))
  891. text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: newRange.lowerBound + 1, length: fullName.count))
  892. text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: newRange)
  893. }
  894. }
  895. }
  896. }
  897. }
  898. private func highlightSearchText(in text: NSMutableAttributedString, searchText: String) {
  899. let range = (text.string as NSString).range(of: searchText, options: .caseInsensitive)
  900. if range.location != NSNotFound {
  901. text.addAttribute(.backgroundColor, value: UIColor.systemYellow, range: range)
  902. }
  903. }
  904. func distance(of index: String.Index) -> Int? {
  905. return utf16.distance(from: startIndex, to: index)
  906. }
  907. }
  908. extension UIFont {
  909. var bold: UIFont {
  910. return with(traits: .traitBold)
  911. } // bold
  912. var italic: UIFont {
  913. return with(traits: .traitItalic)
  914. } // italic
  915. func with(traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
  916. guard let descriptor = self.fontDescriptor.withSymbolicTraits(traits) else {
  917. return self
  918. } // guard
  919. return UIFont(descriptor: descriptor, size: 0)
  920. } // with(traits:)
  921. }
  922. extension UILabel {
  923. public func set(image: UIImage, with text: String, size: CGFloat, y: CGFloat) {
  924. let attachment = NSTextAttachment()
  925. attachment.image = image
  926. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  927. let attachmentStr = NSAttributedString(attachment: attachment)
  928. let mutableAttributedString = NSMutableAttributedString()
  929. mutableAttributedString.append(attachmentStr)
  930. let textString = NSAttributedString(string: text, attributes: [.font: self.font!])
  931. mutableAttributedString.append(textString)
  932. self.attributedText = mutableAttributedString
  933. }
  934. public func setAttributeText(image: UIImage, with textMutable: NSAttributedString, size: CGFloat, y: CGFloat) {
  935. let attachment = NSTextAttachment()
  936. attachment.image = image
  937. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  938. let attachmentStr = NSAttributedString(attachment: attachment)
  939. let mutableAttributedString = NSMutableAttributedString()
  940. mutableAttributedString.append(attachmentStr)
  941. mutableAttributedString.append(textMutable)
  942. self.attributedText = mutableAttributedString
  943. }
  944. }
  945. extension Bundle {
  946. public static func resourceBundle(for frameworkClass: AnyClass) -> Bundle {
  947. let frameworkBundle = Bundle(for: frameworkClass)
  948. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLite", withExtension: "bundle"),
  949. let resourceBundle = Bundle(url: resourceBundleURL) else {
  950. return frameworkBundle
  951. }
  952. return resourceBundle
  953. }
  954. public static func resourcesMediaBundle(for frameworkClass: AnyClass) -> Bundle {
  955. let frameworkBundle = Bundle(for: frameworkClass)
  956. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLiteResources", withExtension: "bundle"),
  957. let resourceBundle = Bundle(url: resourceBundleURL) else {
  958. return frameworkBundle
  959. }
  960. return resourceBundle
  961. }
  962. }
  963. //extension UIFont {
  964. //
  965. // static func register(from url: URL) throws {
  966. // guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
  967. // throw fatalError("Could not create font data provider for \(url).")
  968. // }
  969. // let font = CGFont(fontDataProvider)
  970. // var error: Unmanaged<CFError>?
  971. // guard CTFontManagerRegisterGraphicsFont(font!, &error) else {
  972. // throw error!.takeUnretainedValue()
  973. // }
  974. // }
  975. //
  976. //}
  977. extension UIButton {
  978. private func actionHandleBlock(action:(() -> Void)? = nil) {
  979. struct __ {
  980. static var action :(() -> Void)?
  981. }
  982. if action != nil {
  983. __.action = action
  984. } else {
  985. __.action?()
  986. }
  987. }
  988. @objc private func triggerActionHandleBlock() {
  989. self.actionHandleBlock()
  990. }
  991. public func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  992. self.actionHandleBlock(action: action)
  993. self.addTarget(self, action: #selector(self.triggerActionHandleBlock), for: control)
  994. }
  995. public func setImageRightOfText(image: UIImage?, for state: UIControl.State) {
  996. self.setImage(image, for: state)
  997. self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -((image?.size.width ?? 0) + 5), bottom: 0, right: (image?.size.width ?? 0))
  998. self.imageEdgeInsets = UIEdgeInsets(top: 10, left: (self.titleLabel?.frame.size.width ?? 0) + 90, bottom: 10, right: -((self.titleLabel?.frame.size.width ?? 0) + 5))
  999. }
  1000. }
  1001. extension UINavigationController {
  1002. func replaceAllViewController(with viewController: UIViewController, animated: Bool) {
  1003. pushViewController(viewController, animated: animated)
  1004. viewControllers.removeSubrange(1...viewControllers.count - 2)
  1005. }
  1006. var rootViewController : UIViewController? {
  1007. return viewControllers.first
  1008. }
  1009. func popViewController(animated: Bool, completion: @escaping () -> Void) {
  1010. popViewController(animated: animated)
  1011. if animated, let coordinator = transitionCoordinator {
  1012. coordinator.animate(alongsideTransition: nil) { _ in
  1013. completion()
  1014. }
  1015. } else {
  1016. completion()
  1017. }
  1018. }
  1019. }
  1020. extension UIImageView {
  1021. private static var taskKey = 0
  1022. private static var urlKey = 0
  1023. private var currentTask: URLSessionTask? {
  1024. get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask }
  1025. set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  1026. }
  1027. private var currentURL: URL? {
  1028. get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL }
  1029. set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  1030. }
  1031. public func loadImageAsync(with urlString: String?, isGif: Bool = false) {
  1032. // cancel prior task, if any
  1033. weak var oldTask = currentTask
  1034. currentTask = nil
  1035. oldTask?.cancel()
  1036. // reset imageview's image
  1037. self.image = nil
  1038. // allow supplying of `nil` to remove old image and then return immediately
  1039. guard let urlString = urlString else { return }
  1040. // check cache
  1041. if isGif, let cachedImageGif = ImageCache.shared.imageGif(forKey: urlString) {
  1042. guard let gifImage = UIImage.gifImageWithData(Data(referencing: cachedImageGif)) else {
  1043. print("Failed to create the GIF image.")
  1044. return
  1045. }
  1046. self.image = gifImage
  1047. return
  1048. }
  1049. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  1050. self.image = cachedImage
  1051. return
  1052. }
  1053. // download
  1054. let url = URL(string: urlString)!
  1055. currentURL = url
  1056. let urlConfig = URLSessionConfiguration.default
  1057. let sessionDelegate = SelfSignedURLSessionDelegate()
  1058. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  1059. let task = session.dataTask(with: url) { [weak self] data, response, error in
  1060. self?.currentTask = nil
  1061. //error handling
  1062. if let error = error {
  1063. // don't bother reporting cancelation errors
  1064. if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
  1065. return
  1066. }
  1067. //print(error)
  1068. return
  1069. }
  1070. guard let data = data else {
  1071. //print("unable to extract image")
  1072. return
  1073. }
  1074. let downloadedImage = UIImage(data: data)
  1075. if isGif {
  1076. ImageCache.shared.saveGif(data: NSData(data: data), forKey: urlString)
  1077. } else {
  1078. if downloadedImage != nil {
  1079. ImageCache.shared.save(image: downloadedImage!, forKey: urlString)
  1080. }
  1081. }
  1082. if url == self?.currentURL {
  1083. DispatchQueue.main.async {
  1084. if isGif {
  1085. guard let gifImage = UIImage.gifImageWithData(data) else {
  1086. print("Failed to create the GIF image.")
  1087. return
  1088. }
  1089. self?.image = gifImage
  1090. } else {
  1091. self?.image = downloadedImage
  1092. }
  1093. }
  1094. }
  1095. }
  1096. // save and start new task
  1097. currentTask = task
  1098. task.resume()
  1099. }
  1100. private func actionHandleBlock(action:(() -> Void)? = nil) {
  1101. struct __ {
  1102. static var action :(() -> Void)?
  1103. }
  1104. if action != nil {
  1105. __.action = action
  1106. } else {
  1107. __.action?()
  1108. }
  1109. }
  1110. @objc private func triggerActionHandleBlock() {
  1111. self.actionHandleBlock()
  1112. }
  1113. func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  1114. self.actionHandleBlock(action: action)
  1115. self.isUserInteractionEnabled = true
  1116. self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.triggerActionHandleBlock)))
  1117. }
  1118. }
  1119. extension UITextField {
  1120. public enum PaddingSide {
  1121. case left(CGFloat)
  1122. case right(CGFloat)
  1123. case both(CGFloat)
  1124. }
  1125. public func addPadding(_ padding: PaddingSide) {
  1126. self.leftViewMode = .always
  1127. self.layer.masksToBounds = true
  1128. switch padding {
  1129. case .left(let spacing):
  1130. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1131. self.leftView = paddingView
  1132. self.rightViewMode = .always
  1133. case .right(let spacing):
  1134. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1135. self.rightView = paddingView
  1136. self.rightViewMode = .always
  1137. case .both(let spacing):
  1138. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1139. // left
  1140. self.leftView = paddingView
  1141. self.leftViewMode = .always
  1142. // right
  1143. self.rightView = paddingView
  1144. self.rightViewMode = .always
  1145. }
  1146. }
  1147. }
  1148. public class ImageCache {
  1149. public static let shared = ImageCache()
  1150. private let cache = NSCache<NSString, UIImage>()
  1151. private let cacheGif = NSCache<NSString, NSData>()
  1152. private var cacheKeyMap: [String: String] = [:]
  1153. private let imageCacheDirectory: URL
  1154. private let gifCacheDirectory: URL
  1155. private init() {
  1156. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  1157. imageCacheDirectory = documentsDirectory.appendingPathComponent("fileCacheImage")
  1158. gifCacheDirectory = documentsDirectory.appendingPathComponent("fileCacheGif")
  1159. loadCacheFromDisk(directory: imageCacheDirectory, isGif: false)
  1160. loadCacheFromDisk(directory: gifCacheDirectory, isGif: true)
  1161. }
  1162. public func save(image: UIImage, forKey key: String) {
  1163. let sanitizedKey = sanitizeKey(key)
  1164. cache.setObject(image, forKey: sanitizedKey as NSString)
  1165. cacheKeyMap[key] = sanitizedKey
  1166. saveCacheToDisk(directory: imageCacheDirectory, isGif: false)
  1167. }
  1168. public func saveGif(data: NSData, forKey key: String) {
  1169. let sanitizedKey = sanitizeKey(key)
  1170. cacheGif.setObject(data, forKey: sanitizedKey as NSString)
  1171. cacheKeyMap[key] = sanitizedKey
  1172. saveCacheToDisk(directory: gifCacheDirectory, isGif: true)
  1173. }
  1174. public func image(forKey key: String) -> UIImage? {
  1175. let sanitizedKey = sanitizeKey(key)
  1176. if let image = cache.object(forKey: sanitizedKey as NSString) {
  1177. return image
  1178. }
  1179. // Try loading from disk if not in memory
  1180. let imageName = "\(sanitizedKey).png"
  1181. if FileEncryption.shared.isSecureExists(filename: imageName) {
  1182. do {
  1183. if var data = try FileEncryption.shared.readSecure(filename: imageName) {
  1184. if let decrypted = FileEncryption.shared.decryptFileFromServer(data: data) {
  1185. data = decrypted
  1186. }
  1187. if let image = UIImage(data: data) {
  1188. cache.setObject(image, forKey: sanitizedKey as NSString)
  1189. return image
  1190. }
  1191. }
  1192. } catch {
  1193. print("Failed to read or decrypt image from disk: \(error)")
  1194. }
  1195. }
  1196. return nil
  1197. }
  1198. public func imageGif(forKey key: String) -> NSData? {
  1199. let sanitizedKey = sanitizeKey(key)
  1200. return cacheGif.object(forKey: sanitizedKey as NSString)
  1201. }
  1202. private func saveCacheToDisk(directory: URL, isGif: Bool) {
  1203. let fileManager = FileManager.default
  1204. if !fileManager.fileExists(atPath: directory.path) {
  1205. do {
  1206. try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
  1207. } catch {
  1208. print("Failed to create cache directory: \(error)")
  1209. return
  1210. }
  1211. }
  1212. let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
  1213. do {
  1214. let jsonData = try JSONSerialization.data(withJSONObject: cacheKeyMap, options: [])
  1215. try jsonData.write(to: mappingFilePath)
  1216. } catch {
  1217. print("Failed to write mapping file: \(error)")
  1218. return
  1219. }
  1220. for (_, sanitizedKey) in cacheKeyMap {
  1221. if isGif {
  1222. if let gifData = cacheGif.object(forKey: sanitizedKey as NSString) {
  1223. try? FileEncryption.shared.writeSecure(filename: "\(sanitizedKey).gif", data: gifData as Data)
  1224. }
  1225. } else {
  1226. if let image = cache.object(forKey: sanitizedKey as NSString),
  1227. let imageData = image.pngData() {
  1228. try? FileEncryption.shared.writeSecure(filename: "\(sanitizedKey).png", data: imageData)
  1229. }
  1230. }
  1231. }
  1232. }
  1233. private func loadCacheFromDisk(directory: URL, isGif: Bool) {
  1234. let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
  1235. guard let jsonData = try? Data(contentsOf: mappingFilePath),
  1236. let loadedMapping = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] else {
  1237. return
  1238. }
  1239. if cacheKeyMap.isEmpty {
  1240. cacheKeyMap = loadedMapping
  1241. }
  1242. for (_, sanitizedKey) in loadedMapping {
  1243. let fileExtension = isGif ? "gif" : "png"
  1244. let fileName = "\(sanitizedKey).\(fileExtension)"
  1245. guard FileEncryption.shared.isSecureExists(filename: fileName) else { continue }
  1246. do {
  1247. if var data = try FileEncryption.shared.readSecure(filename: fileName) {
  1248. if let decrypted = FileEncryption.shared.decryptFileFromServer(data: data) {
  1249. data = decrypted
  1250. }
  1251. if isGif {
  1252. cacheGif.setObject(data as NSData, forKey: sanitizedKey as NSString)
  1253. } else if let image = UIImage(data: data) {
  1254. cache.setObject(image, forKey: sanitizedKey as NSString)
  1255. }
  1256. }
  1257. } catch {
  1258. print("Error loading \(fileExtension) from disk: \(error)")
  1259. }
  1260. }
  1261. }
  1262. private func sanitizeKey(_ key: String) -> String {
  1263. let data = Data(key.utf8)
  1264. var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
  1265. data.withUnsafeBytes {
  1266. _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
  1267. }
  1268. return hash.map { String(format: "%02x", $0) }.joined()
  1269. }
  1270. }
  1271. public class LibAlertController: UIAlertController {
  1272. public override func viewWillAppear(_ animated: Bool) {
  1273. super.viewWillAppear(animated)
  1274. // Customize the title's font
  1275. let titleFont = UIFont.boldSystemFont(ofSize: 16)
  1276. let titleAttributes = [NSAttributedString.Key.font: titleFont]
  1277. setValue(NSAttributedString(string: self.title ?? "", attributes: titleAttributes), forKey: "attributedTitle")
  1278. // Change the font for the message
  1279. let messageFont = UIFont.systemFont(ofSize: 14)
  1280. let messageAttributes = [NSAttributedString.Key.font: messageFont]
  1281. setValue(NSAttributedString(string: self.message ?? "", attributes: messageAttributes), forKey: "attributedMessage")
  1282. for i in self.actions {
  1283. let attributedText = NSAttributedString(string: i.title ?? "", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)])
  1284. guard let label = (i.value(forKey: "__representer") as AnyObject).value(forKey: "label") as? UILabel else { return }
  1285. label.attributedText = attributedText
  1286. }
  1287. }
  1288. }
  1289. extension UISearchBar
  1290. {
  1291. public func setMagnifyingGlassColorTo(color: UIColor)
  1292. {
  1293. // Search Icon
  1294. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1295. let glassIconView = textFieldInsideSearchBar?.leftView as? UIImageView
  1296. glassIconView?.image = glassIconView?.image?.withRenderingMode(.alwaysTemplate)
  1297. glassIconView?.tintColor = color
  1298. }
  1299. func setClearButtonColorTo(color: UIColor)
  1300. {
  1301. // Clear Button
  1302. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1303. let crossIconView = textFieldInsideSearchBar?.value(forKey: "clearButton") as? UIButton
  1304. crossIconView?.setImage(crossIconView?.currentImage?.withRenderingMode(.alwaysTemplate), for: .normal)
  1305. crossIconView?.tintColor = color
  1306. }
  1307. func setPlaceholderTextColorTo(color: UIColor)
  1308. {
  1309. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1310. textFieldInsideSearchBar?.textColor = color
  1311. let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
  1312. textFieldInsideSearchBarLabel?.textColor = color
  1313. }
  1314. public func setCustomBackgroundImage(image: UIImage)
  1315. {
  1316. setSearchFieldBackgroundImage(resizeImage(image: image, targetHeight: 30), for: .normal)
  1317. }
  1318. func resizeImage(image: UIImage, targetHeight: CGFloat) -> UIImage? {
  1319. let scaleFactor = targetHeight / image.size.height
  1320. let targetWidth = image.size.width * scaleFactor
  1321. let newSize = CGSize(width: targetWidth, height: targetHeight)
  1322. UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
  1323. defer { UIGraphicsEndImageContext() }
  1324. image.draw(in: CGRect(origin: .zero, size: newSize))
  1325. guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
  1326. return newImage
  1327. }
  1328. public func updateHeight(height: CGFloat, radius: CGFloat = 8.0, borderColor: CGColor = UIColor.white.cgColor) {
  1329. let image: UIImage? = UIImage.imageWithColor(color: UIColor.clear, size: CGSize(width: 1, height: height))
  1330. setSearchFieldBackgroundImage(image, for: .normal)
  1331. for subview in self.subviews {
  1332. for subSubViews in subview.subviews {
  1333. if #available(iOS 13.0, *) {
  1334. for child in subSubViews.subviews {
  1335. if let textField = child as? UISearchTextField {
  1336. textField.layer.cornerRadius = radius
  1337. textField.clipsToBounds = true
  1338. textField.layer.borderColor = borderColor
  1339. textField.layer.borderWidth = 1.0
  1340. }
  1341. }
  1342. continue
  1343. }
  1344. if let textField = subSubViews as? UITextField {
  1345. textField.layer.cornerRadius = radius
  1346. textField.clipsToBounds = true
  1347. textField.layer.borderColor = borderColor
  1348. textField.layer.borderWidth = 1.0
  1349. }
  1350. }
  1351. }
  1352. }
  1353. }
  1354. extension String {
  1355. init(unicodeScalar: UnicodeScalar) {
  1356. self.init(Character(unicodeScalar))
  1357. }
  1358. init?(unicodeCodepoint: Int) {
  1359. if let unicodeScalar = UnicodeScalar(unicodeCodepoint) {
  1360. self.init(unicodeScalar: unicodeScalar)
  1361. } else {
  1362. return nil
  1363. }
  1364. }
  1365. static func +(lhs: String, rhs: Int) -> String {
  1366. return lhs + String(unicodeCodepoint: rhs)!
  1367. }
  1368. static func +=(lhs: inout String, rhs: Int) {
  1369. lhs = lhs + rhs
  1370. }
  1371. }
  1372. extension UIGraphicsRenderer {
  1373. static func renderImagesAt(urls: [NSURL], size: CGSize, scale: CGFloat = 1) -> UIImage {
  1374. let renderer = UIGraphicsImageRenderer(size: size)
  1375. let options: [NSString: Any] = [
  1376. kCGImageSourceThumbnailMaxPixelSize: max(size.width * scale, size.height * scale),
  1377. kCGImageSourceCreateThumbnailFromImageAlways: true
  1378. ]
  1379. let thumbnails = try urls.map { url -> CGImage in
  1380. let imageSource = CGImageSourceCreateWithURL(url, nil)
  1381. let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource!, 0, options as CFDictionary)
  1382. return scaledImage!
  1383. }
  1384. // Translate Y-axis down because cg images are flipped and it falls out of the frame (see bellow)
  1385. let rect = CGRect(x: 0,
  1386. y: -size.height,
  1387. width: size.width,
  1388. height: size.height)
  1389. let resizedImage = renderer.image { ctx in
  1390. let context = ctx.cgContext
  1391. context.scaleBy(x: 1, y: -1) //Flip it ( cg y-axis is flipped)
  1392. for image in thumbnails {
  1393. context.draw(image, in: rect)
  1394. }
  1395. }
  1396. return resizedImage
  1397. }
  1398. static func renderImageAt(url: NSURL, size: CGSize, scale: CGFloat = 1) -> UIImage {
  1399. return renderImagesAt(urls: [url], size: size, scale: scale)
  1400. }
  1401. }
  1402. extension NSAttributedString {
  1403. convenience init(html: String) throws {
  1404. let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
  1405. .documentType: NSAttributedString.DocumentType.html,
  1406. .characterEncoding: String.Encoding.utf8.rawValue
  1407. ]
  1408. guard let data = html.data(using: .utf8) else {
  1409. throw NSError(domain: "Invalid HTML", code: 0, userInfo: nil)
  1410. }
  1411. try self.init(data: data, options: options, documentAttributes: nil)
  1412. }
  1413. }
  1414. extension Collection {
  1415. subscript(safe index: Index) -> Element? {
  1416. return indices.contains(index) ? self[index] : nil
  1417. }
  1418. }
  1419. extension UIViewController {
  1420. static let swizzleViewDidAppearImplementation: Void = {
  1421. let originalSelector = #selector(viewDidAppear(_:))
  1422. let swizzledSelector = #selector(swizzled_viewDidAppear(_:))
  1423. guard
  1424. let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),
  1425. let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector)
  1426. else {
  1427. return
  1428. }
  1429. method_exchangeImplementations(originalMethod, swizzledMethod)
  1430. }()
  1431. @objc func swizzled_viewDidAppear(_ animated: Bool) {
  1432. swizzled_viewDidAppear(animated)
  1433. if self is UINavigationController || self is UITabBarController {
  1434. // If it's a UINavigationController, get its topViewController
  1435. if let nav = self as? UINavigationController,
  1436. let topVC = nav.topViewController {
  1437. DataCaptured.actVC = "\(String(describing: type(of: topVC)))"
  1438. DispatchQueue.global().async {
  1439. DataCaptured.sendLogMonitorActivity()
  1440. }
  1441. }
  1442. return
  1443. }
  1444. if "\(type(of: self))".hasPrefix("UI") {
  1445. return
  1446. }
  1447. // Normal screen
  1448. DataCaptured.actVC = "\(String(describing: type(of: self)))"
  1449. DispatchQueue.global().async {
  1450. DataCaptured.sendLogMonitorActivity()
  1451. }
  1452. }
  1453. }
  1454. extension UINavigationController {
  1455. static let swizzlePushViewControllerImplementation: Void = {
  1456. let originalSelector = #selector(pushViewController(_:animated:))
  1457. let swizzledSelector = #selector(swizzled_pushViewController(_:animated:))
  1458. guard
  1459. let originalMethod = class_getInstanceMethod(UINavigationController.self, originalSelector),
  1460. let swizzledMethod = class_getInstanceMethod(UINavigationController.self, swizzledSelector)
  1461. else {
  1462. return
  1463. }
  1464. method_exchangeImplementations(originalMethod, swizzledMethod)
  1465. }()
  1466. @objc func swizzled_pushViewController(_ viewController: UIViewController, animated: Bool) {
  1467. DataCaptured.actNC = "\(String(describing: type(of: viewController)))"
  1468. DataCaptured.actVC = ""
  1469. DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
  1470. if DataCaptured.actVC.isEmpty {
  1471. DataCaptured.sendLogMonitorActivity()
  1472. }
  1473. })
  1474. swizzled_pushViewController(viewController, animated: animated)
  1475. }
  1476. static let swizzlePopViewControllerImplementation: Void = {
  1477. let originalSelector = #selector(popViewController(animated:))
  1478. let swizzledSelector = #selector(swizzled_popViewController(animated:))
  1479. guard
  1480. let originalMethod = class_getInstanceMethod(UINavigationController.self, originalSelector),
  1481. let swizzledMethod = class_getInstanceMethod(UINavigationController.self, swizzledSelector)
  1482. else {
  1483. return
  1484. }
  1485. method_exchangeImplementations(originalMethod, swizzledMethod)
  1486. }()
  1487. @objc func swizzled_popViewController(animated: Bool) -> UIViewController? {
  1488. let poppedVC = swizzled_popViewController(animated: animated)
  1489. if let topVC = self.topViewController {
  1490. DataCaptured.actNC = "\(String(describing: type(of: topVC)))"
  1491. DataCaptured.actVC = ""
  1492. DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
  1493. if DataCaptured.actVC.isEmpty {
  1494. DataCaptured.sendLogMonitorActivity()
  1495. }
  1496. })
  1497. }
  1498. return poppedVC
  1499. }
  1500. }
  1501. extension UIApplication {
  1502. static let swizzleSendAction: Void = {
  1503. let original = #selector(sendAction(_:to:from:for:))
  1504. let swizzled = #selector(swizzled_sendAction(_:to:from:for:))
  1505. guard let originalMethod = class_getInstanceMethod(UIApplication.self, original),
  1506. let swizzledMethod = class_getInstanceMethod(UIApplication.self, swizzled) else { return }
  1507. method_exchangeImplementations(originalMethod, swizzledMethod)
  1508. }()
  1509. @objc func swizzled_sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool {
  1510. if let button = sender as? UIButton {
  1511. let title = button.titleLabel?.text ?? "Unnamed Button"
  1512. DataCaptured.action = "CLICKED"
  1513. DataCaptured.textAction = "\(title)"
  1514. DispatchQueue.global().async {
  1515. DataCaptured.sendLogMonitorAction()
  1516. }
  1517. }
  1518. return swizzled_sendAction(action, to: target, from: sender, for: event)
  1519. }
  1520. static func enableSwizzling() {
  1521. _ = swizzleSendAction
  1522. }
  1523. }
  1524. class DataCaptured: NSObject {
  1525. static var actVC = ""
  1526. static var actNC = ""
  1527. static var action = ""
  1528. static var textAction = ""
  1529. override init() {
  1530. NotificationCenter.default.addObserver(
  1531. forName: UITextField.textDidChangeNotification,
  1532. object: nil,
  1533. queue: .main
  1534. ) { notification in
  1535. if let textField = notification.object as? UITextField {
  1536. DataCaptured.action = "TEXT_CHANGED"
  1537. DataCaptured.textAction = textField.text ?? ""
  1538. DispatchQueue.global().async {
  1539. DataCaptured.sendLogMonitorAction()
  1540. }
  1541. }
  1542. }
  1543. NotificationCenter.default.addObserver(
  1544. forName: UITextView.textDidChangeNotification,
  1545. object: nil,
  1546. queue: .main
  1547. ) { notification in
  1548. if let textView = notification.object as? UITextView {
  1549. DataCaptured.action = "TEXT_CHANGED"
  1550. DataCaptured.textAction = textView.text ?? ""
  1551. DispatchQueue.global().async {
  1552. DataCaptured.sendLogMonitorAction()
  1553. }
  1554. }
  1555. }
  1556. }
  1557. static func sendLogMonitorAction() {
  1558. var type = "1"
  1559. var value = "1"
  1560. if action == "TEXT_CHANGED" {
  1561. type = "2"
  1562. value = textAction
  1563. }
  1564. print("sendLogMonitorAction: \(type) <><> \(textAction) <><> \(value)")
  1565. // _ = Nexilis.writeSync(message: CoreMessage_TMessageBank.getLogMonitor(type: type, label: textAction, value: value))
  1566. }
  1567. static func sendLogMonitorActivity() {
  1568. var act = actNC
  1569. if !actVC.isEmpty {
  1570. act = actVC
  1571. }
  1572. print("sendLogMonitorActivity: \(act)")
  1573. // _ = Nexilis.write(message: CoreMessage_TMessageBank.getLogActivity(pActivityClassName: act))
  1574. }
  1575. static func sendErrorDLP(fileName: String, code: Int) {
  1576. var data = collectDeviceAttributes()
  1577. data["security_shield"] = "\(code)"
  1578. data["filename"] = fileName
  1579. if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
  1580. let jsonString = String(data: jsonData, encoding: .utf8) {
  1581. print("sendErrorDLP: \(jsonString)")
  1582. // _ = Nexilis.write(message: CoreMessage_TMessageBank.getCaptureDLP(data: jsonString))
  1583. }
  1584. }
  1585. private var currentLocation: CLLocation?
  1586. private static func collectDeviceAttributes() -> [String: Any] {
  1587. var params: [String: Any] = [:]
  1588. // User and session
  1589. let me: String? = User.getMyPin() ?? ""
  1590. let sesId: String? = Utils.getConnectionID()
  1591. params["f_pin"] = me
  1592. params["session_id"] = sesId
  1593. // App info (replace with your preferences retrieval)
  1594. params["api"] = Nexilis.sAPIKey
  1595. params["app_id"] = APIS.getAppNm()
  1596. params["lib_version"] = Nexilis.cpaasVersion
  1597. params["app_version"] = Nexilis.cpaasVersion
  1598. // Network Info
  1599. let (netType, netTypeName) = getNetworkType()
  1600. let (operatorCode, operatorName) = getCarrierInfo()
  1601. let (wifiStatus, wifiIp, wifiSsid, wifiBssid) = getWifiInfo()
  1602. params["network_type"] = netType
  1603. params["network_type_name"] = netTypeName
  1604. params["network_operator"] = operatorCode
  1605. params["network_operator_name"] = operatorName
  1606. params["wifi_ssid"] = wifiSsid
  1607. params["wifi_bssid"] = wifiBssid
  1608. params["wifi_adapter"] = wifiStatus
  1609. params["wifi_ip"] = wifiIp
  1610. // IP Address
  1611. params["ip_addressv4"] = getIPAddress(useIPv4: true)
  1612. params["ip_address"] = getIPAddress(useIPv4: false)
  1613. // GPS / location
  1614. let semaphore = DispatchSemaphore(value: 0)
  1615. DispatchQueue.main.async {
  1616. LocationFetcher.shared.getCurrentLocation { coordinate in
  1617. var long = "0"
  1618. var lat = "0"
  1619. if let coord = coordinate {
  1620. long = "\(coord.longitude)"
  1621. lat = "\(coord.latitude)"
  1622. }
  1623. // print("Latitude: \(lat), Longitude: \(long)")
  1624. params["latitude"] = lat
  1625. params["longitude"] = long
  1626. semaphore.signal()
  1627. }
  1628. }
  1629. _ = semaphore.wait(timeout: .now() + 10.0)
  1630. // iOS doesn't have an Android ID; use identifierForVendor
  1631. params["ios_identifier"] = UIDevice.current.identifierForVendor?.uuidString ?? ""
  1632. // Device attributes
  1633. let device = UIDevice.current
  1634. params["device_NAME"] = device.name
  1635. params["device_MODEL"] = device.model
  1636. params["device_SYSTEM_NAME"] = device.systemName
  1637. params["device_SYSTEM_VERSION"] = device.systemVersion
  1638. params["device_IDENTIFIER_FOR_VENDOR"] = device.identifierForVendor?.uuidString ?? ""
  1639. return getSimData(params: params)
  1640. }
  1641. private static func getSimData(params: [String: Any] = [:]) -> [String: Any] {
  1642. var params = params
  1643. var simArray: [[String: Any]] = []
  1644. let networkInfo = CTTelephonyNetworkInfo()
  1645. if #available(iOS 12.0, *) {
  1646. if let carriers = networkInfo.serviceSubscriberCellularProviders {
  1647. for (key, carrier) in carriers {
  1648. var simInfo: [String: Any] = [:]
  1649. simInfo["carrier_name"] = carrier.carrierName ?? ""
  1650. simInfo["mcc"] = carrier.mobileCountryCode ?? ""
  1651. simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
  1652. simInfo["sim_slot"] = key // This is not a true "slot", but the key used internally
  1653. simArray.append(simInfo)
  1654. }
  1655. }
  1656. } else {
  1657. if let carrier = networkInfo.subscriberCellularProvider {
  1658. var simInfo: [String: Any] = [:]
  1659. simInfo["carrier_name"] = carrier.carrierName ?? ""
  1660. simInfo["mcc"] = carrier.mobileCountryCode ?? ""
  1661. simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
  1662. simInfo["sim_slot"] = "default"
  1663. simArray.append(simInfo)
  1664. }
  1665. }
  1666. params["sim_data"] = simArray
  1667. return params
  1668. }
  1669. private static func getNetworkType() -> (type: String, name: String) {
  1670. let monitor = NWPathMonitor()
  1671. var networkType = ""
  1672. var networkTypeName = ""
  1673. let semaphore = DispatchSemaphore(value: 0)
  1674. monitor.pathUpdateHandler = { path in
  1675. if path.usesInterfaceType(.wifi) {
  1676. networkType = "1" // Corresponds to TYPE_WIFI in Android
  1677. networkTypeName = "WIFI"
  1678. } else if path.usesInterfaceType(.cellular) {
  1679. networkType = "0" // Corresponds to TYPE_MOBILE
  1680. networkTypeName = "MOBILE"
  1681. } else {
  1682. networkType = "-1"
  1683. networkTypeName = "UNKNOWN"
  1684. }
  1685. semaphore.signal()
  1686. monitor.cancel()
  1687. }
  1688. let queue = DispatchQueue(label: "NetworkMonitor")
  1689. monitor.start(queue: queue)
  1690. semaphore.wait()
  1691. return (networkType, networkTypeName)
  1692. }
  1693. private static func getCarrierInfo() -> (operatorCode: String, operatorName: String) {
  1694. let networkInfo = CTTelephonyNetworkInfo()
  1695. var carrierCode = ""
  1696. var carrierName = ""
  1697. if #available(iOS 12.0, *) {
  1698. if let carriers = networkInfo.serviceSubscriberCellularProviders {
  1699. for (_, carrier) in carriers {
  1700. carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
  1701. carrierName = carrier.carrierName ?? ""
  1702. break // Just use the first one
  1703. }
  1704. }
  1705. } else {
  1706. if let carrier = networkInfo.subscriberCellularProvider {
  1707. carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
  1708. carrierName = carrier.carrierName ?? ""
  1709. }
  1710. }
  1711. return (carrierCode, carrierName)
  1712. }
  1713. private static func getWifiInfo() -> (adapter: String, ip: String, ssid: String, bssid: String) {
  1714. var adapterStatus = "Off"
  1715. var ipAddress = ""
  1716. var ssid = ""
  1717. var bssid = ""
  1718. // Get IP Address
  1719. if let interfaces = CNCopySupportedInterfaces() as NSArray? {
  1720. for interfaceName in interfaces {
  1721. if let unsafeInterfaceData = CNCopyCurrentNetworkInfo(interfaceName as! CFString) as NSDictionary? {
  1722. ssid = unsafeInterfaceData["SSID"] as? String ?? ""
  1723. bssid = unsafeInterfaceData["BSSID"] as? String ?? ""
  1724. adapterStatus = "Connected"
  1725. break
  1726. }
  1727. }
  1728. }
  1729. if ssid.isEmpty {
  1730. adapterStatus = "Not Connected"
  1731. }
  1732. ipAddress = getWiFiIPAddress() ?? ""
  1733. return (adapterStatus, ipAddress, ssid, bssid)
  1734. }
  1735. private static func getWiFiIPAddress() -> String? {
  1736. var address: String?
  1737. var ifaddr: UnsafeMutablePointer<ifaddrs>?
  1738. guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { return nil }
  1739. for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
  1740. let interface = ptr.pointee
  1741. let addrFamily = interface.ifa_addr.pointee.sa_family
  1742. if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
  1743. let name = String(cString: interface.ifa_name)
  1744. if name == "en0" { // en0 is Wi-Fi
  1745. var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
  1746. getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
  1747. &hostname, socklen_t(hostname.count),
  1748. nil, socklen_t(0), NI_NUMERICHOST)
  1749. address = String(cString: hostname)
  1750. break
  1751. }
  1752. }
  1753. }
  1754. freeifaddrs(ifaddr)
  1755. return address
  1756. }
  1757. private static func getIPAddress(useIPv4: Bool) -> String {
  1758. var address: String = ""
  1759. var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
  1760. if getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr {
  1761. for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
  1762. let interface = ptr.pointee
  1763. let addrFamily = interface.ifa_addr.pointee.sa_family
  1764. if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
  1765. let name = String(cString: interface.ifa_name)
  1766. if name == "en0" || name == "pdp_ip0" {
  1767. var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
  1768. let result = getnameinfo(
  1769. interface.ifa_addr,
  1770. socklen_t(interface.ifa_addr.pointee.sa_len),
  1771. &hostname,
  1772. socklen_t(hostname.count),
  1773. nil,
  1774. socklen_t(0),
  1775. NI_NUMERICHOST
  1776. )
  1777. if result == 0 {
  1778. let ip = String(cString: hostname)
  1779. let isIPv4 = ip.contains(":") == false
  1780. if useIPv4 && isIPv4 {
  1781. address = ip
  1782. break
  1783. } else if !useIPv4 && !isIPv4 {
  1784. // Remove IPv6 scope if present
  1785. let cleanIPv6 = ip.split(separator: "%").first.map(String.init) ?? ip
  1786. address = cleanIPv6.uppercased()
  1787. break
  1788. }
  1789. }
  1790. }
  1791. }
  1792. }
  1793. freeifaddrs(ifaddr)
  1794. }
  1795. return address
  1796. }
  1797. }
  1798. private class LocationFetcher: NSObject, CLLocationManagerDelegate {
  1799. static var shared = LocationFetcher()
  1800. private var manager: CLLocationManager?
  1801. private var completion: ((CLLocationCoordinate2D?) -> Void)?
  1802. func getCurrentLocation(completion: @escaping (CLLocationCoordinate2D?) -> Void) {
  1803. self.completion = completion
  1804. self.manager = CLLocationManager()
  1805. self.manager?.delegate = self
  1806. self.manager?.desiredAccuracy = kCLLocationAccuracyBest
  1807. self.manager?.requestWhenInUseAuthorization()
  1808. if CLLocationManager.locationServicesEnabled() {
  1809. self.manager?.requestLocation()
  1810. } else {
  1811. completion(nil)
  1812. }
  1813. }
  1814. // MARK: - CLLocationManagerDelegate
  1815. func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  1816. completion?(locations.last?.coordinate)
  1817. cleanup()
  1818. }
  1819. func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
  1820. print("Error: \(error.localizedDescription)")
  1821. completion?(nil)
  1822. cleanup()
  1823. }
  1824. private func cleanup() {
  1825. manager?.stopUpdatingLocation()
  1826. manager?.delegate = nil
  1827. manager = nil
  1828. completion = nil
  1829. }
  1830. }