diff --git a/ios/LibNovel/LibNovel/Services/AudioPlayerService.swift b/ios/LibNovel/LibNovel/Services/AudioPlayerService.swift index bd7c7bd..e1c92d0 100644 --- a/ios/LibNovel/LibNovel/Services/AudioPlayerService.swift +++ b/ios/LibNovel/LibNovel/Services/AudioPlayerService.swift @@ -47,6 +47,20 @@ final class AudioPlayerService: ObservableObject { default: return true } } + + /// Absolute previous chapter number (current - 1), or nil if at first chapter + var absolutePrevChapter: Int? { + guard chapter > 1 else { return nil } + return chapter - 1 + } + + /// Absolute next chapter number (current + 1), or nil if at last chapter + var absoluteNextChapter: Int? { + guard !chapters.isEmpty else { return nil } + let maxChapter = chapters.map(\.number).max() ?? chapter + guard chapter < maxChapter else { return nil } + return chapter + 1 + } // MARK: - Private diff --git a/ios/LibNovel/LibNovel/Views/Player/PlayerViews.swift b/ios/LibNovel/LibNovel/Views/Player/PlayerViews.swift index e09501f..3458f14 100644 --- a/ios/LibNovel/LibNovel/Views/Player/PlayerViews.swift +++ b/ios/LibNovel/LibNovel/Views/Player/PlayerViews.swift @@ -77,7 +77,7 @@ struct MiniPlayerView: View { // Previous chapter button if audioPlayer.status == .ready { Button { - if let prev = audioPlayer.prevChapter { + if let prev = audioPlayer.absolutePrevChapter { NotificationCenter.default.post( name: .skipToPrevChapter, object: nil, @@ -92,8 +92,8 @@ struct MiniPlayerView: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - .disabled(audioPlayer.prevChapter == nil) - .opacity(audioPlayer.prevChapter == nil ? 0.4 : 1.0) + .disabled(audioPlayer.absolutePrevChapter == nil) + .opacity(audioPlayer.absolutePrevChapter == nil ? 0.4 : 1.0) } // Status indicator or play/pause control @@ -126,7 +126,7 @@ struct MiniPlayerView: View { // Next chapter button if audioPlayer.status == .ready { Button { - if let next = audioPlayer.nextChapter { + if let next = audioPlayer.absoluteNextChapter { NotificationCenter.default.post( name: .skipToNextChapter, object: nil, @@ -158,8 +158,8 @@ struct MiniPlayerView: View { .contentShape(Rectangle()) } .buttonStyle(.plain) - .disabled(audioPlayer.nextChapter == nil) - .opacity(audioPlayer.nextChapter == nil ? 0.4 : 1.0) + .disabled(audioPlayer.absoluteNextChapter == nil) + .opacity(audioPlayer.absoluteNextChapter == nil ? 0.4 : 1.0) } } } @@ -456,7 +456,7 @@ struct FullPlayerView: View { // ← previous chapter Button { - if let prev = audioPlayer.prevChapter { + if let prev = audioPlayer.absolutePrevChapter { onDismiss() NotificationCenter.default.post( name: .skipToPrevChapter, @@ -471,8 +471,8 @@ struct FullPlayerView: View { .frame(maxWidth: .infinity) } .buttonStyle(.plain) - .disabled(audioPlayer.prevChapter == nil) - .opacity(audioPlayer.prevChapter == nil ? 0.5 : 1.0) + .disabled(audioPlayer.absolutePrevChapter == nil) + .opacity(audioPlayer.absolutePrevChapter == nil ? 0.5 : 1.0) // play / pause Button { audioPlayer.togglePlayPause() } label: { @@ -495,7 +495,7 @@ struct FullPlayerView: View { // → next chapter Button { - if let next = audioPlayer.nextChapter { + if let next = audioPlayer.absoluteNextChapter { onDismiss() NotificationCenter.default.post( name: .skipToNextChapter, @@ -527,8 +527,8 @@ struct FullPlayerView: View { .frame(maxWidth: .infinity) } .buttonStyle(.plain) - .disabled(audioPlayer.nextChapter == nil) - .opacity(audioPlayer.nextChapter == nil ? 0.5 : 1.0) + .disabled(audioPlayer.absoluteNextChapter == nil) + .opacity(audioPlayer.absoluteNextChapter == nil ? 0.5 : 1.0) // → skip forward 15s Button { audioPlayer.skip(by: 15) } label: {