Files
libnovel/ios/LibNovelV2/ViewModels/BookDetailViewModel.swift
Admin 7413313100
All checks were successful
CI / Scraper / Lint (push) Successful in 10s
CI / Scraper / Test (push) Successful in 14s
Release / Scraper / Test (push) Successful in 18s
CI / Scraper / Lint (pull_request) Successful in 18s
Release / UI / Build (push) Successful in 23s
CI / Scraper / Test (pull_request) Successful in 15s
CI / UI / Build (pull_request) Successful in 32s
Release / Scraper / Docker (push) Successful in 55s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
CI / Scraper / Docker Push (push) Successful in 1m5s
Release / UI / Docker (push) Successful in 1m12s
iOS CI / Build (push) Successful in 4m18s
iOS CI / Build (pull_request) Successful in 4m25s
iOS CI / Test (push) Successful in 8m11s
iOS CI / Test (pull_request) Successful in 8m21s
fix: update integration_test.go to match server.New signature (version, commit args)
2026-03-14 14:25:46 +05:00

81 lines
2.1 KiB
Swift

import Foundation
// MARK: - BookDetailViewModel
// Loads book metadata, chapter index, save state, and reading progress.
// Uses @Observable (iOS 17+).
@Observable
@MainActor
final class BookDetailViewModel {
let slug: String
var book: Book?
var chapters: [ChapterIndex] = []
var inLib: Bool = false
var saved: Bool = false
var lastChapter: Int?
var isLoading = false
var isSaving = false
var error: String?
init(slug: String) {
self.slug = slug
}
// MARK: - Load
func load() async {
guard !isLoading else { return }
isLoading = true
error = nil
do {
let response = try await APIClient.shared.bookDetail(slug: slug)
book = response.book
chapters = response.chapters
inLib = response.inLib
saved = response.saved
lastChapter = response.lastChapter
} catch {
if !(error is CancellationError) {
self.error = error.localizedDescription
}
}
isLoading = false
}
// MARK: - Toggle saved (bookmark)
func toggleSaved() async {
guard !isSaving else { return }
isSaving = true
let targetSaved = !saved
saved = targetSaved // optimistic update
do {
if targetSaved {
try await APIClient.shared.saveBook(slug: slug)
if !inLib { inLib = true }
} else {
try await APIClient.shared.unsaveBook(slug: slug)
}
} catch {
saved = !targetSaved // revert on failure
self.error = error.localizedDescription
}
isSaving = false
}
// MARK: - Chapter helpers
/// Title stripped of trailing " - Month DD YYYY" date suffixes.
func displayTitle(for chapter: ChapterIndex) -> String {
let stripped = chapter.title.strippingTrailingDate()
if stripped.isEmpty || stripped == "Chapter \(chapter.number)" {
return "Chapter \(chapter.number)"
}
return stripped
}
}