SecureFolderView.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. //
  2. // SecureFolderView.swift
  3. // NexilisLite
  4. //
  5. // Created by Maronakins on 06/12/24.
  6. //
  7. import UIKit
  8. import AVFoundation
  9. import AVKit
  10. import QuickLook
  11. public class SecureFolderViewController: UIViewController, UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, QLPreviewControllerDataSource {
  12. var directoryPath: URL!
  13. var files: [SecureFolderItem] = []
  14. var filteredFiles: [SecureFolderItem] = []
  15. var previewItem: NSURL?
  16. var isGridView: Bool = true
  17. var isTab = true
  18. public init(isTab: Bool = false) {
  19. self.isTab = isTab
  20. super.init(nibName: nil, bundle: nil)
  21. }
  22. required init?(coder: NSCoder) {
  23. fatalError("init(coder:) has not been implemented")
  24. }
  25. let searchBar: UISearchBar = {
  26. let sb = UISearchBar()
  27. sb.placeholder = "Search files"
  28. return sb
  29. }()
  30. let toggleButton: UIButton = {
  31. let button = UIButton(type: .system)
  32. button.setTitle("Grid", for: .normal)
  33. button.addTarget(nil, action: #selector(toggleViewMode), for: .touchUpInside)
  34. return button
  35. }()
  36. let collectionView: UICollectionView = {
  37. let layout = UICollectionViewFlowLayout()
  38. layout.minimumLineSpacing = 10
  39. layout.minimumInteritemSpacing = 10
  40. let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
  41. cv.backgroundColor = .clear
  42. cv.register(FileCell.self, forCellWithReuseIdentifier: "FileCell")
  43. return cv
  44. }()
  45. public override func viewDidLoad() {
  46. super.viewDidLoad()
  47. let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  48. tapGesture.cancelsTouchesInView = false
  49. view.addGestureRecognizer(tapGesture)
  50. setupSubviews()
  51. loadFiles()
  52. filteredFiles = files
  53. }
  54. @objc func dismissKeyboard() {
  55. searchBar.resignFirstResponder()
  56. }
  57. @objc func toggleViewMode() {
  58. isGridView.toggle()
  59. toggleButton.setTitle(isGridView ? "Grid" : "List", for: .normal)
  60. collectionView.collectionViewLayout.invalidateLayout()
  61. collectionView.reloadData()
  62. }
  63. public override func viewWillAppear(_ animated: Bool) {
  64. // self.navigationController?.navigationController?.setNavigationBarHidden(false, animated: false)
  65. self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black]
  66. let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black, NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)]
  67. let navBarAppearance = UINavigationBarAppearance()
  68. navBarAppearance.configureWithTransparentBackground()
  69. navBarAppearance.titleTextAttributes = attributes
  70. navigationController?.navigationBar.standardAppearance = navBarAppearance
  71. navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
  72. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  73. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  74. navigationController?.navigationBar.backgroundColor = .clear
  75. navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
  76. navigationController?.navigationBar.shadowImage = UIImage()
  77. navigationController?.navigationBar.isTranslucent = true
  78. navigationController?.setNavigationBarHidden(false, animated: false)
  79. navigationController?.navigationBar.overrideUserInterfaceStyle = self.traitCollection.userInterfaceStyle == .dark ? .dark : .light
  80. navigationController?.navigationBar.barStyle = .default
  81. navigationController?.navigationBar.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black
  82. // tabBarController?.navigationItem.leftBarButtonItem = nil
  83. tabBarController?.navigationItem.searchController = nil
  84. if !isTab {
  85. navigationItem.leftBarButtonItem = UIBarButtonItem(
  86. title: "Back",
  87. style: .plain,
  88. target: self,
  89. action: #selector(dismissSelf)
  90. )
  91. }
  92. }
  93. @objc func dismissSelf() {
  94. dismiss(animated: true)
  95. }
  96. public override func viewDidAppear(_ animated: Bool) {
  97. // self.title = "Secure Folder".localized()
  98. self.navigationController?.navigationBar.topItem?.title = "Secure Folder".localized()
  99. self.navigationController?.navigationBar.setNeedsLayout()
  100. }
  101. @objc func cancel(sender: Any) {
  102. navigationController?.dismiss(animated: true, completion: nil)
  103. }
  104. func getThumbnail(for fileName: String) -> UIImage? {
  105. // Logic to get the file thumbnail if it exists.
  106. // This could include generating a thumbnail from the actual file.
  107. // Placeholder image is used here as an example.
  108. let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
  109. let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
  110. let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
  111. if let dirPath = paths.first {
  112. let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName)
  113. return UIImage(contentsOfFile: thumbURL.path) ?? UIImage(systemName: "photo")?.withTintColor(.black)
  114. }
  115. return UIImage(systemName: "text.document.fill")?.withTintColor(.black) // Replace with actual thumbnail logic
  116. }
  117. func setupSubviews() {
  118. // Add search bar
  119. searchBar.delegate = self
  120. view.addSubview(searchBar)
  121. searchBar.translatesAutoresizingMaskIntoConstraints = false
  122. toggleButton.translatesAutoresizingMaskIntoConstraints = false
  123. view.addSubview(toggleButton)
  124. collectionView.dataSource = self
  125. collectionView.delegate = self
  126. view.addSubview(collectionView)
  127. collectionView.translatesAutoresizingMaskIntoConstraints = false
  128. NSLayoutConstraint.activate([
  129. searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
  130. searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  131. searchBar.trailingAnchor.constraint(equalTo: toggleButton.leadingAnchor),
  132. toggleButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
  133. toggleButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8),
  134. toggleButton.widthAnchor.constraint(equalToConstant: 60),
  135. toggleButton.heightAnchor.constraint(equalTo: searchBar.heightAnchor),
  136. collectionView.topAnchor.constraint(equalTo: searchBar.bottomAnchor),
  137. collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
  138. collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
  139. collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
  140. ])
  141. }
  142. func loadFiles() {
  143. do {
  144. var query = "SELECT audio_id, video_id, image_id, thumb_id, file_id, attachment_flag, status, server_date FROM MESSAGE where (video_id IS NOT NULL AND video_id != '') OR (image_id IS NOT NULL AND image_id != '') OR (file_id IS NOT NULL AND file_id != '') order by server_date asc"
  145. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  146. do {
  147. if let cursorData = Database.shared.getRecords(fmdb: fmdb, query: query) {
  148. while cursorData.next() {
  149. var fileName = ""
  150. let audioId = cursorData.string(forColumn: "audio_id") ?? ""
  151. let videoId = cursorData.string(forColumn: "video_id") ?? ""
  152. let imageId = cursorData.string(forColumn: "image_id") ?? ""
  153. let thumbId = cursorData.string(forColumn: "thumb_id") ?? ""
  154. let fileId = cursorData.string(forColumn: "file_id") ?? ""
  155. let attachmentFlag = cursorData.string(forColumn: "attachment_flag") ?? ""
  156. let status = cursorData.string(forColumn: "status") ?? ""
  157. let serverDate = cursorData.string(forColumn: "server_date") ?? ""
  158. if imageId != "" {
  159. fileName = imageId
  160. }
  161. else if videoId != "" {
  162. fileName = videoId
  163. }
  164. else if fileId != "" {
  165. fileName = fileId
  166. }
  167. else if audioId != "" {
  168. fileName = audioId
  169. }
  170. if FileEncryption.shared.isSecureExists(filename: fileName) {
  171. let secureFolderItem = SecureFolderItem(audioId: audioId, videoId: videoId, imageId: imageId, fileId: fileId, thumbId: thumbId, attachmentFlag: attachmentFlag, serverDate: serverDate, status: status, filename: fileName)
  172. files.append(secureFolderItem)
  173. }
  174. }
  175. cursorData.close()
  176. }
  177. }
  178. catch {
  179. rollback.pointee = true
  180. print("Access database error: \(error.localizedDescription)")
  181. }
  182. })
  183. } catch {
  184. print("Error loading files: \(error.localizedDescription)")
  185. }
  186. }
  187. // MARK: Search Bar Delegate
  188. public func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
  189. filteredFiles = searchText.isEmpty ? files : files.filter { $0.filename.lowercased().contains(searchText.lowercased()) }
  190. collectionView.reloadData()
  191. }
  192. // MARK: Collection View Data Source
  193. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  194. return filteredFiles.count
  195. }
  196. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  197. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FileCell", for: indexPath) as! FileCell
  198. let fileItem = filteredFiles[indexPath.item]
  199. cell.label.text = fileItem.filename
  200. cell.isGrid = isGridView
  201. if !isGridView {
  202. if let timestamp = Double(fileItem.serverDate) {
  203. let date = Date(timeIntervalSince1970: timestamp / 1000)
  204. let formatter = DateFormatter()
  205. formatter.dateFormat = "dd/MM/yy HH:mm"
  206. cell.dateLabel.text = formatter.string(from: date)
  207. } else {
  208. cell.dateLabel.text = fileItem.serverDate
  209. }
  210. } else {
  211. cell.dateLabel.text = nil
  212. }
  213. cell.dateLabel.isHidden = isGridView == true
  214. cell.imageView.tintColor = .black
  215. // cell.imageView.sizeThatFits(CGSize(width: 200.0, height: 200.0))
  216. var thumbnailImage = UIImage(systemName: "doc.text")?.withTintColor(.black)
  217. if fileItem.thumbId != "" {
  218. thumbnailImage = getThumbnail(for: fileItem.thumbId)
  219. } else if fileItem.audioId != "" {
  220. thumbnailImage = UIImage(systemName: "speaker.wave.3")?.withTintColor(.black)
  221. }
  222. cell.imageView.image = thumbnailImage
  223. return cell
  224. }
  225. // MARK: Collection View Delegate Flow Layout
  226. public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
  227. let padding: CGFloat = 10
  228. // let collectionViewSize = collectionView.frame.size.width - padding
  229. // let width = collectionViewSize / 2
  230. // return CGSize(width: width, height: width)
  231. let width = collectionView.frame.width
  232. if isGridView {
  233. let itemWidth = (width - padding * 3) / 2
  234. return CGSize(width: itemWidth, height: itemWidth)
  235. } else {
  236. return CGSize(width: width - padding * 2, height: 90)
  237. }
  238. }
  239. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  240. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FileCell", for: indexPath) as! FileCell
  241. let fileItem = filteredFiles[indexPath.item]
  242. if fileItem.imageId != "" {
  243. print("this image")
  244. do {
  245. if var data = try FileEncryption.shared.readSecure(filename: fileItem.filename) {
  246. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: data)
  247. if dataDecrypt != nil {
  248. data = dataDecrypt!
  249. }
  250. APIS.openImageNexilis(imageView: cell.imageView, data: data, isGIF: true)
  251. }
  252. }
  253. catch {
  254. print("Error reading secure file")
  255. }
  256. }
  257. else if fileItem.videoId != "" {
  258. print("this video")
  259. do {
  260. if var secureData = try FileEncryption.shared.readSecure(filename: fileItem.filename) {
  261. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: secureData)
  262. if dataDecrypt != nil {
  263. secureData = dataDecrypt!
  264. }
  265. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  266. let tempPath = cachesDirectory.appendingPathComponent(fileItem.filename)
  267. try secureData.write(to: tempPath)
  268. let player = AVPlayer(url: tempPath as URL)
  269. let playerVC = AVPlayerViewController()
  270. playerVC.modalPresentationStyle = .custom
  271. playerVC.player = player
  272. self.present(playerVC, animated: true, completion: nil)
  273. }
  274. } catch {
  275. }
  276. }
  277. else if fileItem.fileId != "" {
  278. print("this file")
  279. do {
  280. if var docData = try FileEncryption.shared.readSecure(filename: fileItem.filename) {
  281. let dataDecrypt = FileEncryption.shared.decryptFileFromServer(data: docData)
  282. if dataDecrypt != nil {
  283. docData = dataDecrypt!
  284. }
  285. let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
  286. let tempPath = cachesDirectory.appendingPathComponent(fileItem.filename)
  287. try docData.write(to: tempPath)
  288. self.previewItem = tempPath as NSURL
  289. let previewController = QLPreviewController()
  290. let rightBarButton = UIBarButtonItem()
  291. previewController.navigationItem.rightBarButtonItem = rightBarButton
  292. previewController.dataSource = self
  293. previewController.modalPresentationStyle = .custom
  294. self.present(previewController,animated: true)
  295. }
  296. }
  297. catch {
  298. }
  299. }
  300. }
  301. public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
  302. return self.previewItem != nil ? 1 : 0
  303. }
  304. public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
  305. return self.previewItem!
  306. }
  307. }
  308. struct SecureFolderItem {
  309. let audioId: String
  310. let videoId: String
  311. let imageId: String
  312. let fileId: String
  313. let thumbId: String
  314. let attachmentFlag: String
  315. let serverDate: String
  316. let status: String
  317. let filename: String
  318. init(audioId: String, videoId: String, imageId: String, fileId: String, thumbId: String, attachmentFlag: String, serverDate: String, status: String, filename: String) {
  319. self.audioId = audioId
  320. self.videoId = videoId
  321. self.imageId = imageId
  322. self.fileId = fileId
  323. self.thumbId = thumbId
  324. self.attachmentFlag = attachmentFlag
  325. self.serverDate = serverDate
  326. self.status = status
  327. self.filename = filename
  328. }
  329. }
  330. // Custom UICollectionViewCell
  331. class FileCell: UICollectionViewCell {
  332. let imageView: UIImageView = {
  333. let iv = UIImageView()
  334. iv.contentMode = .scaleAspectFill
  335. iv.clipsToBounds = true
  336. iv.image = UIImage(systemName: "doc")
  337. iv.translatesAutoresizingMaskIntoConstraints = false
  338. iv.layer.cornerRadius = 6
  339. iv.layer.masksToBounds = true
  340. return iv
  341. }()
  342. let label: UILabel = {
  343. let lbl = UILabel()
  344. lbl.font = UIFont.systemFont(ofSize: 14)
  345. lbl.numberOfLines = 1
  346. lbl.translatesAutoresizingMaskIntoConstraints = false
  347. return lbl
  348. }()
  349. let dateLabel: UILabel = {
  350. let lbl = UILabel()
  351. lbl.font = UIFont.systemFont(ofSize: 12)
  352. lbl.textColor = .gray
  353. lbl.translatesAutoresizingMaskIntoConstraints = false
  354. return lbl
  355. }()
  356. var isGrid: Bool = true {
  357. didSet {
  358. updateLayout()
  359. }
  360. }
  361. private var gridConstraints: [NSLayoutConstraint] = []
  362. private var listConstraints: [NSLayoutConstraint] = []
  363. override init(frame: CGRect) {
  364. super.init(frame: frame)
  365. contentView.addSubview(imageView)
  366. contentView.addSubview(label)
  367. contentView.addSubview(dateLabel)
  368. setupConstraints()
  369. }
  370. private func setupConstraints() {
  371. // Grid layout constraints
  372. gridConstraints = [
  373. imageView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
  374. imageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
  375. imageView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.6),
  376. imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
  377. label.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 4),
  378. label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 4),
  379. label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -4),
  380. dateLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2),
  381. dateLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 4),
  382. dateLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -4),
  383. dateLabel.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -4)
  384. ]
  385. // List layout constraints
  386. listConstraints = [
  387. imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
  388. imageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
  389. imageView.widthAnchor.constraint(equalToConstant: 60),
  390. imageView.heightAnchor.constraint(equalToConstant: 60),
  391. label.topAnchor.constraint(equalTo: imageView.topAnchor),
  392. label.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10),
  393. label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
  394. dateLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 4),
  395. dateLabel.leadingAnchor.constraint(equalTo: label.leadingAnchor),
  396. dateLabel.trailingAnchor.constraint(equalTo: label.trailingAnchor)
  397. ]
  398. }
  399. private func updateLayout() {
  400. NSLayoutConstraint.deactivate(gridConstraints + listConstraints)
  401. if isGrid {
  402. NSLayoutConstraint.activate(gridConstraints)
  403. label.textAlignment = .center
  404. dateLabel.textAlignment = .center
  405. } else {
  406. NSLayoutConstraint.activate(listConstraints)
  407. label.textAlignment = .left
  408. dateLabel.textAlignment = .left
  409. }
  410. }
  411. required init?(coder: NSCoder) {
  412. super.init(coder: coder)
  413. }
  414. }