Extension.swift 87 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343
  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(
  382. name url: String,
  383. placeholderImage: UIImage? = nil,
  384. isCircle: Bool = false,
  385. tableView: UITableView? = nil,
  386. indexPath: IndexPath? = nil,
  387. isResized: Bool = true,
  388. completion: @escaping (Bool, Bool, UIImage?) -> ()
  389. ) {
  390. let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
  391. type(of: self).urlStore[tmpAddress] = url
  392. // Handle empty URL
  393. guard !url.isEmpty else {
  394. completion(false, false, placeholderImage)
  395. return
  396. }
  397. do {
  398. let documentDir = try FileManager.default.url(for: .documentDirectory,
  399. in: .userDomainMask,
  400. appropriateFor: nil,
  401. create: true)
  402. let file = documentDir.appendingPathComponent(url)
  403. if FileManager().fileExists(atPath: file.path) {
  404. var image = UIImage(contentsOfFile: file.path)
  405. if isResized {
  406. image = image?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  407. }
  408. if isCircle {
  409. image = image?.circleMasked
  410. }
  411. completion(true, false, image)
  412. return
  413. }
  414. if var tempData = try? FileEncryption.shared.readSecure(filename: url) {
  415. if let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: tempData) {
  416. tempData = dataDecrypt
  417. }
  418. var image = UIImage(data: tempData)
  419. if isResized {
  420. image = image?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  421. }
  422. if isCircle {
  423. image = image?.circleMasked
  424. }
  425. completion(true, false, image)
  426. return
  427. }
  428. // ❌ 3. Not available locally → fallback to download (async)
  429. completion(false, false, placeholderImage) // give placeholder early
  430. Download().startHTTP(forKey: url) { (name, progress) in
  431. guard progress == 100 else { return }
  432. DispatchQueue.main.async {
  433. if type(of: self).urlStore[tmpAddress] == name && tableView == nil {
  434. if FileManager().fileExists(atPath: file.path) {
  435. var image = UIImage(contentsOfFile: file.path)
  436. if isResized {
  437. image = image?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  438. }
  439. if isCircle {
  440. image = image?.circleMasked
  441. }
  442. completion(true, true, image)
  443. } else if FileEncryption.shared.isSecureExists(filename: url) {
  444. if var imageData = try? FileEncryption.shared.readSecure(filename: url) {
  445. if let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: imageData) {
  446. imageData = dataDecrypt
  447. }
  448. var image = UIImage(data: imageData)
  449. if isResized {
  450. image = image?.sd_resizedImage(with: CGSize(width: 400, height: 400), scaleMode: .aspectFill)
  451. }
  452. if isCircle {
  453. image = image?.circleMasked
  454. }
  455. completion(true, true, image)
  456. }
  457. }
  458. } else if let tableView = tableView {
  459. tableView.reloadData()
  460. }
  461. }
  462. }
  463. } catch {
  464. // In case documentDir fetch fails
  465. completion(false, false, placeholderImage)
  466. Download().startHTTP(forKey: url) { (name, progress) in
  467. guard progress == 100 else { return }
  468. DispatchQueue.main.async {
  469. tableView?.reloadData()
  470. }
  471. }
  472. }
  473. }
  474. func loadImage(named: String, placeholderImage: UIImage?, completion: @escaping (UIImage?, Bool) -> ()) {
  475. guard !named.isEmpty else {
  476. completion(placeholderImage, true)
  477. return
  478. }
  479. SDWebImageManager.shared.loadImage(with: URL.palioImage(named: named), options: .highPriority, progress: .none) { image, data, error, type, finish, url in
  480. completion(image, finish)
  481. }
  482. }
  483. public func deleteAllRecordDatabase() {
  484. Database.shared.database?.inTransaction({ fmdb, rollback in
  485. do {
  486. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "BUDDY", _where: "")
  487. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ", _where: "")
  488. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE", _where: "")
  489. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "GROUPZ_MEMBER", _where: "")
  490. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "DISCUSSION_FORUM", _where: "")
  491. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "POST", _where: "")
  492. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_STATUS", _where: "")
  493. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_SUMMARY", _where: "")
  494. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "OUTGOING", _where: "")
  495. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FOLLOW", _where: "")
  496. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "MESSAGE_FAVORITE", _where: "")
  497. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "LINK_PREVIEW", _where: "")
  498. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PULL_DB", _where: "")
  499. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "PREFS", _where: "")
  500. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", _where: "")
  501. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "FORM_DATA", _where: "")
  502. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_PIC", _where: "")
  503. _ = Database.shared.deleteRecord(fmdb: fmdb, table: "TASK_DETAIL", _where: "")
  504. } catch {
  505. rollback.pointee = true
  506. print("Access database error: \(error.localizedDescription)")
  507. }
  508. })
  509. Utils.setFinishInitPrefs(value: false)
  510. }
  511. }
  512. extension URL {
  513. static func palioImage(named: String) -> URL? {
  514. return URL(string: Utils.getURLBase() + "filepalio/image/\(named)")
  515. }
  516. struct FileTypeSignature {
  517. let magic: [String]
  518. let extensions: String
  519. }
  520. func detectFileType(from data: Data) -> FileTypeSignature? {
  521. let hexString = data.prefix(4).map { String(format: "%02X", $0) }.joined()
  522. let extUploadedFile = self.absoluteString.split(separator: ".").last ?? ""
  523. let dataPrefs = Utils.getWhitelistFileExt()
  524. // print("HOHOHO: \(extUploadedFile) <><>> \(hexString) <><><> \(dataPrefs)")
  525. guard !dataPrefs.isEmpty,
  526. let jsonData = dataPrefs.data(using: .utf8) else {
  527. return nil
  528. }
  529. do {
  530. if let jsonArray = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [[String: Any]] {
  531. guard let _ = jsonArray.firstIndex(where: { $0["ext"] as? String ?? "" == extUploadedFile.lowercased() }) else {
  532. return nil
  533. }
  534. if let idxRealFile = jsonArray.firstIndex(where: { $0["ext"] as? String == extUploadedFile.lowercased()} ) {
  535. let jsonRealFile = jsonArray[idxRealFile]
  536. let magic = jsonRealFile["magic"] as! [String]
  537. if magic.contains(hexString) || magic.contains(where: { $0.components(separatedBy: "~").contains(hexString) }) {
  538. return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
  539. } else if isMP4File(data) {
  540. return FileTypeSignature(magic: jsonRealFile["magic"] as? [String] ?? [], extensions: jsonRealFile["ext"] as? String ?? "")
  541. }
  542. }
  543. }
  544. } catch {
  545. print("Error parsing JSON: \(error)")
  546. }
  547. return nil
  548. }
  549. func isMP4File(_ data: Data) -> Bool {
  550. guard data.count >= 12 else { return false }
  551. // Read bytes 4-7 (ftyp)
  552. let ftyp = String(data: data.subdata(in: 4..<8), encoding: .ascii)
  553. return ftyp == "ftyp"
  554. }
  555. func isEncryptedPDF(data: Data) -> Bool {
  556. guard let document = PDFDocument(data: data) else {
  557. return false
  558. }
  559. if document.isEncrypted {
  560. if document.isLocked {
  561. return true
  562. } else {
  563. return false
  564. }
  565. }
  566. return false
  567. }
  568. func isEncryptedOfficeFile(data: Data) -> Bool {
  569. if let archive = Archive(data: data, accessMode: .read) {
  570. if archive.contains(where: { $0.path.contains("EncryptedPackage") }) {
  571. return true
  572. }
  573. }
  574. // Check if it's OLE format (common for password-protected files)
  575. let oleMagic = data.prefix(4).map { String(format: "%02X", $0) }.joined()
  576. return oleMagic == "D0CF11E0"
  577. }
  578. func doesFileMatchExtension() -> (result: Int, realFileType: FileTypeSignature?) {
  579. guard let fileExtension = self.pathExtension.lowercased().split(separator: "?").first,
  580. let fileData = try? Data(contentsOf: self) else {
  581. return (-1, nil)
  582. }
  583. if let detected = detectFileType(from: fileData) {
  584. if detected.extensions == String(fileExtension) {
  585. return (1, detected)
  586. } else {
  587. return (-1, detected)
  588. }
  589. }
  590. return (0, nil)
  591. }
  592. func validateFile() -> Int {
  593. guard let fileData = try? Data(contentsOf: self) else {
  594. return -1
  595. }
  596. let matches = self.doesFileMatchExtension()
  597. func isDoc(type: String) -> Bool {
  598. return type == "ppt" || type == "doc" || type == "xls"
  599. }
  600. if (matches.result == -1 && matches.realFileType != nil) {
  601. if isDoc(type: matches.realFileType!.extensions) && isEncryptedOfficeFile(data: fileData) {
  602. return 1
  603. } else if matches.realFileType!.extensions == "pdf" && isEncryptedPDF(data: fileData) {
  604. return 1
  605. }
  606. }
  607. if matches.result != 1 {
  608. DispatchQueue.global().async {
  609. DataCaptured.sendErrorDLP(fileName: self.lastPathComponent, code: matches.result == -1 ? 22 : 21)
  610. }
  611. }
  612. return matches.result
  613. }
  614. }
  615. extension UIColor {
  616. public static var mainColor: UIColor {
  617. if Utils.getIsWATheme() {
  618. return whatsappGreenColor
  619. }
  620. return renderColor(hex: "#046cfc")
  621. }
  622. public static var borderTabColor: UIColor {
  623. return renderColor(hex: "#c4e4f4")
  624. }
  625. public static var nxColor: UIColor {
  626. return renderColor(hex: "#04ecfc")
  627. }
  628. public static var secondaryColor: UIColor {
  629. return renderColor(hex: "#FAFAFF")
  630. }
  631. public static var blackDarkMode: UIColor {
  632. return renderColor(hex: "#262626")
  633. }
  634. public static var orangeColor: UIColor {
  635. return renderColor(hex: "#FFA03E")
  636. }
  637. public static var orangeBNI: UIColor {
  638. return renderColor(hex: "#EE6600")
  639. }
  640. public static var greenColor: UIColor {
  641. return renderColor(hex: "#C7EA46")
  642. }
  643. public static var whiteBubbleColor: UIColor {
  644. if UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark {
  645. return blackDarkMode
  646. }
  647. return renderColor(hex: "#F5F5F5")
  648. }
  649. public static var grayTitleColor: UIColor {
  650. return renderColor(hex: "#5A5A5A")
  651. }
  652. public static var docColor: UIColor {
  653. return renderColor(hex: "#798F9A")
  654. }
  655. public static var mentionColor: UIColor {
  656. return UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark ? renderColor(hex: "#f6fcae") : renderColor(hex: "#FFA500")
  657. }
  658. public static var blueBubbleColor: UIColor {
  659. if UIApplication.shared.visibleViewController?.traitCollection.userInterfaceStyle == .dark {
  660. return renderColor(hex: "#367dd9")
  661. }
  662. return renderColor(hex: "#C5D1E1")
  663. }
  664. public static var officialColor: UIColor {
  665. return renderColor(hex: "#4c87ef")
  666. }
  667. public static var verifiedColor: UIColor {
  668. return renderColor(hex: "#00b333")
  669. }
  670. public static var ccColor: UIColor {
  671. return renderColor(hex: "#FFF1A353")
  672. }
  673. public static var internalColor: UIColor {
  674. return renderColor(hex: "#ff0000")
  675. }
  676. public static var blueTextField: UIColor {
  677. return renderColor(hex: "#4c92d2")
  678. }
  679. public static var whatsappGreenColor: UIColor {
  680. return renderColor(hex: "#20A961")
  681. }
  682. public static var whatsappGreenTitleColor: UIColor {
  683. return renderColor(hex: "#295C3B")
  684. }
  685. public static var whatsappGreenLightColor: UIColor {
  686. return renderColor(hex: "#E5FCE4")
  687. }
  688. public static var whatsappGrayPPColor: UIColor {
  689. return renderColor(hex: "#a1a5b1")
  690. }
  691. public static var waGrayLight: UIColor {
  692. return renderColor(hex: "#eceaeb")
  693. }
  694. public static var waGrayFont: UIColor {
  695. return renderColor(hex: "#b7b5b6")
  696. }
  697. public class func renderColor(hex: String) -> UIColor {
  698. var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
  699. if (cString.hasPrefix("#")) {
  700. cString.remove(at: cString.startIndex)
  701. }
  702. if ((cString.count) != 6) {
  703. return UIColor.gray
  704. }
  705. var rgbValue:UInt64 = 0
  706. Scanner(string: cString).scanHexInt64(&rgbValue)
  707. return UIColor(
  708. red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
  709. green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
  710. blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
  711. alpha: CGFloat(1.0)
  712. )
  713. }
  714. }
  715. extension UIView {
  716. public func circle() {
  717. layer.cornerRadius = 0.5 * bounds.size.width
  718. clipsToBounds = true
  719. }
  720. public func maxCornerRadius() -> CGFloat {
  721. return (self.frame.width > self.frame.height) ? self.frame.height / 2 : self.frame.width / 2
  722. }
  723. public func currentFirstResponder() -> UIView? {
  724. if self.isFirstResponder {
  725. return self
  726. }
  727. for subview in self.subviews {
  728. if let responder = subview.currentFirstResponder() {
  729. return responder
  730. }
  731. }
  732. return nil
  733. }
  734. }
  735. extension String {
  736. public func localized(uppercased: Bool = false) -> String {
  737. if let _ : String = SecureUserDefaults.shared.value(forKey: "i18n_language") {} else {
  738. // we set a default, just in case
  739. let langDefault = UserDefaults.standard.stringArray(forKey: "AppleLanguages")
  740. if langDefault![0].contains("id") {
  741. SecureUserDefaults.shared.set("id", forKey: "i18n_language")
  742. } else {
  743. SecureUserDefaults.shared.set("en", forKey: "i18n_language")
  744. }
  745. }
  746. let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? ""
  747. let bundle = Bundle.resourceBundle(for: Nexilis.self).path(forResource: lang, ofType: "lproj")
  748. let bundlePath = Bundle(path: bundle!)
  749. let result = NSLocalizedString(
  750. self,
  751. tableName: "Localizable",
  752. bundle: bundlePath!,
  753. value: self,
  754. comment: self)
  755. if uppercased {
  756. return result.uppercased()
  757. }
  758. return result
  759. }
  760. }
  761. extension UIViewController {
  762. public func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
  763. UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0);
  764. image.draw(in: CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height))
  765. let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
  766. UIGraphicsEndImageContext()
  767. return newImage
  768. }
  769. }
  770. extension UITextView {
  771. enum ShouldChangeCursor {
  772. case incrementCursor
  773. case preserveCursor
  774. }
  775. func preserveCursorPosition(withChanges mutatingFunction: (UITextPosition?) -> (ShouldChangeCursor)) {
  776. //save the cursor positon
  777. var cursorPosition: UITextPosition? = nil
  778. if let selectedRange = self.selectedTextRange {
  779. let offset = self.offset(from: self.beginningOfDocument, to: selectedRange.start)
  780. cursorPosition = self.position(from: self.beginningOfDocument, offset: offset)
  781. }
  782. //make mutaing changes that may reset the cursor position
  783. let shouldChangeCursor = mutatingFunction(cursorPosition)
  784. //restore the cursor
  785. if var cursorPosition = cursorPosition {
  786. if shouldChangeCursor == .incrementCursor {
  787. cursorPosition = self.position(from: cursorPosition, offset: 1) ?? cursorPosition
  788. }
  789. if let range = self.textRange(from: cursorPosition, to: cursorPosition) {
  790. self.selectedTextRange = range
  791. }
  792. }
  793. }
  794. func rangesOfMentionText(withColor color: UIColor) -> [NSRange] {
  795. var ranges: [NSRange] = []
  796. guard let attributedText = self.attributedText else { return ranges }
  797. attributedText.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in
  798. if let foundColor = value as? UIColor, foundColor == color {
  799. ranges.append(range)
  800. }
  801. }
  802. return ranges
  803. }
  804. }
  805. extension String {
  806. public func substring(from: Int?, to: Int?) -> String {
  807. if let start = from {
  808. guard start < self.count else {
  809. return ""
  810. }
  811. }
  812. if let end = to {
  813. guard end >= 0 else {
  814. return ""
  815. }
  816. }
  817. if let start = from, let end = to {
  818. guard end - start >= 0 else {
  819. return ""
  820. }
  821. }
  822. let startIndex: String.Index
  823. if let start = from, start >= 0 {
  824. startIndex = self.index(self.startIndex, offsetBy: start)
  825. } else {
  826. startIndex = self.startIndex
  827. }
  828. let endIndex: String.Index
  829. if let end = to, end >= 0, end < self.count {
  830. endIndex = self.index(self.startIndex, offsetBy: end + 1)
  831. } else {
  832. endIndex = self.endIndex
  833. }
  834. return String(self[startIndex ..< endIndex])
  835. }
  836. public func substring(with nsRange: NSRange) -> String? {
  837. guard let range = Range(nsRange, in: self) else { return nil }
  838. return String(self[range])
  839. }
  840. static public func offset() -> CGFloat{
  841. guard let fontSize = Int(SecureUserDefaults.shared.value(forKey: "font_size") ?? "0") else { return 0 }
  842. return CGFloat(fontSize)
  843. }
  844. public func richText(
  845. fontSize: CGFloat = 12 + String.offset(),
  846. isEditing: Bool = false,
  847. isSearching: Bool = false,
  848. textSearch: String = "",
  849. group_id: String = "",
  850. listMentionInTextField: [User] = []
  851. ) -> NSMutableAttributedString {
  852. let font = UIFont.systemFont(ofSize: fontSize)
  853. let boldFont = UIFont.boldSystemFont(ofSize: fontSize)
  854. let italicFont = UIFont.italicSystemFont(ofSize: fontSize)
  855. let boldItalicFont = UIFont.systemFont(ofSize: fontSize, weight: .semibold)
  856. let textUTF8 = self
  857. let finalText = NSMutableAttributedString(string: textUTF8, attributes: [.font: font])
  858. let rules: [(String, [NSAttributedString.Key: Any])] = [
  859. ("_", [NSAttributedString.Key.font: italicFont]),
  860. ("*", [NSAttributedString.Key.font: boldFont]),
  861. ("~", [NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.single.rawValue]),
  862. ("^", [NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue]),
  863. ("$", [NSAttributedString.Key.font: italicFont,
  864. NSAttributedString.Key.foregroundColor: UIColor.darkGray])
  865. ]
  866. if !isEditing {
  867. applyParagraphStyles(to: finalText, font: font)
  868. }
  869. for (sign, attributes) in rules {
  870. applyTextFormatting(to: finalText, sign: sign, attributes: attributes, isEditing: isEditing, boldItalicFont: boldItalicFont)
  871. }
  872. processMentions(in: finalText, groupID: group_id, isEditing: isEditing, listMentionInTextField: listMentionInTextField)
  873. if isSearching {
  874. highlightSearchText(in: finalText, searchText: textSearch)
  875. }
  876. return finalText
  877. }
  878. // MARK: - Helper Functions
  879. private func applyTextFormatting(
  880. to text: NSMutableAttributedString,
  881. sign: String,
  882. attributes: [NSAttributedString.Key: Any],
  883. isEditing: Bool,
  884. boldItalicFont: UIFont? = nil
  885. ) {
  886. let escapedSign = NSRegularExpression.escapedPattern(for: sign)
  887. let pattern = "\(escapedSign)(.+?)\(escapedSign)"
  888. guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return }
  889. let matches = regex.matches(in: text.string, options: [], range: NSRange(location: 0, length: text.length))
  890. for match in matches.reversed() {
  891. let fullRange = match.range
  892. let textRange = match.range(at: 1)
  893. // Special case: if applying bold or italic, check if the other style is already present
  894. if let font = attributes[.font] as? UIFont, let boldItalicFont {
  895. let currentFont = text.attribute(.font, at: textRange.location, effectiveRange: nil) as? UIFont
  896. let isItalic = currentFont?.fontDescriptor.symbolicTraits.contains(.traitItalic) ?? false
  897. let isBold = currentFont?.fontDescriptor.symbolicTraits.contains(.traitBold) ?? false
  898. if (font.fontDescriptor.symbolicTraits.contains(.traitBold) && isItalic) ||
  899. (font.fontDescriptor.symbolicTraits.contains(.traitItalic) && isBold) {
  900. text.addAttribute(.font, value: boldItalicFont, range: textRange)
  901. } else {
  902. text.addAttribute(.font, value: font, range: textRange)
  903. }
  904. } else {
  905. // Apply normally
  906. for (key, value) in attributes {
  907. text.addAttribute(key, value: value, range: textRange)
  908. }
  909. }
  910. if !isEditing {
  911. text.replaceCharacters(in: NSRange(location: fullRange.upperBound - sign.count, length: sign.count), with: "")
  912. text.replaceCharacters(in: NSRange(location: fullRange.lowerBound, length: sign.count), with: "")
  913. } else {
  914. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.lowerBound, length: sign.count))
  915. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.upperBound - sign.count, length: sign.count))
  916. }
  917. }
  918. }
  919. private func applyParagraphStyles(to text: NSMutableAttributedString, font: UIFont) {
  920. let original = text.string
  921. let lines = original.components(separatedBy: .newlines)
  922. let result = NSMutableAttributedString()
  923. // regex untuk deteksi dan menghapus leading space/tab hanya jika diikuti bullet / number.
  924. let leadingPattern = #"^[ \t]+(?=(•|[0-9]+\.) )"#
  925. let numPattern = #"^[0-9]+\."#
  926. let leadingRegex = try? NSRegularExpression(pattern: leadingPattern, options: [])
  927. let numRegex = try? NSRegularExpression(pattern: numPattern, options: [])
  928. for (index, var line) in lines.enumerated() {
  929. // jika ada leading spaces/tabs sebelum bullet/number -> hapus
  930. if let regex = leadingRegex {
  931. let nsLine = line as NSString
  932. let fullRange = NSRange(location: 0, length: nsLine.length)
  933. if let match = regex.firstMatch(in: line, options: [], range: fullRange) {
  934. line = nsLine.replacingCharacters(in: match.range, with: "")
  935. }
  936. }
  937. // cek apakah setelah pembersihan ini baris diawali bullet atau number.
  938. let trimmedLeading = line.trimmingCharacters(in: .whitespaces)
  939. let paragraphStyle = NSMutableParagraphStyle()
  940. paragraphStyle.lineSpacing = 0
  941. paragraphStyle.paragraphSpacing = 0
  942. paragraphStyle.firstLineHeadIndent = 0
  943. var attributes: [NSAttributedString.Key: Any] = [
  944. .font: font
  945. ]
  946. if trimmedLeading.hasPrefix("•") ||
  947. (numRegex?.firstMatch(in: trimmedLeading, options: [], range: NSRange(location: 0, length: (trimmedLeading as NSString).length)) != nil) {
  948. // list detected -> atur headIndent sesuai tipe
  949. if trimmedLeading.hasPrefix("•") {
  950. paragraphStyle.headIndent = 12 + String.offset() - 3
  951. } else {
  952. paragraphStyle.headIndent = 12 + String.offset()
  953. }
  954. attributes[.paragraphStyle] = paragraphStyle
  955. }
  956. // gabungkan baris (ikutkan newline kecuali baris terakhir) dan tambahkan ke result
  957. var content = line
  958. if index < lines.count - 1 {
  959. content += "\n"
  960. }
  961. let attrLine = NSAttributedString(string: content, attributes: attributes)
  962. result.append(attrLine)
  963. }
  964. // replace seluruh attributed string dengan yang telah dibangun ulang
  965. text.setAttributedString(result)
  966. }
  967. private func processMentions(in text: NSMutableAttributedString, groupID: String, isEditing: Bool, listMentionInTextField: [User] = []) {
  968. let regex = try? NSRegularExpression(pattern: "@([\\w-]+)", options: [])
  969. let matches = regex?.matches(in: text.string, options: [], range: NSRange(location: 0, length: text.length)) ?? []
  970. for match in matches.reversed() {
  971. let range = match.range(at: 1)
  972. let username = (text.string as NSString).substring(with: range)
  973. if isEditing && listMentionInTextField.count > 0 {
  974. for mention in listMentionInTextField {
  975. let upper = (Int(mention.ex_block ?? "0") ?? 0)
  976. let lower = upper - mention.fullName.count
  977. if lower >= 0 {
  978. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: lower, length: 1))
  979. text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: lower + 1, length: mention.fullName.count))
  980. text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: lower, length: mention.fullName.count + 1))
  981. }
  982. }
  983. } else {
  984. if let member = Member.getMember(f_pin: username) {
  985. let fullName = "\(member.fullName)".trimmingCharacters(in: .whitespaces)
  986. text.replaceCharacters(in: range, with: fullName)
  987. if ((!groupID.isEmpty && Member.getMemberInGroup(f_pin: username, group_id: groupID) != nil) || username == "-997") {
  988. let newRange = (text.string as NSString).range(of: "@\(fullName)")
  989. text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: newRange.lowerBound, length: 1))
  990. text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: newRange.lowerBound + 1, length: fullName.count))
  991. text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: newRange)
  992. }
  993. }
  994. }
  995. }
  996. }
  997. private func highlightSearchText(in text: NSMutableAttributedString, searchText: String) {
  998. let range = (text.string as NSString).range(of: searchText, options: .caseInsensitive)
  999. if range.location != NSNotFound {
  1000. text.addAttribute(.backgroundColor, value: UIColor.systemYellow, range: range)
  1001. }
  1002. }
  1003. func distance(of index: String.Index) -> Int? {
  1004. return utf16.distance(from: startIndex, to: index)
  1005. }
  1006. }
  1007. extension UIFont {
  1008. var bold: UIFont {
  1009. return with(traits: .traitBold)
  1010. } // bold
  1011. var italic: UIFont {
  1012. return with(traits: .traitItalic)
  1013. } // italic
  1014. func with(traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
  1015. guard let descriptor = self.fontDescriptor.withSymbolicTraits(traits) else {
  1016. return self
  1017. } // guard
  1018. return UIFont(descriptor: descriptor, size: 0)
  1019. } // with(traits:)
  1020. }
  1021. extension UILabel {
  1022. public func set(image: UIImage, with text: String, size: CGFloat, y: CGFloat) {
  1023. let attachment = NSTextAttachment()
  1024. attachment.image = image
  1025. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  1026. let attachmentStr = NSAttributedString(attachment: attachment)
  1027. let mutableAttributedString = NSMutableAttributedString()
  1028. mutableAttributedString.append(attachmentStr)
  1029. let textString = NSAttributedString(string: text, attributes: [.font: self.font!])
  1030. mutableAttributedString.append(textString)
  1031. self.attributedText = mutableAttributedString
  1032. }
  1033. public func setAttributeText(image: UIImage, with textMutable: NSAttributedString, size: CGFloat, y: CGFloat) {
  1034. let attachment = NSTextAttachment()
  1035. attachment.image = image
  1036. attachment.bounds = CGRect(x: 0, y: y, width: size, height: size)
  1037. let attachmentStr = NSAttributedString(attachment: attachment)
  1038. let mutableAttributedString = NSMutableAttributedString()
  1039. mutableAttributedString.append(attachmentStr)
  1040. mutableAttributedString.append(textMutable)
  1041. self.attributedText = mutableAttributedString
  1042. }
  1043. }
  1044. extension Bundle {
  1045. public static func resourceBundle(for frameworkClass: AnyClass) -> Bundle {
  1046. let frameworkBundle = Bundle(for: frameworkClass)
  1047. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLite", withExtension: "bundle"),
  1048. let resourceBundle = Bundle(url: resourceBundleURL) else {
  1049. return frameworkBundle
  1050. }
  1051. return resourceBundle
  1052. }
  1053. public static func resourcesMediaBundle(for frameworkClass: AnyClass) -> Bundle {
  1054. let frameworkBundle = Bundle(for: frameworkClass)
  1055. guard let resourceBundleURL = frameworkBundle.url(forResource: "NexilisLiteResources", withExtension: "bundle"),
  1056. let resourceBundle = Bundle(url: resourceBundleURL) else {
  1057. return frameworkBundle
  1058. }
  1059. return resourceBundle
  1060. }
  1061. }
  1062. //extension UIFont {
  1063. //
  1064. // static func register(from url: URL) throws {
  1065. // guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
  1066. // throw fatalError("Could not create font data provider for \(url).")
  1067. // }
  1068. // let font = CGFont(fontDataProvider)
  1069. // var error: Unmanaged<CFError>?
  1070. // guard CTFontManagerRegisterGraphicsFont(font!, &error) else {
  1071. // throw error!.takeUnretainedValue()
  1072. // }
  1073. // }
  1074. //
  1075. //}
  1076. private var actionKey: UInt8 = 0
  1077. extension UIButton {
  1078. private func actionHandleBlock(action:(() -> Void)? = nil) {
  1079. struct __ {
  1080. static var action :(() -> Void)?
  1081. }
  1082. if action != nil {
  1083. __.action = action
  1084. } else {
  1085. __.action?()
  1086. }
  1087. }
  1088. @objc private func triggerActionHandleBlock() {
  1089. self.actionHandleBlock()
  1090. }
  1091. public func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  1092. self.actionHandleBlock(action: action)
  1093. self.addTarget(self, action: #selector(self.triggerActionHandleBlock), for: control)
  1094. }
  1095. public func setImageRightOfText(image: UIImage?, for state: UIControl.State) {
  1096. self.setImage(image, for: state)
  1097. self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -((image?.size.width ?? 0) + 5), bottom: 0, right: (image?.size.width ?? 0))
  1098. self.imageEdgeInsets = UIEdgeInsets(top: 10, left: (self.titleLabel?.frame.size.width ?? 0) + 90, bottom: 10, right: -((self.titleLabel?.frame.size.width ?? 0) + 5))
  1099. }
  1100. func addAction(for controlEvents: UIControl.Event = .touchUpInside,
  1101. _ closure: @escaping (UIButton) -> Void) {
  1102. objc_setAssociatedObject(self, &actionKey, closure, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  1103. addTarget(self, action: #selector(handleAction), for: controlEvents)
  1104. }
  1105. @objc private func handleAction() {
  1106. if let closure = objc_getAssociatedObject(self, &actionKey) as? (UIButton) -> Void {
  1107. closure(self)
  1108. }
  1109. }
  1110. }
  1111. extension UINavigationController {
  1112. func replaceAllViewController(with viewController: UIViewController, animated: Bool) {
  1113. pushViewController(viewController, animated: animated)
  1114. viewControllers.removeSubrange(1...viewControllers.count - 2)
  1115. }
  1116. var rootViewController : UIViewController? {
  1117. return viewControllers.first
  1118. }
  1119. func popViewController(animated: Bool, completion: @escaping () -> Void) {
  1120. popViewController(animated: animated)
  1121. if animated, let coordinator = transitionCoordinator {
  1122. coordinator.animate(alongsideTransition: nil) { _ in
  1123. completion()
  1124. }
  1125. } else {
  1126. completion()
  1127. }
  1128. }
  1129. }
  1130. extension UIImageView {
  1131. private static var taskKey = 0
  1132. private static var urlKey = 0
  1133. private var currentTask: URLSessionTask? {
  1134. get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask }
  1135. set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  1136. }
  1137. private var currentURL: URL? {
  1138. get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL }
  1139. set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  1140. }
  1141. public func loadImageAsync(with urlString: String?, isGif: Bool = false) {
  1142. // cancel prior task, if any
  1143. weak var oldTask = currentTask
  1144. currentTask = nil
  1145. oldTask?.cancel()
  1146. // reset imageview's image
  1147. self.image = nil
  1148. // allow supplying of `nil` to remove old image and then return immediately
  1149. guard let urlString = urlString else { return }
  1150. // check cache
  1151. if isGif, let cachedImageGif = ImageCache.shared.imageGif(forKey: urlString) {
  1152. guard let gifImage = UIImage.gifImageWithData(Data(referencing: cachedImageGif)) else {
  1153. print("Failed to create the GIF image.")
  1154. return
  1155. }
  1156. self.image = gifImage
  1157. return
  1158. }
  1159. if let cachedImage = ImageCache.shared.image(forKey: urlString) {
  1160. self.image = cachedImage
  1161. return
  1162. }
  1163. // download
  1164. guard let url = URL(string: urlString) else {
  1165. return
  1166. }
  1167. currentURL = url
  1168. let urlConfig = URLSessionConfiguration.default
  1169. let sessionDelegate = SelfSignedURLSessionDelegate()
  1170. let session = URLSession(configuration: urlConfig, delegate: sessionDelegate, delegateQueue: nil)
  1171. let task = session.dataTask(with: url) { [weak self] data, response, error in
  1172. self?.currentTask = nil
  1173. //error handling
  1174. if let error = error {
  1175. // don't bother reporting cancelation errors
  1176. if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
  1177. return
  1178. }
  1179. //print(error)
  1180. return
  1181. }
  1182. guard let data = data else {
  1183. //print("unable to extract image")
  1184. return
  1185. }
  1186. let downloadedImage = UIImage(data: data)
  1187. if isGif {
  1188. ImageCache.shared.saveGif(data: NSData(data: data), forKey: urlString)
  1189. } else {
  1190. if downloadedImage != nil {
  1191. ImageCache.shared.save(image: downloadedImage!, forKey: urlString)
  1192. }
  1193. }
  1194. if url == self?.currentURL {
  1195. DispatchQueue.main.async {
  1196. if isGif {
  1197. guard let gifImage = UIImage.gifImageWithData(data) else {
  1198. print("Failed to create the GIF image.")
  1199. return
  1200. }
  1201. self?.image = gifImage
  1202. } else {
  1203. self?.image = downloadedImage
  1204. }
  1205. }
  1206. }
  1207. }
  1208. // save and start new task
  1209. currentTask = task
  1210. task.resume()
  1211. }
  1212. private func actionHandleBlock(action:(() -> Void)? = nil) {
  1213. struct __ {
  1214. static var action :(() -> Void)?
  1215. }
  1216. if action != nil {
  1217. __.action = action
  1218. } else {
  1219. __.action?()
  1220. }
  1221. }
  1222. @objc private func triggerActionHandleBlock() {
  1223. self.actionHandleBlock()
  1224. }
  1225. func actionHandle(controlEvents control :UIControl.Event, ForAction action:@escaping () -> Void) {
  1226. self.actionHandleBlock(action: action)
  1227. self.isUserInteractionEnabled = true
  1228. self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.triggerActionHandleBlock)))
  1229. }
  1230. }
  1231. extension UITextField {
  1232. public enum PaddingSide {
  1233. case left(CGFloat)
  1234. case right(CGFloat)
  1235. case both(CGFloat)
  1236. }
  1237. public func addPadding(_ padding: PaddingSide) {
  1238. self.leftViewMode = .always
  1239. self.layer.masksToBounds = true
  1240. switch padding {
  1241. case .left(let spacing):
  1242. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1243. self.leftView = paddingView
  1244. self.rightViewMode = .always
  1245. case .right(let spacing):
  1246. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1247. self.rightView = paddingView
  1248. self.rightViewMode = .always
  1249. case .both(let spacing):
  1250. let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: spacing, height: self.frame.height))
  1251. // left
  1252. self.leftView = paddingView
  1253. self.leftViewMode = .always
  1254. // right
  1255. self.rightView = paddingView
  1256. self.rightViewMode = .always
  1257. }
  1258. }
  1259. }
  1260. public class ImageCache {
  1261. public static let shared = ImageCache()
  1262. private let cache = NSCache<NSString, UIImage>()
  1263. private let cacheGif = NSCache<NSString, NSData>()
  1264. private var cacheKeyMap: [String: String] = [:]
  1265. private let imageCacheDirectory: URL
  1266. private let gifCacheDirectory: URL
  1267. private init() {
  1268. let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
  1269. imageCacheDirectory = documentsDirectory.appendingPathComponent("fileCacheImage")
  1270. gifCacheDirectory = documentsDirectory.appendingPathComponent("fileCacheGif")
  1271. loadCacheFromDisk(directory: imageCacheDirectory, isGif: false)
  1272. loadCacheFromDisk(directory: gifCacheDirectory, isGif: true)
  1273. }
  1274. public func save(image: UIImage, forKey key: String) {
  1275. let sanitizedKey = sanitizeKey(key)
  1276. cache.setObject(image, forKey: sanitizedKey as NSString)
  1277. cacheKeyMap[key] = sanitizedKey
  1278. saveCacheToDisk(directory: imageCacheDirectory, isGif: false)
  1279. }
  1280. public func saveGif(data: NSData, forKey key: String) {
  1281. let sanitizedKey = sanitizeKey(key)
  1282. cacheGif.setObject(data, forKey: sanitizedKey as NSString)
  1283. cacheKeyMap[key] = sanitizedKey
  1284. saveCacheToDisk(directory: gifCacheDirectory, isGif: true)
  1285. }
  1286. public func image(forKey key: String) -> UIImage? {
  1287. let sanitizedKey = sanitizeKey(key)
  1288. if let image = cache.object(forKey: sanitizedKey as NSString) {
  1289. return image
  1290. }
  1291. // Try loading from disk if not in memory
  1292. let imageName = "\(sanitizedKey).png"
  1293. if FileEncryption.shared.isSecureExists(filename: imageName) {
  1294. do {
  1295. if var data = try FileEncryption.shared.readSecure(filename: imageName) {
  1296. if let decrypted = FileEncryption.shared.decryptFileFromServer(data: data) {
  1297. data = decrypted
  1298. }
  1299. if let image = UIImage(data: data) {
  1300. cache.setObject(image, forKey: sanitizedKey as NSString)
  1301. return image
  1302. }
  1303. }
  1304. } catch {
  1305. print("Failed to read or decrypt image from disk: \(error)")
  1306. }
  1307. }
  1308. return nil
  1309. }
  1310. public func imageGif(forKey key: String) -> NSData? {
  1311. let sanitizedKey = sanitizeKey(key)
  1312. return cacheGif.object(forKey: sanitizedKey as NSString)
  1313. }
  1314. private func saveCacheToDisk(directory: URL, isGif: Bool) {
  1315. let fileManager = FileManager.default
  1316. if !fileManager.fileExists(atPath: directory.path) {
  1317. do {
  1318. try fileManager.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
  1319. } catch {
  1320. print("Failed to create cache directory: \(error)")
  1321. return
  1322. }
  1323. }
  1324. let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
  1325. do {
  1326. let jsonData = try JSONSerialization.data(withJSONObject: cacheKeyMap, options: [])
  1327. try jsonData.write(to: mappingFilePath)
  1328. } catch {
  1329. print("Failed to write mapping file: \(error)")
  1330. return
  1331. }
  1332. for (_, sanitizedKey) in cacheKeyMap {
  1333. if isGif {
  1334. if let gifData = cacheGif.object(forKey: sanitizedKey as NSString) {
  1335. try? FileEncryption.shared.writeSecure(filename: "\(sanitizedKey).gif", data: gifData as Data)
  1336. }
  1337. } else {
  1338. if let image = cache.object(forKey: sanitizedKey as NSString),
  1339. let imageData = image.pngData() {
  1340. try? FileEncryption.shared.writeSecure(filename: "\(sanitizedKey).png", data: imageData)
  1341. }
  1342. }
  1343. }
  1344. }
  1345. private func loadCacheFromDisk(directory: URL, isGif: Bool) {
  1346. let mappingFilePath = directory.appendingPathComponent("keyMapping.json")
  1347. guard let jsonData = try? Data(contentsOf: mappingFilePath),
  1348. let loadedMapping = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: String] else {
  1349. return
  1350. }
  1351. if cacheKeyMap.isEmpty {
  1352. cacheKeyMap = loadedMapping
  1353. }
  1354. for (_, sanitizedKey) in loadedMapping {
  1355. let fileExtension = isGif ? "gif" : "png"
  1356. let fileName = "\(sanitizedKey).\(fileExtension)"
  1357. guard FileEncryption.shared.isSecureExists(filename: fileName) else { continue }
  1358. do {
  1359. if var data = try FileEncryption.shared.readSecure(filename: fileName) {
  1360. if let decrypted = FileEncryption.shared.decryptFileFromServer(data: data) {
  1361. data = decrypted
  1362. }
  1363. if isGif {
  1364. cacheGif.setObject(data as NSData, forKey: sanitizedKey as NSString)
  1365. } else if let image = UIImage(data: data) {
  1366. cache.setObject(image, forKey: sanitizedKey as NSString)
  1367. }
  1368. }
  1369. } catch {
  1370. print("Error loading \(fileExtension) from disk: \(error)")
  1371. }
  1372. }
  1373. }
  1374. private func sanitizeKey(_ key: String) -> String {
  1375. let data = Data(key.utf8)
  1376. var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
  1377. data.withUnsafeBytes {
  1378. _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
  1379. }
  1380. return hash.map { String(format: "%02x", $0) }.joined()
  1381. }
  1382. }
  1383. public class LibAlertController: UIAlertController {
  1384. public override func viewWillAppear(_ animated: Bool) {
  1385. super.viewWillAppear(animated)
  1386. // Customize the title's font
  1387. let titleFont = UIFont.boldSystemFont(ofSize: 16)
  1388. let titleAttributes = [NSAttributedString.Key.font: titleFont]
  1389. setValue(NSAttributedString(string: self.title ?? "", attributes: titleAttributes), forKey: "attributedTitle")
  1390. // Change the font for the message
  1391. let messageFont = UIFont.systemFont(ofSize: 14)
  1392. let messageAttributes = [NSAttributedString.Key.font: messageFont]
  1393. setValue(NSAttributedString(string: self.message ?? "", attributes: messageAttributes), forKey: "attributedMessage")
  1394. for i in self.actions {
  1395. let attributedText = NSAttributedString(string: i.title ?? "", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)])
  1396. guard let label = (i.value(forKey: "__representer") as AnyObject).value(forKey: "label") as? UILabel else { return }
  1397. label.attributedText = attributedText
  1398. }
  1399. }
  1400. }
  1401. extension UISearchBar
  1402. {
  1403. public func setMagnifyingGlassColorTo(color: UIColor)
  1404. {
  1405. // Search Icon
  1406. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1407. let glassIconView = textFieldInsideSearchBar?.leftView as? UIImageView
  1408. glassIconView?.image = glassIconView?.image?.withRenderingMode(.alwaysTemplate)
  1409. glassIconView?.tintColor = color
  1410. }
  1411. func setClearButtonColorTo(color: UIColor)
  1412. {
  1413. // Clear Button
  1414. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1415. let crossIconView = textFieldInsideSearchBar?.value(forKey: "clearButton") as? UIButton
  1416. crossIconView?.setImage(crossIconView?.currentImage?.withRenderingMode(.alwaysTemplate), for: .normal)
  1417. crossIconView?.tintColor = color
  1418. }
  1419. func setPlaceholderTextColorTo(color: UIColor)
  1420. {
  1421. let textFieldInsideSearchBar = self.value(forKey: "searchField") as? UITextField
  1422. textFieldInsideSearchBar?.textColor = color
  1423. let textFieldInsideSearchBarLabel = textFieldInsideSearchBar!.value(forKey: "placeholderLabel") as? UILabel
  1424. textFieldInsideSearchBarLabel?.textColor = color
  1425. }
  1426. public func setCustomBackgroundImage(image: UIImage)
  1427. {
  1428. setSearchFieldBackgroundImage(resizeImage(image: image, targetHeight: 30), for: .normal)
  1429. }
  1430. func resizeImage(image: UIImage, targetHeight: CGFloat) -> UIImage? {
  1431. let scaleFactor = targetHeight / image.size.height
  1432. let targetWidth = image.size.width * scaleFactor
  1433. let newSize = CGSize(width: targetWidth, height: targetHeight)
  1434. UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
  1435. defer { UIGraphicsEndImageContext() }
  1436. image.draw(in: CGRect(origin: .zero, size: newSize))
  1437. guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
  1438. return newImage
  1439. }
  1440. public func updateHeight(height: CGFloat, radius: CGFloat = 8.0, borderColor: CGColor = UIColor.white.cgColor) {
  1441. let image: UIImage? = UIImage.imageWithColor(color: UIColor.clear, size: CGSize(width: 1, height: height))
  1442. setSearchFieldBackgroundImage(image, for: .normal)
  1443. for subview in self.subviews {
  1444. for subSubViews in subview.subviews {
  1445. if #available(iOS 13.0, *) {
  1446. for child in subSubViews.subviews {
  1447. if let textField = child as? UISearchTextField {
  1448. textField.layer.cornerRadius = radius
  1449. textField.clipsToBounds = true
  1450. textField.layer.borderColor = borderColor
  1451. textField.layer.borderWidth = 1.0
  1452. }
  1453. }
  1454. continue
  1455. }
  1456. if let textField = subSubViews as? UITextField {
  1457. textField.layer.cornerRadius = radius
  1458. textField.clipsToBounds = true
  1459. textField.layer.borderColor = borderColor
  1460. textField.layer.borderWidth = 1.0
  1461. }
  1462. }
  1463. }
  1464. }
  1465. }
  1466. extension String {
  1467. init(unicodeScalar: UnicodeScalar) {
  1468. self.init(Character(unicodeScalar))
  1469. }
  1470. init?(unicodeCodepoint: Int) {
  1471. if let unicodeScalar = UnicodeScalar(unicodeCodepoint) {
  1472. self.init(unicodeScalar: unicodeScalar)
  1473. } else {
  1474. return nil
  1475. }
  1476. }
  1477. static func +(lhs: String, rhs: Int) -> String {
  1478. return lhs + String(unicodeCodepoint: rhs)!
  1479. }
  1480. static func +=(lhs: inout String, rhs: Int) {
  1481. lhs = lhs + rhs
  1482. }
  1483. }
  1484. extension UIGraphicsRenderer {
  1485. static func renderImagesAt(urls: [NSURL], size: CGSize, scale: CGFloat = 1) -> UIImage {
  1486. let renderer = UIGraphicsImageRenderer(size: size)
  1487. let options: [NSString: Any] = [
  1488. kCGImageSourceThumbnailMaxPixelSize: max(size.width * scale, size.height * scale),
  1489. kCGImageSourceCreateThumbnailFromImageAlways: true
  1490. ]
  1491. let thumbnails = try urls.map { url -> CGImage in
  1492. let imageSource = CGImageSourceCreateWithURL(url, nil)
  1493. let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource!, 0, options as CFDictionary)
  1494. return scaledImage!
  1495. }
  1496. // Translate Y-axis down because cg images are flipped and it falls out of the frame (see bellow)
  1497. let rect = CGRect(x: 0,
  1498. y: -size.height,
  1499. width: size.width,
  1500. height: size.height)
  1501. let resizedImage = renderer.image { ctx in
  1502. let context = ctx.cgContext
  1503. context.scaleBy(x: 1, y: -1) //Flip it ( cg y-axis is flipped)
  1504. for image in thumbnails {
  1505. context.draw(image, in: rect)
  1506. }
  1507. }
  1508. return resizedImage
  1509. }
  1510. static func renderImageAt(url: NSURL, size: CGSize, scale: CGFloat = 1) -> UIImage {
  1511. return renderImagesAt(urls: [url], size: size, scale: scale)
  1512. }
  1513. }
  1514. extension NSAttributedString {
  1515. convenience init(html: String) throws {
  1516. let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
  1517. .documentType: NSAttributedString.DocumentType.html,
  1518. .characterEncoding: String.Encoding.utf8.rawValue
  1519. ]
  1520. guard let data = html.data(using: .utf8) else {
  1521. throw NSError(domain: "Invalid HTML", code: 0, userInfo: nil)
  1522. }
  1523. try self.init(data: data, options: options, documentAttributes: nil)
  1524. }
  1525. }
  1526. extension Collection {
  1527. subscript(safe index: Index) -> Element? {
  1528. return indices.contains(index) ? self[index] : nil
  1529. }
  1530. }
  1531. extension UIViewController {
  1532. static let swizzleViewDidAppearImplementation: Void = {
  1533. let originalSelector = #selector(viewDidAppear(_:))
  1534. let swizzledSelector = #selector(swizzled_viewDidAppear(_:))
  1535. guard
  1536. let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),
  1537. let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector)
  1538. else {
  1539. return
  1540. }
  1541. method_exchangeImplementations(originalMethod, swizzledMethod)
  1542. }()
  1543. @objc func swizzled_viewDidAppear(_ animated: Bool) {
  1544. swizzled_viewDidAppear(animated)
  1545. if self is UINavigationController || self is UITabBarController {
  1546. // If it's a UINavigationController, get its topViewController
  1547. if let nav = self as? UINavigationController,
  1548. let topVC = nav.topViewController {
  1549. DataCaptured.actVC = "\(String(describing: type(of: topVC)))"
  1550. DispatchQueue.global().async {
  1551. DataCaptured.sendLogMonitorActivity()
  1552. }
  1553. }
  1554. return
  1555. }
  1556. if "\(type(of: self))".hasPrefix("UI") {
  1557. return
  1558. }
  1559. // Normal screen
  1560. DataCaptured.actVC = "\(String(describing: type(of: self)))"
  1561. DispatchQueue.global().async {
  1562. DataCaptured.sendLogMonitorActivity()
  1563. }
  1564. }
  1565. }
  1566. extension UINavigationController {
  1567. static let swizzlePushViewControllerImplementation: Void = {
  1568. let originalSelector = #selector(pushViewController(_:animated:))
  1569. let swizzledSelector = #selector(swizzled_pushViewController(_:animated:))
  1570. guard
  1571. let originalMethod = class_getInstanceMethod(UINavigationController.self, originalSelector),
  1572. let swizzledMethod = class_getInstanceMethod(UINavigationController.self, swizzledSelector)
  1573. else {
  1574. return
  1575. }
  1576. method_exchangeImplementations(originalMethod, swizzledMethod)
  1577. }()
  1578. @objc func swizzled_pushViewController(_ viewController: UIViewController, animated: Bool) {
  1579. DataCaptured.actNC = "\(String(describing: type(of: viewController)))"
  1580. DataCaptured.actVC = ""
  1581. DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
  1582. if DataCaptured.actVC.isEmpty {
  1583. DataCaptured.sendLogMonitorActivity()
  1584. }
  1585. })
  1586. swizzled_pushViewController(viewController, animated: animated)
  1587. }
  1588. static let swizzlePopViewControllerImplementation: Void = {
  1589. let originalSelector = #selector(popViewController(animated:))
  1590. let swizzledSelector = #selector(swizzled_popViewController(animated:))
  1591. guard
  1592. let originalMethod = class_getInstanceMethod(UINavigationController.self, originalSelector),
  1593. let swizzledMethod = class_getInstanceMethod(UINavigationController.self, swizzledSelector)
  1594. else {
  1595. return
  1596. }
  1597. method_exchangeImplementations(originalMethod, swizzledMethod)
  1598. }()
  1599. @objc func swizzled_popViewController(animated: Bool) -> UIViewController? {
  1600. let poppedVC = swizzled_popViewController(animated: animated)
  1601. if let topVC = self.topViewController {
  1602. DataCaptured.actNC = "\(String(describing: type(of: topVC)))"
  1603. DataCaptured.actVC = ""
  1604. DispatchQueue.global().asyncAfter(deadline: .now() + 1, execute: {
  1605. if DataCaptured.actVC.isEmpty {
  1606. DataCaptured.sendLogMonitorActivity()
  1607. }
  1608. })
  1609. }
  1610. return poppedVC
  1611. }
  1612. }
  1613. extension UIApplication {
  1614. static let swizzleSendAction: Void = {
  1615. let original = #selector(sendAction(_:to:from:for:))
  1616. let swizzled = #selector(swizzled_sendAction(_:to:from:for:))
  1617. guard let originalMethod = class_getInstanceMethod(UIApplication.self, original),
  1618. let swizzledMethod = class_getInstanceMethod(UIApplication.self, swizzled) else { return }
  1619. method_exchangeImplementations(originalMethod, swizzledMethod)
  1620. }()
  1621. @objc func swizzled_sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool {
  1622. if let button = sender as? UIButton {
  1623. let title = button.titleLabel?.text ?? "Unnamed Button"
  1624. DataCaptured.action = "CLICKED"
  1625. DataCaptured.textAction = title
  1626. DispatchQueue.global().async {
  1627. DataCaptured.sendLogMonitorAction()
  1628. }
  1629. }
  1630. return swizzled_sendAction(action, to: target, from: sender, for: event)
  1631. }
  1632. static func enableSwizzling() {
  1633. _ = swizzleSendAction
  1634. }
  1635. }
  1636. class DataCaptured: NSObject {
  1637. static var actVC = ""
  1638. static var actNC = ""
  1639. static var action = ""
  1640. static var textAction = ""
  1641. override init() {
  1642. NotificationCenter.default.addObserver(
  1643. forName: UITextField.textDidChangeNotification,
  1644. object: nil,
  1645. queue: .main
  1646. ) { notification in
  1647. if let textField = notification.object as? UITextField {
  1648. DataCaptured.action = "TEXT_CHANGED"
  1649. DataCaptured.textAction = textField.text ?? ""
  1650. DispatchQueue.global().async {
  1651. DataCaptured.sendLogMonitorAction()
  1652. }
  1653. }
  1654. }
  1655. NotificationCenter.default.addObserver(
  1656. forName: UITextView.textDidChangeNotification,
  1657. object: nil,
  1658. queue: .main
  1659. ) { notification in
  1660. if let textView = notification.object as? UITextView {
  1661. DataCaptured.action = "TEXT_CHANGED"
  1662. DataCaptured.textAction = textView.text ?? ""
  1663. DispatchQueue.global().async {
  1664. DataCaptured.sendLogMonitorAction()
  1665. }
  1666. }
  1667. }
  1668. }
  1669. static func sendLogMonitorAction() {
  1670. if !Nexilis.checkingAccess(key: "activity_monitoring") {
  1671. return
  1672. }
  1673. var type = "1"
  1674. var value = "1"
  1675. if action == "TEXT_CHANGED" {
  1676. type = "2"
  1677. value = textAction
  1678. }
  1679. // print("sendLogMonitorAction: \(type) <><> \(textAction) <><> \(value)")
  1680. _ = Nexilis.writeSync(message: CoreMessage_TMessageBank.getLogMonitor(type: type, label: textAction, value: value))
  1681. }
  1682. static func sendLogMonitorActivity() {
  1683. if !Nexilis.checkingAccess(key: "activity_monitoring") {
  1684. return
  1685. }
  1686. var act = actNC
  1687. if !actVC.isEmpty {
  1688. act = actVC
  1689. }
  1690. // print("sendLogMonitorActivity: \(act)")
  1691. _ = Nexilis.write(message: CoreMessage_TMessageBank.getLogActivity(pActivityClassName: act))
  1692. }
  1693. static func sendErrorDLP(fileName: String, code: Int) {
  1694. var data = collectDeviceAttributes()
  1695. data["security_shield"] = "\(code)"
  1696. data["filename"] = fileName
  1697. if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: .prettyPrinted),
  1698. let jsonString = String(data: jsonData, encoding: .utf8) {
  1699. // print("sendErrorDLP: \(jsonString)")
  1700. _ = Nexilis.write(message: CoreMessage_TMessageBank.getCaptureDLP(data: jsonString))
  1701. }
  1702. }
  1703. private var currentLocation: CLLocation?
  1704. private static func collectDeviceAttributes() -> [String: Any] {
  1705. var params: [String: Any] = [:]
  1706. // User and session
  1707. let me: String? = User.getMyPin() ?? ""
  1708. let sesId: String? = Utils.getConnectionID()
  1709. params["f_pin"] = me
  1710. params["session_id"] = sesId
  1711. // App info (replace with your preferences retrieval)
  1712. params["api"] = Nexilis.sAPIKey
  1713. params["app_id"] = APIS.getAppNm()
  1714. params["lib_version"] = Nexilis.cpaasVersion
  1715. params["app_version"] = Nexilis.cpaasVersion
  1716. // Network Info
  1717. let (netType, netTypeName) = getNetworkType()
  1718. let (operatorCode, operatorName) = getCarrierInfo()
  1719. let (wifiStatus, wifiIp, wifiSsid, wifiBssid) = getWifiInfo()
  1720. params["network_type"] = netType
  1721. params["network_type_name"] = netTypeName
  1722. params["network_operator"] = operatorCode
  1723. params["network_operator_name"] = operatorName
  1724. params["wifi_ssid"] = wifiSsid
  1725. params["wifi_bssid"] = wifiBssid
  1726. params["wifi_adapter"] = wifiStatus
  1727. params["wifi_ip"] = wifiIp
  1728. // IP Address
  1729. params["ip_addressv4"] = getIPAddress(useIPv4: true)
  1730. params["ip_address"] = getIPAddress(useIPv4: false)
  1731. // GPS / location
  1732. let semaphore = DispatchSemaphore(value: 0)
  1733. DispatchQueue.main.async {
  1734. LocationFetcher.shared.getCurrentLocation { coordinate in
  1735. var long = "0"
  1736. var lat = "0"
  1737. if let coord = coordinate {
  1738. long = "\(coord.longitude)"
  1739. lat = "\(coord.latitude)"
  1740. }
  1741. // print("Latitude: \(lat), Longitude: \(long)")
  1742. params["latitude"] = lat
  1743. params["longitude"] = long
  1744. semaphore.signal()
  1745. }
  1746. }
  1747. _ = semaphore.wait(timeout: .now() + 10.0)
  1748. // iOS doesn't have an Android ID; use identifierForVendor
  1749. params["ios_identifier"] = UIDevice.current.identifierForVendor?.uuidString ?? ""
  1750. // Device attributes
  1751. let device = UIDevice.current
  1752. params["device_NAME"] = device.name
  1753. params["device_MODEL"] = device.model
  1754. params["device_SYSTEM_NAME"] = device.systemName
  1755. params["device_SYSTEM_VERSION"] = device.systemVersion
  1756. params["device_IDENTIFIER_FOR_VENDOR"] = device.identifierForVendor?.uuidString ?? ""
  1757. return getSimData(params: params)
  1758. }
  1759. private static func getSimData(params: [String: Any] = [:]) -> [String: Any] {
  1760. var params = params
  1761. var simArray: [[String: Any]] = []
  1762. let networkInfo = CTTelephonyNetworkInfo()
  1763. if #available(iOS 12.0, *) {
  1764. if let carriers = networkInfo.serviceSubscriberCellularProviders {
  1765. for (key, carrier) in carriers {
  1766. var simInfo: [String: Any] = [:]
  1767. simInfo["carrier_name"] = carrier.carrierName ?? ""
  1768. simInfo["mcc"] = carrier.mobileCountryCode ?? ""
  1769. simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
  1770. simInfo["sim_slot"] = key // This is not a true "slot", but the key used internally
  1771. simArray.append(simInfo)
  1772. }
  1773. }
  1774. } else {
  1775. if let carrier = networkInfo.subscriberCellularProvider {
  1776. var simInfo: [String: Any] = [:]
  1777. simInfo["carrier_name"] = carrier.carrierName ?? ""
  1778. simInfo["mcc"] = carrier.mobileCountryCode ?? ""
  1779. simInfo["mnc"] = carrier.mobileNetworkCode ?? ""
  1780. simInfo["sim_slot"] = "default"
  1781. simArray.append(simInfo)
  1782. }
  1783. }
  1784. params["sim_data"] = simArray
  1785. return params
  1786. }
  1787. private static func getNetworkType() -> (type: String, name: String) {
  1788. let monitor = NWPathMonitor()
  1789. var networkType = ""
  1790. var networkTypeName = ""
  1791. let semaphore = DispatchSemaphore(value: 0)
  1792. monitor.pathUpdateHandler = { path in
  1793. if path.usesInterfaceType(.wifi) {
  1794. networkType = "1" // Corresponds to TYPE_WIFI in Android
  1795. networkTypeName = "WIFI"
  1796. } else if path.usesInterfaceType(.cellular) {
  1797. networkType = "0" // Corresponds to TYPE_MOBILE
  1798. networkTypeName = "MOBILE"
  1799. } else {
  1800. networkType = "-1"
  1801. networkTypeName = "UNKNOWN"
  1802. }
  1803. semaphore.signal()
  1804. monitor.cancel()
  1805. }
  1806. let queue = DispatchQueue(label: "NetworkMonitor")
  1807. monitor.start(queue: queue)
  1808. semaphore.wait()
  1809. return (networkType, networkTypeName)
  1810. }
  1811. private static func getCarrierInfo() -> (operatorCode: String, operatorName: String) {
  1812. let networkInfo = CTTelephonyNetworkInfo()
  1813. var carrierCode = ""
  1814. var carrierName = ""
  1815. if #available(iOS 12.0, *) {
  1816. if let carriers = networkInfo.serviceSubscriberCellularProviders {
  1817. for (_, carrier) in carriers {
  1818. carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
  1819. carrierName = carrier.carrierName ?? ""
  1820. break // Just use the first one
  1821. }
  1822. }
  1823. } else {
  1824. if let carrier = networkInfo.subscriberCellularProvider {
  1825. carrierCode = (carrier.mobileCountryCode ?? "") + (carrier.mobileNetworkCode ?? "")
  1826. carrierName = carrier.carrierName ?? ""
  1827. }
  1828. }
  1829. return (carrierCode, carrierName)
  1830. }
  1831. private static func getWifiInfo() -> (adapter: String, ip: String, ssid: String, bssid: String) {
  1832. var adapterStatus = "Off"
  1833. var ipAddress = ""
  1834. var ssid = ""
  1835. var bssid = ""
  1836. // Get IP Address
  1837. if let interfaces = CNCopySupportedInterfaces() as NSArray? {
  1838. for interfaceName in interfaces {
  1839. if let unsafeInterfaceData = CNCopyCurrentNetworkInfo(interfaceName as! CFString) as NSDictionary? {
  1840. ssid = unsafeInterfaceData["SSID"] as? String ?? ""
  1841. bssid = unsafeInterfaceData["BSSID"] as? String ?? ""
  1842. adapterStatus = "Connected"
  1843. break
  1844. }
  1845. }
  1846. }
  1847. if ssid.isEmpty {
  1848. adapterStatus = "Not Connected"
  1849. }
  1850. ipAddress = getWiFiIPAddress() ?? ""
  1851. return (adapterStatus, ipAddress, ssid, bssid)
  1852. }
  1853. private static func getWiFiIPAddress() -> String? {
  1854. var address: String?
  1855. var ifaddr: UnsafeMutablePointer<ifaddrs>?
  1856. guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { return nil }
  1857. for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
  1858. let interface = ptr.pointee
  1859. let addrFamily = interface.ifa_addr.pointee.sa_family
  1860. if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
  1861. let name = String(cString: interface.ifa_name)
  1862. if name == "en0" { // en0 is Wi-Fi
  1863. var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
  1864. getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
  1865. &hostname, socklen_t(hostname.count),
  1866. nil, socklen_t(0), NI_NUMERICHOST)
  1867. address = String(cString: hostname)
  1868. break
  1869. }
  1870. }
  1871. }
  1872. freeifaddrs(ifaddr)
  1873. return address
  1874. }
  1875. private static func getIPAddress(useIPv4: Bool) -> String {
  1876. var address: String = ""
  1877. var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
  1878. if getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr {
  1879. for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
  1880. let interface = ptr.pointee
  1881. let addrFamily = interface.ifa_addr.pointee.sa_family
  1882. if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
  1883. let name = String(cString: interface.ifa_name)
  1884. if name == "en0" || name == "pdp_ip0" {
  1885. var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
  1886. let result = getnameinfo(
  1887. interface.ifa_addr,
  1888. socklen_t(interface.ifa_addr.pointee.sa_len),
  1889. &hostname,
  1890. socklen_t(hostname.count),
  1891. nil,
  1892. socklen_t(0),
  1893. NI_NUMERICHOST
  1894. )
  1895. if result == 0 {
  1896. let ip = String(cString: hostname)
  1897. let isIPv4 = ip.contains(":") == false
  1898. if useIPv4 && isIPv4 {
  1899. address = ip
  1900. break
  1901. } else if !useIPv4 && !isIPv4 {
  1902. // Remove IPv6 scope if present
  1903. let cleanIPv6 = ip.split(separator: "%").first.map(String.init) ?? ip
  1904. address = cleanIPv6.uppercased()
  1905. break
  1906. }
  1907. }
  1908. }
  1909. }
  1910. }
  1911. freeifaddrs(ifaddr)
  1912. }
  1913. return address
  1914. }
  1915. }
  1916. private class LocationFetcher: NSObject, CLLocationManagerDelegate {
  1917. static var shared = LocationFetcher()
  1918. private var manager: CLLocationManager?
  1919. private var completion: ((CLLocationCoordinate2D?) -> Void)?
  1920. func getCurrentLocation(completion: @escaping (CLLocationCoordinate2D?) -> Void) {
  1921. self.completion = completion
  1922. self.manager = CLLocationManager()
  1923. self.manager?.delegate = self
  1924. self.manager?.desiredAccuracy = kCLLocationAccuracyBest
  1925. self.manager?.requestWhenInUseAuthorization()
  1926. if CLLocationManager.locationServicesEnabled() {
  1927. self.manager?.requestLocation()
  1928. } else {
  1929. completion(nil)
  1930. }
  1931. }
  1932. // MARK: - CLLocationManagerDelegate
  1933. func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
  1934. completion?(locations.last?.coordinate)
  1935. cleanup()
  1936. }
  1937. func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
  1938. print("Error: \(error.localizedDescription)")
  1939. completion?(nil)
  1940. cleanup()
  1941. }
  1942. private func cleanup() {
  1943. manager?.stopUpdatingLocation()
  1944. manager?.delegate = nil
  1945. manager = nil
  1946. completion = nil
  1947. }
  1948. }