import UIKit import VisionKit final class DocumentsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { struct Constants { static let reuseIdentifier = String(describing: DocumentsTableViewController.self) static let storyboardName = "Documents" static let rowHeight: CGFloat = 112 static let cellReuseIdentifier = String(describing: DocumentsTableViewCell.self) static let topBottom: CGFloat = 120 static let margin: CGFloat = 64 } static func buildViewController() -> DocumentsTableViewController { let controller = UIStoryboard(name: Constants.storyboardName, bundle: .main).instantiateViewController(withIdentifier: Constants.reuseIdentifier) return controller as! DocumentsTableViewController } @IBOutlet weak var tableView: UITableView! @IBOutlet weak var AllFolderView: AllFolderTableViewCell! @IBOutlet weak var fixedTableSheet: UIView! private var viewModels: [File] = [] private var localFileManager: LocalFileManager? private var sortType: SortyFileType = .date // private let noDocumentsimageView = UIImageView(image: UIImage(named: "box")) private var renameAlertController: UIAlertController? private var renameFileName: String? private var selectedFolder: AppConfigurator.Folder? override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "File Manager" tableView.tableFooterView = UIView() tableView.delegate = self tableView.dataSource = self tableView.register(UINib(nibName: "DocumentsTableViewCell", bundle: nil), forCellReuseIdentifier: "DocumentsTableViewCell") let savedFolders = AppConfigurator().getFolders() for item in savedFolders { if item.isSelected == true { selectedFolder = item } } if let folder = selectedFolder { AllFolderView?.set(selectedFolder: folder, delegate: self) } localFileManager = LocalFileManager() tabBarController?.delegate = UIApplication.shared.delegate as? UITabBarControllerDelegate NotificationCenter.default.addObserver(self, selector: #selector(middleButtonTapped(_:)), name: NSNotification.Name.MiddleButtonTapped, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(fetchViewModels), name: NSNotification.Name.ReloadViewModels, object: nil) if UserDefaults.standard.startsAppWithCamera { openCamera() } fixedTableSheet.layer.cornerRadius = 30 fixedTableSheet.layer.shadowColor = UIColor.gray.cgColor fixedTableSheet.layer.shadowOffset = CGSize(width: 0, height: 2) // Shadow position fixedTableSheet.layer.shadowOpacity = 0.7 // Shadow opacity fixedTableSheet.layer.shadowRadius = 4.0 fixedTableSheet.backgroundColor = .white // or any non-clear color fixedTableSheet.clipsToBounds = false fetchViewModels() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func fetchViewModels() { if let folder = selectedFolder { guard let directoryURL = localFileManager?.getFolderUrl(folder: folder), let viewModels = localFileManager?.filesForDirectory(directoryURL, sortType: sortType) else { return } self.viewModels = viewModels AllFolderView?.set(selectedFolder: folder, delegate: self) tableView.reloadData() } } // MARK: - Table view data source func numberOfSections(in tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return viewModels.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellReuseIdentifier, for: indexPath) as? DocumentsTableViewCell else { return UITableViewCell() } // Configure the cell... let viewModel = viewModels[indexPath.row] cell.configure(with: viewModel, image: localFileManager?.getThumbnail(for: viewModel.fileURL)) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return Constants.rowHeight } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 20 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let viewModel = viewModels[indexPath.row] let controller = DocumentPreviewViewController.buildViewController() controller.file = viewModel controller.hidesBottomBarWhenPushed = true navigationController?.pushViewController(controller, animated: true) } func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { return .none } func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { // Trash action let trash = UIContextualAction(style: .destructive, title: "Delete".localized) { [weak self] (_, _, completionHandler) in self?.deleteFile(at: indexPath) completionHandler(true) } trash.backgroundColor = .systemRed trash.image = UIImage(systemName: "trash")?.tint(with: .white) // Rename action let rename = UIContextualAction(style: .normal, title: "Rename".localized) { [weak self] (_, _, completionHandler) in self?.renameFile(at: indexPath) completionHandler(true) } rename.backgroundColor = UIColor.black.withAlphaComponent(0.5) rename.image = UIImage(systemName: "square.and.pencil")?.tint(with: .white) let configuration = UISwipeActionsConfiguration(actions: [trash, rename]) configuration.performsFirstActionWithFullSwipe = true return configuration } @objc private func middleButtonTapped(_ notification: NSNotification) { let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let cameraAction = UIAlertAction(title: "Camera".localized, style: .default, handler: { action in self.openCamera() }) let galleryAction = UIAlertAction(title: "Gallery".localized, style: .default, handler: { action in self.openGallery() }) alertController.addAction(cameraAction) cameraAction.setValue(UIImage(systemName: "camera"), forKey: "image") cameraAction.setValue(CATextLayerAlignmentMode.left, forKey: "titleTextAlignment") alertController.addAction(galleryAction) galleryAction.setValue(UIImage(systemName: "photo"), forKey: "image") galleryAction.setValue(CATextLayerAlignmentMode.left, forKey: "titleTextAlignment") alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) alertController.popoverPresentationController?.sourceView = notification.object as? UIButton present(alertController, animated: true) } private func openCamera() { let scannerOptions = ImageScannerOptions(scanMultipleItems: true, allowAutoScan: false, allowTapToFocus: false, defaultColorRenderOption:.color) let scannerViewController = ImageScannerController(options: scannerOptions) scannerViewController.imageScannerDelegate = self present(scannerViewController, animated: true) } private func openGallery() { guard UIImagePickerController.isSourceTypeAvailable(.savedPhotosAlbum) else { return } let imagePickerController = UIImagePickerController() imagePickerController.delegate = self imagePickerController.sourceType = UIImagePickerController.SourceType.savedPhotosAlbum imagePickerController.allowsEditing = UserDefaults.standard.isPhotoEditigOn present(imagePickerController, animated: true, completion: nil) } private func renameFile(at indexPath: IndexPath) { let viewModel = viewModels[indexPath.row] renameAlertController = UIAlertController(title: "Rename document".localized, message: nil, preferredStyle: .alert) renameAlertController!.addTextField { [weak self] textField in guard let self = self else { return } textField.placeholder = viewModel.displayName textField.text = viewModel.displayName textField.clearButtonMode = .always self.renameFileName = viewModel.displayName textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) } let continueAction = UIAlertAction(title: "Rename".localized, style: .default) { [weak self] _ in guard let self = self else { return } guard let textFields = self.renameAlertController?.textFields else { return } if let newName = textFields.first?.text, newName != viewModel.displayName { do { try self.localFileManager?.renameFile(at: viewModel.fileURL, withName: newName) self.fetchViewModels() } catch { return } } } renameAlertController!.addAction(continueAction) renameAlertController!.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) renameAlertController?.actions.first?.isEnabled = false present(renameAlertController!, animated: true) } private func deleteFile(at indexPath: IndexPath) { let viewModel = viewModels[indexPath.row] if UserDefaults.standard.askOnSwipeDelete { let alertController = UIAlertController(title: Bundle.appName, message: "Are you sure you want to delete this document?".localized, preferredStyle: .alert) let okAction = UIAlertAction(title: "Yes, delete!".localized, style: .destructive, handler: { [weak self] action in guard let self = self else { return } self.delete(file: viewModel.fileURL, at: indexPath) }) alertController.addAction(okAction) alertController.addAction(UIAlertAction(title: "Cancel".localized, style: .cancel, handler: nil)) alertController.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) present(alertController, animated: true) } else { delete(file: viewModel.fileURL, at: indexPath) } } private func delete(file fileURL: URL, at indexPath: IndexPath) { do { try self.localFileManager?.filesManager.removeItem(at: fileURL) try self.localFileManager?.removeThumbnail(for: fileURL) self.viewModels.remove(at: indexPath.row) self.tableView.deleteRows(at: [indexPath], with: .fade) } catch { return } } } /// Tells the delegate that the user picked a still image. extension DocumentsTableViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true) { [weak self] in guard let self = self else { return } guard let image = info[UserDefaults.standard.isPhotoEditigOn ? UIImagePickerController.InfoKey.editedImage : UIImagePickerController.InfoKey.originalImage] as? UIImage else { return } if let folder = selectedFolder { PDFManager.createPDFDocument(from: image, localFileManager: self.localFileManager, folder: folder) self.fetchViewModels() } } } } extension DocumentsTableViewController: ImageScannerControllerDelegate { func imageScannerController(_ scanner: ImageScannerController, didFinishWithSession session: MultiPageScanSession) { var images = [URL]() for index in 0..<session.scannedItems.count { if let url = session.scannedItems[index].renderedImage { images.append(url) } } print("images: \(images)") if let folder = selectedFolder { PDFManager.createMultiPDFPage(from: images, localFileManager: localFileManager, folder: folder) { scanner.dismiss(animated: true) { self.fetchViewModels() } } } } func imageScannerControllerDidCancel(_ scanner: ImageScannerController) { scanner.dismiss(animated: true) } func imageScannerController(_ scanner: ImageScannerController, didFailWithError error: Error) { scanner.dismiss(animated: true) } @objc func textFieldDidChange(_ textField: UITextField) { guard let text = textField.text, let renameFileName = renameFileName, renameFileName != text, !text.trimmingCharacters(in: .whitespaces).isEmpty else { renameAlertController?.actions.first?.isEnabled = false return } renameAlertController?.actions.first?.isEnabled = true } } extension DocumentsTableViewController: AllFolderTableViewCellDelegate { func cellTapped(folder: AppConfigurator.Folder) { selectedFolder = folder fetchViewModels() } }