From 9a34d4e95299a337806c2234b40074410efbc515 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Mon, 3 Aug 2026 21:31:35 +0200 Subject: [PATCH] fix: better local state handling --- ImageViewer/ViewController.swift | 184 ++++++++++++++----------------- 1 file changed, 81 insertions(+), 103 deletions(-) diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index cfac009..ea97ff8 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -21,10 +21,10 @@ enum ReadProgress: Codable { case scroll(CGPoint) } -struct ProgressIndices { - var v: Int - var c: Int - var i: Int +struct ProgressIndices: Codable { + var v: Int = 0 + var c: Int = 0 + var i: Int = 0 } enum PageTurnMode: Codable, CaseIterable { @@ -43,11 +43,13 @@ struct GlobalState: Codable { var comicName: String? } +/// nil indicates a default should be used, aka what is in settings. struct LocalState: Codable { - var progress: ReadProgress + var progress: ProgressIndices = ProgressIndices() + var mode: PageTurnMode? var backgroundColor: String? - var useDefaultPageTurnMode: Bool - var aniListTracking: Bool + var aniListTracking: Bool = false + var scrollPos: CGPoint = CGPoint() } struct Settings: Codable { @@ -63,7 +65,6 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { var readerView = UIView() @IBOutlet var scrollingCollectionView: UICollectionView! - var scrollPos: CGPoint! var hasSetContentOffset = false var pendingAlert: UIAlertController? @@ -72,16 +73,13 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { let dragRangeForFullBrightness: CGFloat = 300 var imageView = UIImageView() - var mode = PageTurnMode.leftToRight - var useDefaultPageTurnMode = true - var useDefaultBackgroundColor = true var metadataList: [URL: Metadata] = [:] + // directory name is key, indexPath.item can change. + var state: [String: LocalState] = [:] var metadata: Metadata! - var progress = ProgressIndices(v: 0, c: 0, i: 0) var currentPath: URL! var lastLayoutSize: CGSize = .zero var aniListToken: String? = getAniListToken() - var aniListTracking: [Int: Bool] = [:] var currentItemIndex = 0 var leftTap: UITapGestureRecognizer! @@ -126,9 +124,9 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { if let alert = pendingAlert { present(alert, animated: false, completion: nil) } - if !hasSetContentOffset, scrollPos != nil { + if !hasSetContentOffset, let scrollPos = state[currentPath.lastPathComponent]?.scrollPos { let screenSize = UIScreen.main.bounds.size - var offset = scrollPos! + var offset = scrollPos if screenSize.width > screenSize.height { offset.y *= (screenSize.width / screenSize.height) } @@ -142,6 +140,20 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } } + func getMode() -> PageTurnMode { + if let state = state[currentPath.lastPathComponent] { + return state.mode ?? settings.defaultPageTurnMode + } + return settings.defaultPageTurnMode + } + + func getProgress() -> ProgressIndices { + if let state = state[currentPath.lastPathComponent] { + return state.progress + } + return ProgressIndices() + } + func setup() { do { if try fileManager.contentsOfDirectory(atPath: documentsURL.path).isEmpty { @@ -170,7 +182,6 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { readerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), readerView.widthAnchor.constraint(equalTo: view.widthAnchor), ]) - mode = settings.defaultPageTurnMode setupScrollingCollectionView() loadComics() setupImageView() @@ -285,7 +296,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } metadataList[dir] = metadata loadLocalState() - let volCovP = ProgressIndices(v: progress.v, c: 0, i: 0) + let volCovP = ProgressIndices(v: state[currentPath.lastPathComponent]!.progress.v, c: 0, i: 0) let archivePath = getArchiveURL(volCovP.v) let archive = try Archive(url: archivePath, accessMode: .read) let filename = metadata.volumes[volCovP.v].chapters[volCovP.c].images[volCovP.i].filename! @@ -772,37 +783,30 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } func saveLocalState() { + guard let currentDir = currentPath?.lastPathComponent else { print("did not save currentPath nil"); return } + guard let currentState = state[currentDir] else { print("did not save currentState nil"); return } + let progress = currentState.progress + let mode = currentState.mode let color = readerView.backgroundColor ?? .black + let aniListTracking = currentState.aniListTracking + let screenSize = UIScreen.main.bounds.size var scrollOffset = scrollingCollectionView.contentOffset if screenSize.width > screenSize.height { scrollOffset.y *= (screenSize.height / screenSize.width) } - var newProgress = - ReadProgress.leftToRight( - volumeIndex: progress.v, chapterIndex: progress.c, - imageIndex: progress.i - ) + // TODO: Calucate the offset based on progress, to allow seamless switching. + if mode != .scroll {} - 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) - } queue.async { do { try JSONEncoder().encode( LocalState( - progress: newProgress, backgroundColor: self.convertColorToString(color), - useDefaultPageTurnMode: self.useDefaultPageTurnMode, - aniListTracking: self.aniListTracking[self.currentItemIndex] ?? false + progress: progress, + mode: mode, + backgroundColor: self.convertColorToString(color), + aniListTracking: aniListTracking, + scrollPos: scrollOffset ) ).write( to: self.currentPath.appendingPathComponent("state.json") @@ -814,59 +818,22 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } func loadLocalState() { + if state[currentPath.lastPathComponent] != nil { return } do { let path = currentPath.appendingPathComponent("state.json").path if !fileManager.fileExists(atPath: path) { - progress = ProgressIndices(v: 0, c: 0, i: 0) - mode = settings.defaultPageTurnMode + state[currentPath.lastPathComponent] = LocalState() return } let json = try Data(String(contentsOfFile: path).utf8) let local = try JSONDecoder().decode(LocalState.self, from: json) - useDefaultPageTurnMode = local.useDefaultPageTurnMode - aniListTracking[currentItemIndex] = local.aniListTracking - switch local.progress { - case let .leftToRight(volumeIndex, chapterIndex, imageIndex): - progress.v = volumeIndex - progress.c = chapterIndex - progress.i = imageIndex - mode = useDefaultPageTurnMode ? settings.defaultPageTurnMode : .leftToRight - case let .rightToLeft(volumeIndex, chapterIndex, imageIndex): - progress.v = volumeIndex - progress.c = chapterIndex - progress.i = imageIndex - mode = useDefaultPageTurnMode ? settings.defaultPageTurnMode : .rightToLeft - case let .scroll(point): - if scrollPos == nil { - scrollPos = point - let screenSize = UIScreen.main.bounds.size - var scrollOffset = point - if screenSize.width > screenSize.height { - scrollOffset.y *= (screenSize.width / screenSize.height) - } - if let indexPath = scrollingCollectionView.indexPathForItem(at: scrollOffset) { - var theProgress = ProgressIndices(v: 0, c: 0, i: 0) - for _ in 0 ..< indexPath.item { - theProgress = getProgressIndicesFromTurn( - turn: .next, progress: theProgress - ) - } - progress = theProgress - } - } - mode = useDefaultPageTurnMode ? settings.defaultPageTurnMode : .scroll - } - if let color = local.backgroundColor { - readerView.backgroundColor = convertStringToColor(color) - useDefaultBackgroundColor = false - } else { - readerView.backgroundColor = convertStringToColor(settings.defaultReadingBackgroundColor) - useDefaultBackgroundColor = true - } + state[currentPath.lastPathComponent] = local } catch let decodingError as DecodingError { - print(decodingError.errorDescription!) + print("Failed to decode state.json, setting default: \(decodingError.errorDescription ?? "")") + state[currentPath.lastPathComponent] = LocalState() } catch { - print("Unexpected error: \(error)") + print("Failed to decode state.json, setting default: \(error)") + state[currentPath.lastPathComponent] = LocalState() } } @@ -899,8 +866,9 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { for _ in 0 ..< indexPath.item { newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) } + guard let progress = state[currentPath.lastPathComponent]?.progress else { return } aniListProgressUpdate(progress: progress, newProgress: newProgress) - progress = newProgress + state[currentPath.lastPathComponent]!.progress = newProgress } if scrollingCollectionView.isHidden == false { saveLocalState() @@ -912,10 +880,11 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { guard view.bounds.size != lastLayoutSize else { return } lastLayoutSize = UIScreen.main.bounds.size if scrollingCollectionView.isHidden - || mode != .scroll + || getMode() != .scroll || scrollingCollectionView.contentSize == .zero { if metadata == nil { return } + let progress = getProgress() imageLoader.loadImage( archiveURL: getArchiveURL(progress.v), filename: getImagePath(progress), @@ -936,6 +905,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { if let path = getPathFromComicName(name: name) { currentPath = path metadata = metadataList[path] + let mode = getMode() globalState.comicName = metadata.title saveGlobalState() @@ -1187,7 +1157,9 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { let point = gesture.location(in: comicCollectionView) guard let indexPath = comicCollectionView.indexPathForItem(at: point) else { return } + currentPath = comics[indexPath.item].path + loadLocalState() comicOptionLongPressed(indexPath) } @@ -1196,7 +1168,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } @objc func handleLeftTap() { - switch mode { + switch getMode() { case .rightToLeft: changeImage(turn: .next) case .leftToRight: changeImage(turn: .previous) case .scroll: break @@ -1204,7 +1176,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } @objc func handleRightTap() { - switch mode { + switch getMode() { case .rightToLeft: changeImage(turn: .previous) case .leftToRight: changeImage(turn: .next) case .scroll: break @@ -1214,13 +1186,13 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { switch gesture.direction { case .left: - switch mode { + switch getMode() { case .rightToLeft: changeImage(turn: .previous) case .leftToRight: changeImage(turn: .next) case .scroll: break } case .right: - switch mode { + switch getMode() { case .rightToLeft: changeImage(turn: .next) case .leftToRight: changeImage(turn: .previous) case .scroll: break @@ -1280,13 +1252,14 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { func changeImage(turn: PageTurn) { let scaling = UIView.ContentMode.scaleAspectFit + let progress = getProgress() var newProgress = progress newProgress = getProgressIndicesFromTurn(turn: turn, progress: newProgress) if newProgress.v == progress.v, newProgress.c == progress.c, newProgress.i == progress.i { return } aniListProgressUpdate(progress: progress, newProgress: newProgress) - progress = newProgress + state[currentPath.lastPathComponent]!.progress = newProgress updateInfo() let path = getImagePath(progress) @@ -1326,6 +1299,7 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { func updateInfo() { if metadata == nil { return } + let progress = getProgress() var text = "\(metadata.title ?? "")\n" @@ -1427,6 +1401,8 @@ final class ViewController: UIViewController, UIGestureRecognizerDelegate { } func setImages() { + let mode = getMode() + let progress = getProgress() let scaling: UIView.ContentMode = mode == .scroll ? .scaleAspectFill : .scaleAspectFit imageLoader.loadImage( archiveURL: getArchiveURL(progress.v), @@ -1560,7 +1536,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl else { return ScrollingImageCell() } - if mode != .scroll { return cell } + if getMode() != .scroll { return cell } if metadata == nil { print("metadata is nil, should probably not be the case") return cell @@ -1571,6 +1547,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl newProgress = getProgressIndicesFromTurn(turn: .next, progress: newProgress) } } + let progress = getProgress() imageLoader.loadImage( archiveURL: getArchiveURL(progress.v), filename: getImagePath(newProgress), @@ -2244,11 +2221,12 @@ extension ViewController { return SettingsMenuItem( title: title, children: nil, action: { [self] in - self.useDefaultBackgroundColor = color == nil + self.state[currentPath.lastPathComponent]!.backgroundColor = color == nil + ? nil : convertColorToString(color!) colorHandler(color ?? self.convertStringToColor(settings.defaultReadingBackgroundColor)) self.dismiss(animated: false) }, selected: { [self] in - if self.useDefaultBackgroundColor { return color == nil } + if self.state[currentPath.lastPathComponent]!.backgroundColor == nil { return color == nil } return color == self.readerView.backgroundColor && color != nil } ) @@ -2266,7 +2244,7 @@ extension ViewController { self.dismiss(animated: false) }, selected: { [self] in - return self.mode == pageTurnMode + return self.getMode() == pageTurnMode } ) } @@ -2284,14 +2262,12 @@ extension ViewController { return SettingsMenuItem( title: title, children: nil, action: { [self] in - self.useDefaultPageTurnMode = pageTurnMode == nil - let mode = pageTurnMode ?? self.settings.defaultPageTurnMode + let mode = getMode() pageTurnHandler(mode) self.dismiss(animated: false) }, selected: { [self] in - if self.useDefaultPageTurnMode { return pageTurnMode == nil } - return self.mode == pageTurnMode && pageTurnMode != nil + return self.getMode() == pageTurnMode } ) } @@ -2334,9 +2310,11 @@ extension ViewController { private func makePageTurnTree() -> [SettingsMenuItem] { return pageTurnDefaultItems { [self] pageTurnMode in + let mode = getMode() + let progress = getProgress() if mode == pageTurnMode { return } let scrollSwitch = mode != .scroll && pageTurnMode != .scroll - mode = pageTurnMode + state[currentPath.lastPathComponent]!.mode = pageTurnMode saveLocalState() if scrollSwitch { return } let archiveURL = getArchiveURL(progress.v) @@ -2426,7 +2404,7 @@ extension ViewController { case .updateStatus: children = aniListUpdateStatusItems(indexPath) case .fetchMetadata: _ = 0 case .track: - if aniListTracking[indexPath.item] ?? false { + if state[currentPath.lastPathComponent]!.aniListTracking { title += " (Enabled)" } else { title += " (Disabled)" @@ -2435,14 +2413,14 @@ extension ViewController { return ComicOptionItem( title: title, children: children, destructive: false, available: { true }, - action: { [weak self] in + action: { [self] in if action == .track { - var v = self?.aniListTracking[indexPath.item] ?? false + var v = self.state[currentPath.lastPathComponent]!.aniListTracking v.toggle() - self?.aniListTracking[indexPath.item] = v - self?.saveLocalState() + self.state[self.currentPath.lastPathComponent]!.aniListTracking = v + self.saveLocalState() } - self?.dismiss(animated: false) + self.dismiss(animated: false) } ) }