iOS: replace shuffle/repeat with time skip and add chapter navigation to full player
Some checks failed
CI / Scraper / Test (pull_request) Failing after 8s
CI / UI / Build (pull_request) Failing after 8s
CI / Scraper / Lint (pull_request) Failing after 11s
CI / Scraper / Build (pull_request) Has been skipped
iOS CI / Build (push) Has been cancelled
iOS CI / Test (push) Has been cancelled
iOS CI / Build (pull_request) Failing after 1s
iOS CI / Test (pull_request) Has been skipped

- Leftmost button: 15-second backward skip (gobackward.15)
- Second button: previous chapter navigation (backward.end.fill)
- Center: play/pause (unchanged)
- Fourth button: next chapter navigation (forward.end.fill)
- Rightmost button: 15-second forward skip (goforward.15)
- Added prevChapter tracking in AudioPlayerService
- Added skipToPrevChapter notification support
- Chapter nav buttons disabled when no prev/next available (0.5 opacity)
- Updated load() signature to accept prevChapter parameter
- Auto-next properly tracks prevChapter when advancing chapters
This commit is contained in:
Admin
2026-03-08 13:14:26 +05:00
parent 466e289b68
commit 37bd73651a
4 changed files with 21 additions and 4 deletions

View File

@@ -33,6 +33,7 @@ final class AudioPlayerService: ObservableObject {
@Published var autoNext: Bool = false
@Published var nextChapter: Int? = nil
@Published var prevChapter: Int? = nil
@Published var nextPrefetchStatus: NextPrefetchStatus = .none
@Published var nextAudioURL: String = ""
@@ -73,7 +74,7 @@ final class AudioPlayerService: ObservableObject {
/// Load audio for a specific chapter. Triggers TTS generation if not cached.
func load(slug: String, chapter: Int, chapterTitle: String,
bookTitle: String, coverURL: String, voice: String, speed: Double,
chapters: [ChapterIndexBrief], nextChapter: Int?) {
chapters: [ChapterIndexBrief], nextChapter: Int?, prevChapter: Int?) {
generationTask?.cancel()
prefetchTask?.cancel()
stop()
@@ -87,6 +88,7 @@ final class AudioPlayerService: ObservableObject {
self.speed = speed
self.chapters = chapters
self.nextChapter = nextChapter
self.prevChapter = prevChapter
self.nextPrefetchStatus = .none
self.nextAudioURL = ""
self.nextPrefetchedChapter = nil
@@ -328,6 +330,7 @@ final class AudioPlayerService: ObservableObject {
let nextTitle = chapters.first(where: { $0.number == next })?.title ?? ""
let nextNextChapter = chapters.first(where: { $0.number > next })?.number
let nextPrevChapter: Int? = chapter // Current chapter becomes previous for the next one
// If we already prefetched a URL for the next chapter, skip straight to
// playback and kick off generation in the background for the one after.
@@ -338,6 +341,7 @@ final class AudioPlayerService: ObservableObject {
chapter = next
chapterTitle = nextTitle
nextChapter = nextNextChapter
prevChapter = nextPrevChapter
nextPrefetchStatus = .none
nextAudioURL = ""
nextPrefetchedChapter = nil
@@ -360,7 +364,8 @@ final class AudioPlayerService: ObservableObject {
voice: voice,
speed: speed,
chapters: chapters,
nextChapter: nextNextChapter
nextChapter: nextNextChapter,
prevChapter: nextPrevChapter
)
}
}
@@ -462,4 +467,6 @@ enum AudioPlayerStatus: Equatable {
extension Notification.Name {
static let audioDidFinishChapter = Notification.Name("audioDidFinishChapter")
static let skipToNextChapter = Notification.Name("skipToNextChapter")
static let skipToPrevChapter = Notification.Name("skipToPrevChapter")
}

View File

@@ -47,6 +47,7 @@ final class ChapterReaderViewModel: ObservableObject {
audioPlayer.togglePlayPause()
} else {
let nextChapter: Int? = content.next
let prevChapter: Int? = content.prev
audioPlayer.load(
slug: slug,
chapter: chapter,
@@ -56,7 +57,8 @@ final class ChapterReaderViewModel: ObservableObject {
voice: settings.voice,
speed: settings.speed,
chapters: content.chapters,
nextChapter: nextChapter
nextChapter: nextChapter,
prevChapter: prevChapter
)
}
}

View File

@@ -86,7 +86,7 @@ struct BookDetailView: View {
Button(summaryExpanded ? "Less" : "More") {
withAnimation { summaryExpanded.toggle() }
}
.font(.caption.bold())
.font(.caption.bold()) 
.foregroundStyle(.amber)
}
}

View File

@@ -61,6 +61,14 @@ struct ChapterReaderView: View {
vm.navigateTo = next
}
}
.onReceive(NotificationCenter.default.publisher(for: .skipToNextChapter)) { note in
guard let next = note.userInfo?["next"] as? Int else { return }
vm.navigateTo = next
}
.onReceive(NotificationCenter.default.publisher(for: .skipToPrevChapter)) { note in
guard let prev = note.userInfo?["prev"] as? Int else { return }
vm.navigateTo = prev
}
.navigationDestination(isPresented: Binding(
get: { vm.navigateTo != nil },
set: { if !$0 { vm.navigateTo = nil } }