This commit is contained in:
2026-02-27 02:32:19 +01:00
parent 86b1752e91
commit aa5ba0f79e
3 changed files with 150 additions and 122 deletions

View File

@@ -192,6 +192,7 @@
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -245,6 +246,7 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -2,12 +2,11 @@ import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = ViewController()

View File

@@ -1,8 +1,8 @@
//TODO: Anilist support?
//TODO: Properly avoid swallowing of input from UICollectionView used for scrolling
//TODO: Convert between state for normal and scrolling page turn
//TODO: Support reading with scrolling and landscape mode
//FIXME: Update comicCollectionView when switching between landscape and portrait mode
// TODO: Anilist support?
// TODO: Properly avoid swallowing of input from UICollectionView used for scrolling
// TODO: Convert between state for normal and scrolling page turn
// TODO: Support reading with scrolling and landscape mode
// FIXME: Update comicCollectionView when switching between landscape and portrait mode
import Foundation
import UIKit
@@ -103,8 +103,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var imageView = UIImageView()
var mode = PageTurnMode.leftToRight
var metadata: Metadata!
var currentPage: Int! = nil
var progress = ProgressIndices.init(v: 0, c: 0, i: 0)
var currentPage: Int!
var progress = ProgressIndices(v: 0, c: 0, i: 0)
var currentPath: URL!
var changedOrientation = false
@@ -136,7 +136,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var comics: [Comic] = []
@IBOutlet var comicCollectionView: UICollectionView!
let imageLoader = ImageLoader.init()
let imageLoader = ImageLoader()
override func viewDidLoad() {
super.viewDidLoad()
@@ -198,7 +198,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
comicCollectionView.delegate = self
comicCollectionView.backgroundColor = .white
comicCollectionView.register(
ComicImageCell.self, forCellWithReuseIdentifier: "ComicImageCell")
ComicImageCell.self, forCellWithReuseIdentifier: "ComicImageCell"
)
homeView.addSubview(comicCollectionView)
}
@@ -232,13 +233,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
metadata = try JSONDecoder().decode(
Metadata.self,
from:
Data(
try String(
contentsOfFile: dir.appendingPathComponent(
"metadata.json"
)
.path
).utf8)
Data(
String(
contentsOfFile: dir.appendingPathComponent(
"metadata.json"
)
.path
).utf8)
)
loadLocalState()
comics.append(
@@ -304,11 +305,11 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return
}
var newMetadata = Metadata.init(
var newMetadata = Metadata(
title: "",
original_language: "",
last_volume: MetaValue.init(main: 0, bonus: nil),
last_chapter: MetaValue.init(main: 0, bonus: nil),
last_volume: MetaValue(main: 0, bonus: nil),
last_chapter: MetaValue(main: 0, bonus: nil),
chapter_count: 0,
publication_demographic: "",
status: "",
@@ -351,19 +352,23 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
newMetadata.chapter_count += 1
assert(volume > currentVolume)
newMetadata.volumes.append(
VolumeMetadata.init(
volume: MetaValue.init(main: volume, bonus: nil), name: nil,
VolumeMetadata(
volume: MetaValue(main: volume, bonus: nil), name: nil,
chapters: [
ChapterMetadata.init(
chapter: MetaValue.init(
main: chapter.0, bonus: chapter.1),
ChapterMetadata(
chapter: MetaValue(
main: chapter.0, bonus: chapter.1
),
name: "",
images: [
ImageMetadata.init(
ImageMetadata(
doublePage: doublePage, fileName: fileName,
firstPage: page)
])
]))
firstPage: page
),
]
),
]
))
} else if chapter != currentChapter {
newMetadata.chapter_count += 1
if chapter.0 == currentChapter.0 {
@@ -372,37 +377,43 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
assert(chapter.0 == currentChapter.0 + 1)
}
newMetadata.volumes[newMetadata.volumes.count - 1].chapters.append(
ChapterMetadata.init(
chapter: MetaValue.init(main: chapter.0, bonus: chapter.1),
ChapterMetadata(
chapter: MetaValue(main: chapter.0, bonus: chapter.1),
name: "",
images: [
ImageMetadata.init(
ImageMetadata(
doublePage: doublePage, fileName: fileName,
firstPage: page)
]))
firstPage: page
),
]
))
} else {
newMetadata.volumes[newMetadata.volumes.count - 1].chapters[
newMetadata.volumes[newMetadata.volumes.count - 1].chapters.count - 1
].images.append(
ImageMetadata.init(
doublePage: doublePage, fileName: fileName, firstPage: page)
ImageMetadata(
doublePage: doublePage, fileName: fileName, firstPage: page
)
)
}
} else {
newMetadata.chapter_count += 1
newMetadata.volumes.append(
VolumeMetadata.init(
volume: MetaValue.init(main: volume, bonus: nil), name: nil,
VolumeMetadata(
volume: MetaValue(main: volume, bonus: nil), name: nil,
chapters: [
ChapterMetadata.init(
chapter: MetaValue.init(main: chapter.0, bonus: chapter.1),
ChapterMetadata(
chapter: MetaValue(main: chapter.0, bonus: chapter.1),
name: "",
images: [
ImageMetadata.init(
ImageMetadata(
doublePage: doublePage, fileName: fileName,
firstPage: page)
])
]))
firstPage: page
),
]
),
]
))
}
currentVolume = volume
currentChapter = chapter
@@ -434,20 +445,23 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
if screenSize.width > screenSize.height {
scrollOffset.y *= (screenSize.height / screenSize.width)
}
var newProgress: ReadProgress =
ReadProgress.leftToRight(
volumeIndex: progress.v, chapterIndex: progress.c,
imageIndex: progress.i)
var newProgress =
ReadProgress.leftToRight(
volumeIndex: progress.v, chapterIndex: progress.c,
imageIndex: progress.i
)
switch mode {
case .leftToRight:
newProgress = ReadProgress.leftToRight(
volumeIndex: progress.v, chapterIndex: progress.c,
imageIndex: progress.i)
case .rightToLeft: newProgress = ReadProgress.rightToLeft(
volumeIndex: progress.v, chapterIndex: progress.c,
imageIndex: progress.i)
case .scroll: newProgress = ReadProgress.scroll(scrollOffset)
case .leftToRight:
newProgress = ReadProgress.leftToRight(
volumeIndex: progress.v, chapterIndex: progress.c,
imageIndex: progress.i
)
case .rightToLeft: newProgress = ReadProgress.rightToLeft(
volumeIndex: progress.v, chapterIndex: progress.c,
imageIndex: progress.i
)
case .scroll: newProgress = ReadProgress.scroll(scrollOffset)
}
queue.async {
do {
@@ -465,23 +479,23 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
func loadLocalState() {
do {
let json = Data(
try String(
let json = try Data(
String(
contentsOfFile: currentPath.appendingPathComponent("state.json").path
).utf8)
let local = try JSONDecoder().decode(LocalState.self, from: json)
switch local.progress {
case .leftToRight(let volumeIndex, let chapterIndex, let imageIndex):
case let .leftToRight(volumeIndex, chapterIndex, imageIndex):
progress.v = volumeIndex
progress.c = chapterIndex
progress.i = imageIndex
mode = .leftToRight
case .rightToLeft(let volumeIndex, let chapterIndex, let imageIndex):
case let .rightToLeft(volumeIndex, chapterIndex, imageIndex):
progress.v = volumeIndex
progress.c = chapterIndex
progress.i = imageIndex
mode = .rightToLeft
case .scroll(let point):
case let .scroll(point):
if scrollPos == nil {
scrollPos = point
let screenSize = UIScreen.main.bounds.size
@@ -491,13 +505,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
}
if let indexPath = scrollingCollectionView.indexPathForItem(at: scrollOffset) {
var theProgress = ProgressIndices(v: 0, c: 0, i: 0)
for _ in 0...indexPath.item {
for _ in 0 ... indexPath.item {
theProgress = getProgressIndicesFromTurn(
turn: .next, progress: theProgress)
turn: .next, progress: theProgress
)
}
progress = theProgress
}
}
mode = .scroll
}
@@ -521,11 +535,12 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
setupTapZones()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
func scrollViewDidScroll(_: UIScrollView) {
if scrollingCollectionView.isHidden { return }
let centerPoint = CGPoint(
x: scrollingCollectionView.bounds.midX + scrollingCollectionView.contentOffset.x,
y: scrollingCollectionView.bounds.midY + scrollingCollectionView.contentOffset.y)
y: scrollingCollectionView.bounds.midY + scrollingCollectionView.contentOffset.y
)
if let indexPath = scrollingCollectionView.indexPathForItem(at: centerPoint) {
let (chapter, page) = chapterAndPages[indexPath.item]
@@ -544,7 +559,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
{
return
}
if !hasSetContentOffset && scrollPos != nil {
if !hasSetContentOffset, scrollPos != nil {
let screenSize = UIScreen.main.bounds.size
var offset = scrollPos!
if screenSize.width > screenSize.height {
@@ -597,17 +612,17 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
var count = 0
if let enumerator = fileManager.enumerator(
at: currentPath, includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsHiddenFiles])
{
options: [.skipsHiddenFiles]
) {
for case let dir as URL in enumerator {
do {
if let enumerator = fileManager.enumerator(
at: dir, includingPropertiesForKeys: [.isRegularFileKey],
options: [.skipsHiddenFiles])
{
options: [.skipsHiddenFiles]
) {
for case let file as URL in enumerator {
let resourceValues = try file.resourceValues(forKeys: [
.isRegularFileKey
.isRegularFileKey,
])
if resourceValues.isRegularFile == true {
count += 1
@@ -628,14 +643,16 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
scrollingCollectionView = UICollectionView(
frame: view.bounds, collectionViewLayout: layout)
frame: view.bounds, collectionViewLayout: layout
)
scrollingCollectionView.translatesAutoresizingMaskIntoConstraints = false
scrollingCollectionView.dataSource = self
scrollingCollectionView.delegate = self
scrollingCollectionView.backgroundColor = .white
scrollingCollectionView.isHidden = true
scrollingCollectionView.register(
ScrollingImageCell.self, forCellWithReuseIdentifier: "ScrollingImageCell")
ScrollingImageCell.self, forCellWithReuseIdentifier: "ScrollingImageCell"
)
readerView.addSubview(scrollingCollectionView)
NSLayoutConstraint.activate([
@@ -949,8 +966,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
}
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer
) -> Bool {
return true
}
@@ -965,13 +982,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
func scrollViewDidEndDecelerating(_: UIScrollView) {
if scrollingCollectionView.isHidden { return }
updateInfo()
saveLocalState()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
func scrollViewDidEndDragging(_: UIScrollView, willDecelerate decelerate: Bool) {
if scrollingCollectionView.isHidden { return }
if !decelerate {
updateInfo()
@@ -1044,8 +1061,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
let scaling = UIView.ContentMode.scaleAspectFit
var newProgress = progress
newProgress = getProgressIndicesFromTurn(turn: turn, progress: newProgress)
if newProgress.v == progress.v && newProgress.c == progress.c && newProgress.i == progress.i
{
if newProgress.v == progress.v, newProgress.c == progress.c, newProgress.i == progress.i {
return
}
progress = newProgress
@@ -1058,7 +1074,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return
}
for _ in 0...preloadCount {
for _ in 0 ... preloadCount {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
if let path = getImagePath(progress: newProgress) {
imageLoader.preloadImage(at: path, scaling: scaling)
@@ -1106,7 +1122,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
metadata.volumes[progress.v].chapters[lastChapterIndex].images.count - 1
let lastImage = metadata.volumes[progress.v].chapters[lastChapterIndex].images[
lastImageIndex]
lastImageIndex
]
var lastPageString = ""
if lastImage.doublePage {
lastPageString = String(lastImage.firstPage + 1)
@@ -1150,37 +1167,39 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
switch turn {
case .next:
if metadata.volumes[v].chapters[c].images.count > i + 1 {
return ProgressIndices.init(v: v, c: c, i: i + 1)
return ProgressIndices(v: v, c: c, i: i + 1)
}
if metadata.volumes[v].chapters.count > c + 1 {
return ProgressIndices.init(v: v, c: c + 1, i: 0)
return ProgressIndices(v: v, c: c + 1, i: 0)
}
if metadata.volumes.count > v + 1 {
return ProgressIndices.init(v: v + 1, c: 0, i: 0)
return ProgressIndices(v: v + 1, c: 0, i: 0)
}
case .previous:
if i > 0 {
return ProgressIndices.init(v: v, c: c, i: i - 1)
return ProgressIndices(v: v, c: c, i: i - 1)
}
if c > 0 {
return ProgressIndices.init(
v: v, c: c - 1, i: metadata.volumes[v].chapters[c - 1].images.count - 1)
return ProgressIndices(
v: v, c: c - 1, i: metadata.volumes[v].chapters[c - 1].images.count - 1
)
}
if v > 0 {
return ProgressIndices.init(
return ProgressIndices(
v: v - 1, c: metadata.volumes[v - 1].chapters.count - 1,
i: metadata.volumes[v - 1].chapters[
metadata.volumes[v - 1].chapters.count - 1
].images.count - 1)
].images.count - 1
)
}
}
return ProgressIndices.init(v: v, c: c, i: i)
return ProgressIndices(v: v, c: c, i: i)
}
func getMetadata(path: URL) -> Metadata? {
do {
let json = Data(
try String(contentsOfFile: path.appendingPathComponent("metadata.json").path)
let json = try Data(
String(contentsOfFile: path.appendingPathComponent("metadata.json").path)
.utf8)
let metadata = try JSONDecoder().decode(Metadata.self, from: json)
return metadata
@@ -1193,14 +1212,14 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
return nil
}
func setImages(path: URL) {
func setImages(path _: URL) {
let scaling: UIView.ContentMode!
if mode == .scroll {
scaling = UIView.ContentMode.scaleAspectFill
} else { scaling = UIView.ContentMode.scaleAspectFit }
if mode == .scroll {
scaling = UIView.ContentMode.scaleAspectFill
} else { scaling = UIView.ContentMode.scaleAspectFit }
imageLoader.loadImage(
at:
getImagePath(progress: progress),
getImagePath(progress: progress),
scaling: scaling
) { [weak self] image in
self?.imageView.image = image
@@ -1208,7 +1227,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
}
var newProgress = progress
for _ in 0...preloadCount {
for _ in 0 ... preloadCount {
newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress)
imageLoader.preloadImage(at: getImagePath(progress: newProgress), scaling: scaling)
}
@@ -1245,8 +1264,8 @@ func getGlobalState() -> GlobalState {
let fileManager = FileManager.default
if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
do {
let json = Data(
try String(
let json = try Data(
String(
contentsOfFile: documentsURL.appendingPathComponent("state.json").path
).utf8)
return try JSONDecoder().decode(GlobalState.self, from: json)
@@ -1268,9 +1287,8 @@ func getDocumentsURL() -> URL? {
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(
_ collectionView: UICollectionView, numberOfItemsInSection section: Int
_ collectionView: UICollectionView, numberOfItemsInSection _: Int
)
-> Int
{
@@ -1287,7 +1305,8 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
if collectionView == comicCollectionView {
let cell =
collectionView.dequeueReusableCell(
withReuseIdentifier: "ComicImageCell", for: indexPath)
withReuseIdentifier: "ComicImageCell", for: indexPath
)
as! ComicImageCell
cell.imageView.image = comics[indexPath.item].cover
return cell
@@ -1295,7 +1314,8 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
// let scaling = UIView.ContentMode.scaleAspectFill
let cell =
collectionView.dequeueReusableCell(
withReuseIdentifier: "ScrollingImageCell", for: indexPath)
withReuseIdentifier: "ScrollingImageCell", for: indexPath
)
as! ScrollingImageCell
if metadata == nil {
return cell
@@ -1303,20 +1323,21 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
// TODO: Fix scrolling again
return cell
} else {
assert(false)
assertionFailure()
}
// Xcode profiling sucks:
return
collectionView.dequeueReusableCell(
withReuseIdentifier: "ScrollingImageCell", for: indexPath)
withReuseIdentifier: "ScrollingImageCell", for: indexPath
)
as! ScrollingImageCell
}
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath
layout _: UICollectionViewLayout,
sizeForItemAt _: IndexPath
) -> CGSize {
if collectionView == comicCollectionView {
let spacing: CGFloat = 8
@@ -1331,7 +1352,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
// TODO: Fix scrolling again
return CGSize()
} else {
assert(false)
assertionFailure()
}
// Xcode profiling sucks:
return CGSize()
@@ -1367,7 +1388,8 @@ class ScrollingImageCell: UICollectionViewCell {
])
}
required init?(coder: NSCoder) {
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -1390,7 +1412,8 @@ class ComicImageCell: UICollectionViewCell {
])
}
required init?(coder: NSCoder) {
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -1403,7 +1426,7 @@ enum Rotation {
class ImageLoader {
private let cache = NSCache<NSString, UIImage>()
private let horCache = NSCache<NSString, UIImage>()
private var loadingTasks: [String: [((UIImage?) -> Void)]] = [:]
private var loadingTasks: [String: [(UIImage?) -> Void]] = [:]
var size: [String: CGSize] = [:]
init() {
@@ -1421,9 +1444,9 @@ class ImageLoader {
) {
let screenSize = UIScreen.main.bounds.size
let rotation: Rotation!
if screenSize.width > screenSize.height {
rotation = Rotation.horizontal
} else { rotation = Rotation.vertical }
if screenSize.width > screenSize.height {
rotation = Rotation.horizontal
} else { rotation = Rotation.vertical }
if rotation == .vertical {
if let cached = cache.object(forKey: path.path as NSString) {
@@ -1455,9 +1478,11 @@ class ImageLoader {
if completion == nil || rotation == .vertical {
let vertical = CGSize(
width: min(screenSize.width, screenSize.height),
height: max(screenSize.height, screenSize.width))
height: max(screenSize.height, screenSize.width)
)
let vScaledSize = aspectSize(
for: image.size, in: vertical, scaling: scaling)
for: image.size, in: vertical, scaling: scaling
)
let vScaleImage = resizeImage(image, to: vScaledSize)
guard let cost = imageByteSize(vScaleImage) else { return }
self.cache.setObject(vScaleImage, forKey: path.path as NSString, cost: cost)
@@ -1469,9 +1494,11 @@ class ImageLoader {
if completion == nil || rotation == .horizontal {
let horizontal = CGSize(
width: max(screenSize.width, screenSize.height),
height: min(screenSize.height, screenSize.width))
height: min(screenSize.height, screenSize.width)
)
let hScaledSize = aspectSize(
for: image.size, in: horizontal, scaling: scaling)
for: image.size, in: horizontal, scaling: scaling
)
let hScaleImage = resizeImage(image, to: hScaledSize)
guard let cost = imageByteSize(hScaleImage) else { return }
self.horCache.setObject(hScaleImage, forKey: path.path as NSString, cost: cost)