iOS: add chapters list sheet to full player
Some checks failed
CI / UI / Build (pull_request) Failing after 6s
CI / Scraper / Test (pull_request) Failing after 6s
CI / Scraper / Lint (pull_request) Failing after 8s
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 2s
iOS CI / Test (pull_request) Has been skipped

- List bullet button now opens a chapters list sheet
- ChaptersListSheet displays all chapters with scroll view
- Current chapter highlighted with amber badge and background
- Auto-scrolls to current chapter when opened
- Chapter selection uses skip notifications to navigate
- Supports both .medium and .large presentation sizes
- Shows "Now Playing" label on current chapter
- Clean list design with chapter numbers and titles
This commit is contained in:
Admin
2026-03-08 13:18:07 +05:00
parent 37bd73651a
commit fc5cd30c93

View File

@@ -190,6 +190,7 @@ struct FullPlayerView: View {
@State private var isFavorited = false
@State private var isLiked = false
@State private var showingSpeedMenu = false
@State private var showingChaptersList = false
var body: some View {
ZStack {
@@ -515,14 +516,15 @@ struct FullPlayerView: View {
.disabled(true)
// Queue (show chapters list)
Button {} label: {
Button {
showingChaptersList = true
} label: {
Image(systemName: "list.bullet")
.font(.system(size: 22))
.foregroundStyle(.white.opacity(0.7))
.frame(maxWidth: .infinity)
}
.buttonStyle(.plain)
.disabled(true)
// Sleep timer (placeholder)
Button {} label: {
@@ -538,6 +540,31 @@ struct FullPlayerView: View {
.padding(.bottom, 24)
}
}
.sheet(isPresented: $showingChaptersList) {
ChaptersListSheet(
chapters: audioPlayer.chapters,
currentChapter: audioPlayer.chapter,
onChapterSelect: { selectedChapter in
showingChaptersList = false
// Post notification to navigate to selected chapter
if selectedChapter > audioPlayer.chapter {
NotificationCenter.default.post(
name: .skipToNextChapter,
object: nil,
userInfo: ["next": selectedChapter]
)
} else if selectedChapter < audioPlayer.chapter {
NotificationCenter.default.post(
name: .skipToPrevChapter,
object: nil,
userInfo: ["prev": selectedChapter]
)
}
}
)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
}
private func formatTime(_ seconds: Double) -> String {
@@ -575,6 +602,92 @@ struct FullPlayerView: View {
}
}
// MARK: - Chapters List Sheet
struct ChaptersListSheet: View {
let chapters: [ChapterIndexBrief]
let currentChapter: Int
let onChapterSelect: (Int) -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
ScrollViewReader { proxy in
List {
ForEach(chapters, id: \.number) { chapter in
Button {
onChapterSelect(chapter.number)
} label: {
HStack(spacing: 12) {
// Chapter number badge
Text("\(chapter.number)")
.font(.caption.bold())
.foregroundStyle(chapter.number == currentChapter ? .white : .secondary)
.frame(width: 44, height: 44)
.background(
Circle()
.fill(chapter.number == currentChapter ? Color.amber : Color.gray.opacity(0.2))
)
// Chapter title
VStack(alignment: .leading, spacing: 4) {
Text(chapter.title.strippingTrailingDate())
.font(.subheadline.weight(chapter.number == currentChapter ? .semibold : .regular))
.foregroundStyle(chapter.number == currentChapter ? .primary : .primary)
.lineLimit(2)
if chapter.number == currentChapter {
Text("Now Playing")
.font(.caption2)
.foregroundStyle(.amber)
}
}
Spacer()
// Checkmark for current chapter
if chapter.number == currentChapter {
Image(systemName: "checkmark")
.font(.caption.bold())
.foregroundStyle(.amber)
}
}
.padding(.vertical, 8)
}
.buttonStyle(.plain)
.listRowBackground(
chapter.number == currentChapter
? Color.amber.opacity(0.1)
: Color.clear
)
.id(chapter.number)
}
}
.listStyle(.plain)
.onAppear {
// Scroll to current chapter on appear
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation {
proxy.scrollTo(currentChapter, anchor: .center)
}
}
}
}
.navigationTitle("Chapters")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
dismiss()
}
.fontWeight(.semibold)
}
}
}
}
}
// MARK: - Custom seek slider
// A thicker, rounded-thumb slider that matches the amber design language.