Select Git revision
DocumentsTableViewController.swift
DocumentsTableViewController.swift 21.33 KiB
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: false)
}
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]
}
}