import UIKit import VisionKit import ImagePicker 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! @IBOutlet weak var searchForFilesView: SearchFilesView! private var viewModels: [File] = [] private var searchedViewModel: [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? private var isSearching: Bool = false private var pageViewControllers: [UIViewController] = [] @IBOutlet weak var allFolderView_height: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "File Manager" let settingsButton = UIBarButtonItem(image: UIImage(named: "settings"), style: .done, target: self, action: #selector(openSettings)) navigationItem.rightBarButtonItem = settingsButton tableView.register(UINib(nibName: "DocumentsTableViewCell", bundle: nil), forCellReuseIdentifier: "DocumentsTableViewCell") tableView.tableFooterView = UIView() tableView.delegate = self tableView.dataSource = self tableView.reloadData() let savedFolders = AppConfigurator().getFolders() for item in savedFolders { if item.isSelected == true { selectedFolder = item } } if let folder = selectedFolder { AllFolderView?.set(selectedFolder: folder, delegate: self) } searchForFilesView.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) NotificationCenter.default.addObserver(self, selector: #selector(rightButtonTapped(_:)), name: NSNotification.Name.rightButtonTapped, 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 self.searchedViewModel = viewModels AllFolderView?.set(selectedFolder: folder, delegate: self) tableView.reloadData() } } @objc private func openSettings() { let settings = SettingViewController() self.navigationController?.pushViewController(settings, animated: false) } // 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 isSearching ? searchedViewModel.count :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 = isSearching ? searchedViewModel[indexPath.row ] : 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 = isSearching ? searchedViewModel[indexPath.row] : 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) { openCamera() } @objc private func rightButtonTapped(_ notification: NSNotification) { openGallery() } private func openCamera() { let scannerOptions = ImageScannerOptions(scanMultipleItems: true, allowAutoScan: false, allowTapToFocus: false, defaultColorRenderOption:.color) let scannerViewController = ScannerViewController(scanSession: nil, options: scannerOptions) scannerViewController.delegate = self scannerViewController.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(scannerViewController, animated: true) // // scannerViewController.modalPresentationStyle = .overFullScreen // self.present(scannerViewController, animated: true) } private func openGallery() { let imagePickerController = ImagePickerController() imagePickerController.expandGalleryView() imagePickerController.delegate = self imagePickerController.modalPresentationStyle = .fullScreen present(imagePickerController, animated: true, completion: nil) } private func renameFile(at indexPath: IndexPath) { let viewModel = isSearching ? searchedViewModel[indexPath.row] : 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 = isSearching ? searchedViewModel[indexPath.row] : 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) if isSearching { self.searchedViewModel.remove(at: indexPath.row) } else { 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() self.tabBarController?.selectedIndex = 0 } } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) { [weak self] in self?.tabBarController?.selectedIndex = 0 } } } 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: ScannerViewControllerDelegate { func scannerViewController(_ scannerViewController: ScannerViewController, reviewItems inSession: MultiPageScanSession) { let multipageScanViewController = MultiPageScanSessionViewController(scanSession: inSession) multipageScanViewController.delegate = self self.navigationController?.pushViewController(multipageScanViewController, animated: true) } func scannerViewController(_ scannerViewController: ScannerViewController, didFail withError: Error) { scannerViewController.dismiss(animated: true) } func scannerViewControllerDidCancel(_ scannerViewController: ScannerViewController) { scannerViewController.dismiss(animated: true) } } extension DocumentsTableViewController: AllFolderTableViewCellDelegate { func addFolderTapped() { alert(confirm: "Add Folder", destructive: "Cancel", message: "Add Folder?"){ folderName in let newFolder = AppConfigurator.Folder(name: folderName, savedName: folderName.replacingOccurrences(of: " ", with: "_"), isSelected: false) var currentFolders = AppConfigurator().getFolders() currentFolders.append(newFolder) if let encoded = try? JSONEncoder().encode(currentFolders) { UserDefaults.standard.set(encoded, forKey: "folders") self.AllFolderView?.refresh() self.dismiss(animated: true) } } } func cellTapped(folder: AppConfigurator.Folder) { selectedFolder = folder fetchViewModels() } } extension DocumentsTableViewController: SearchFilesViewDelegate { func searchfor(text: String) { if text.count == 0 { isSearching = false UIView.animate(withDuration: 0.5) { self.allFolderView_height.constant = 240 self.AllFolderView.isHidden = false } } else { isSearching = true self.searchedViewModel = self.viewModels.filter { $0.displayName.lowercased().contains(text.lowercased()) } } tableView.reloadData() } } extension DocumentsTableViewController: ImagePickerDelegate { func wrapperDidPress(_ imagePicker: ImagePicker.ImagePickerController, images: [UIImage]) { guard !images.isEmpty else { return } // Create a view controller for each image let viewControllers = images.map { image -> UIViewController in let viewController = UIViewController() viewController.view.backgroundColor = .white let imageView = UIImageView(image: image) imageView.contentMode = .scaleAspectFit imageView.translatesAutoresizingMaskIntoConstraints = false viewController.view.addSubview(imageView) NSLayoutConstraint.activate([ imageView.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor), imageView.topAnchor.constraint(equalTo: viewController.view.topAnchor), imageView.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor) ]) return viewController } self.pageViewControllers = viewControllers // Create a page view controller to allow the user to scroll through the images let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) pageViewController.dataSource = self pageViewController.setViewControllers([viewControllers[0]], direction: .forward, animated: false, completion: nil) // Present the page view controller imagePicker.present(pageViewController, animated: true, completion: nil) } func doneButtonDidPress(_ imagePicker: ImagePicker.ImagePickerController, images: [UIImage]) { imagePicker.dismiss(animated: true) { [weak self] in guard let self = self else { return } if let folder = selectedFolder { PDFManager.createMultiImagesPDFDocument(from: images, localFileManager: self.localFileManager, folder: folder) self.fetchViewModels() self.tabBarController?.selectedIndex = 0 } } } func cancelButtonDidPress(_ imagePicker: ImagePicker.ImagePickerController) { imagePicker.dismiss(animated: true) { [weak self] in self?.tabBarController?.selectedIndex = 0 } } } extension DocumentsTableViewController: MultiPageScanSessionViewControllerDelegate { public func multiPageScanSessionViewController(_ multiPageScanSessionViewController: MultiPageScanSessionViewController, finished 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) { self.fetchViewModels() self.navigationController?.popViewController(animated: true) } } } } extension DocumentsTableViewController: UIPageViewControllerDataSource { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = pageViewControllers.firstIndex(of: viewController), index > 0 else { return nil } return pageViewControllers[index - 1] } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let index = pageViewControllers.firstIndex(of: viewController), index < pageViewControllers.count - 1 else { return nil } return pageViewControllers[index + 1] } }