diff --git a/ExportOptions.plist b/ExportOptions.plist index 942bad7..1822c54 100644 --- a/ExportOptions.plist +++ b/ExportOptions.plist @@ -3,11 +3,15 @@ "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> - uploadBitcode - - uploadSymbols + method + debugging + signingStyle + automatic + destination + export + stripSwiftSymbols compileBitcode - + diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index b792d5c..a39bfcf 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -1,11 +1,13 @@ //TODO: Implement support for bonus chapters //TODO: Implement Anilist support? //TODO: Properly avoid swallowing of input from UICollectionView used for scrolling -//TODO: Fix UICollectionView sizeForItemAt method performance either by caching or lazy loading +//TODO: Convert between state for normal and scrolling page turn import UIKit -let preloadCount = 3 +let preloadCount = 2 +// With a whopping 2 cores, and 2 threads because no hyperthreading, it makes sense to only have a queue as an extra thread to do work with to allow the main thread for ui. +let queue = DispatchQueue(label: "queue", qos: .utility) enum PageTurn { case next @@ -69,6 +71,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { var scrollPos: CGPoint! var hasSetContentOffset = false var pageCount = 0 + var pagesAvailable = 0 + var sizeList: [CGSize] = [] + var chapterAndPages: [(Int, Int)] = [] var imageView = UIImageView() var mode = PageTurnMode.leftToRight @@ -106,12 +111,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { let imageLoader = ImageLoader.init() - let localStateQueue = DispatchQueue(label: "state.local.queue", qos: .background) - override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .clear + view.backgroundColor = .white setup() } @@ -249,10 +252,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { case .leftToRight: ReadProgress.leftToRight(chapter: self.currentChapter, page: self.currentPage) case .rightToLeft: - ReadProgress.leftToRight(chapter: self.currentChapter, page: self.currentPage) + ReadProgress.rightToLeft(chapter: self.currentChapter, page: self.currentPage) case .scroll: ReadProgress.scroll(scrollingCollectionView.contentOffset) } - localStateQueue.async { + queue.async { do { try JSONEncoder().encode( LocalState( @@ -285,6 +288,17 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { case .scroll(let point): if self.scrollPos == nil { self.scrollPos = point + let centerPoint = CGPoint( + x: scrollingCollectionView.bounds.midX + + scrollingCollectionView.contentOffset.x, + y: scrollingCollectionView.bounds.midY + + scrollingCollectionView.contentOffset.y) + + if let indexPath = scrollingCollectionView.indexPathForItem(at: centerPoint) { + let (chapter, page) = chapterAndPages[indexPath.item] + currentChapter = chapter + currentPage = page + } } self.mode = .scroll } @@ -313,13 +327,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { y: scrollingCollectionView.bounds.midY + scrollingCollectionView.contentOffset.y) if let indexPath = scrollingCollectionView.indexPathForItem(at: centerPoint) { - var index = 0 - var (chapter, page) = (1, 1) - while index < indexPath.item { - (chapter, page) = getChapterAndPageFromTurn( - chapter: chapter, page: page, turn: .next) - index += 1 - } + let (chapter, page) = chapterAndPages[indexPath.item] currentChapter = chapter currentPage = page } @@ -330,7 +338,9 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - if scrollingCollectionView.isHidden == false && mode == .scroll && !hasSetContentOffset { + if scrollingCollectionView.isHidden == false && mode == .scroll + && !hasSetContentOffset + { scrollingCollectionView.setContentOffset(scrollPos, animated: false) hasSetContentOffset = true } @@ -343,7 +353,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if let path = getPathFromComicName(name: name) { currentPath = path metadata = getMetadata(path: path)! - pageCount = metadata.chapters.map { $0.pages }.reduce(0, +) + globalState.comicName = metadata.title saveGlobalState() loadLocalState() @@ -353,14 +363,80 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { rightView.isHidden = false setImages(path: path, metadata: metadata) } else { - leftView.isHidden = true - rightView.isHidden = true - scrollingCollectionView.isHidden = false - scrollingCollectionView.reloadData() + setupScrolling() } } } + func setupScrolling() { + leftView.isHidden = true + rightView.isHidden = true + scrollingCollectionView.isHidden = false + pagesAvailable = countFiles() + var index = 0 + var (chapter, page) = (1, 1) + chapterAndPages = Array(repeating: (0, 0), count: pagesAvailable + 1) + chapterAndPages[index] = (chapter, page) + while index < pagesAvailable { + index += 1 + (chapter, page) = getChapterAndPageFromTurn( + chapter: chapter, page: page, turn: .next) + chapterAndPages[index] = (chapter, page) + } + + sizeList = Array(repeating: CGSize(), count: pagesAvailable) + let binPath = currentPath.appendingPathComponent("size.bin") + let decoder = PropertyListDecoder() + do { + let data = try Data(contentsOf: binPath) + sizeList = try decoder.decode([CGSize].self, from: data) + } catch { + print("Failed to read data:", error) + } + + scrollingCollectionView.reloadData() + DispatchQueue.main.async { + let encoder = PropertyListEncoder() + encoder.outputFormat = .binary + do { + let data = try encoder.encode(self.sizeList) + try data.write(to: binPath) + } catch { + print("Encoding or writing failed:", error) + } + } + } + + func countFiles() -> Int { + var count = 0 + if let enumerator = fileManager.enumerator( + at: currentPath, includingPropertiesForKeys: [.isDirectoryKey], + options: [.skipsHiddenFiles]) + { + for case let dir as URL in enumerator { + do { + if let enumerator = fileManager.enumerator( + at: dir, includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles]) + { + for case let file as URL in enumerator { + let resourceValues = try file.resourceValues(forKeys: [ + .isRegularFileKey + ]) + if resourceValues.isRegularFile == true { + count += 1 + } + } + } + + } catch { + print("Error reading file attributes for \(dir):", error) + } + } + } + return count + } + func setupScrollingCollectionView() { let layout = UICollectionViewFlowLayout() layout.minimumInteritemSpacing = 0 @@ -368,7 +444,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { scrollingCollectionView = UICollectionView( frame: view.bounds, collectionViewLayout: layout) scrollingCollectionView.translatesAutoresizingMaskIntoConstraints = false - scrollingCollectionView.isUserInteractionEnabled = true scrollingCollectionView.dataSource = self scrollingCollectionView.delegate = self scrollingCollectionView.backgroundColor = .white @@ -383,7 +458,6 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { scrollingCollectionView.trailingAnchor.constraint(equalTo: readerView.trailingAnchor), scrollingCollectionView.heightAnchor.constraint(equalTo: readerView.heightAnchor), ]) - } func setupTapZones() { @@ -639,16 +713,28 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { @objc func handlePageTurnOption(_ sender: UIButton) { if let title = sender.currentTitle { + let prev = mode switch title.lowercased() { case "left to right": mode = .leftToRight case "right to left": mode = .rightToLeft - case "scroll": - if mode != .scroll { - scrollingCollectionView.reloadData() - } - mode = .scroll + case "scroll": mode = .scroll default: break } + if prev == mode { return } + if mode == .scroll { + setupScrolling() + } else { + imageLoader.loadImage( + at: getImagePath( + chapter: currentChapter, + volume: Int(metadata.chapters[currentChapter - 1].volume), + page: currentPage, + path: currentPath) + ) { + [weak self] image in + self?.imageView.image = image + } + } } scrollingCollectionView.isHidden = mode != .scroll leftView.isHidden = mode == .scroll @@ -824,7 +910,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate { if let chapterTitle = metadata.chapters[currentChapter - 1].title { text = - text + "\(chapterTitle)" + text + "\(chapterTitle)\n" } text = text + """ @@ -1002,7 +1088,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl if collectionView == comicCollectionView { return comics.count } else { - return pageCount + return pagesAvailable } } @@ -1024,20 +1110,26 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl if metadata == nil { return cell } - var index = 0 - var (chapter, page) = (1, 1) - while index < indexPath.item { - (chapter, page) = getChapterAndPageFromTurn( - chapter: chapter, page: page, turn: .next) - index += 1 - } + var (chapter, page) = chapterAndPages[indexPath.item] if let url = getImagePath( chapter: chapter, volume: Int(metadata.chapters[chapter - 1].volume), page: page, path: currentPath) { - // cell.imageView.image = UIImage(contentsOfFile: url.path) imageLoader.loadImage(at: url) { image in cell.imageView.image = image } + for _ in 0...preloadCount { + (chapter, page) = getChapterAndPageFromTurn( + chapter: chapter, page: page, turn: .next) + if let url = getImagePath( + chapter: chapter, + volume: Int(metadata.chapters[chapter - 1].volume), + page: page, path: currentPath) + { + imageLoader.preloadImage(at: url) + } else { + break + } + } } return cell } else { @@ -1066,13 +1158,13 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl if metadata == nil { return CGSize() } - var index = 0 - var (chapter, page) = (1, 1) - while index < indexPath.item { - (chapter, page) = getChapterAndPageFromTurn( - chapter: chapter, page: page, turn: .next) - index += 1 + if sizeList[indexPath.item] != CGSize(width: 0, height: 0) { + return CGSize( + width: readerView.bounds.width, + height: sizeList[indexPath.item].height * readerView.bounds.width + / sizeList[indexPath.item].width) } + let (chapter, page) = chapterAndPages[indexPath.item] if let imagePath = getImagePath( chapter: chapter, volume: Int(metadata.chapters[chapter - 1].volume), @@ -1080,11 +1172,15 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl let imageSource = CGImageSourceCreateWithURL(imagePath as CFURL, nil), let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any], - let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat + let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat, + let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat { - return CGSize(width: readerView.bounds.width, height: height) + sizeList[indexPath.item] = CGSize(width: width, height: height) + return CGSize( + width: readerView.bounds.width, height: height * readerView.bounds.width / width + ) } - return CGSize(width: readerView.bounds.width, height: readerView.bounds.height) + return CGSize() } else { assert(false) } @@ -1152,7 +1248,6 @@ class ComicImageCell: UICollectionViewCell { class ImageLoader { private let cache = NSCache() private var loadingTasks: [String: [((UIImage?) -> Void)]] = [:] - private let queue = DispatchQueue(label: "image.loading.queue", qos: .userInitiated) init() { cache.totalCostLimit = 50 * 1024 * 1024