import Foundation // MARK: - ChapterReaderViewModel @Observable @MainActor final class ChapterReaderViewModel { let slug: String private(set) var chapter: Int var content: ChapterResponse? var isLoading = false var error: String? init(slug: String, chapter: Int) { self.slug = slug self.chapter = chapter } /// Switch to a different chapter in-place; `chapter` change causes `.task(id: chapter)` to re-fire `load()`. func switchChapter(to newChapter: Int) { guard newChapter != chapter else { return } chapter = newChapter content = nil error = nil } func load() async { isLoading = true error = nil do { content = try await APIClient.shared.chapterContent(slug: slug, chapter: chapter) try? await APIClient.shared.setProgress(slug: slug, chapter: chapter) } catch { if !(error is CancellationError) { self.error = error.localizedDescription } } isLoading = false } func toggleAudio(audioPlayer: AudioPlayerService, settings: UserSettings) { guard let content else { return } let isCurrent = audioPlayer.isActive && audioPlayer.slug == slug && audioPlayer.chapter == chapter if isCurrent { audioPlayer.togglePlayPause() } else { let voice = BookVoicePreferences.shared.voiceWithFallback( for: slug, globalVoice: settings.voice ) audioPlayer.load( slug: slug, chapter: chapter, chapterTitle: content.chapter.title, bookTitle: content.book.title, coverURL: content.book.cover, voice: voice, speed: settings.speed, chapters: content.chapters, nextChapter: content.next, prevChapter: content.prev ) } } }