Podcast UX baseline: mini player during generation, end-of-track reset, library view

- Mini player shows as soon as generation starts (not just when audio ready)
- Idempotency: tapping headphones again during generation is a no-op
- Playback resets to position 0 on completion (standard player behavior)
- Stop button in full player visible during generation
- PodcastLibraryView lists all generated podcasts
- Sparkles menu gains Podcasts entry
- Fix AnalyticsService Sendable error (serialize before Task.detached boundary)
This commit is contained in:
Krishna Kumar
2026-05-23 00:46:46 -05:00
parent 18423a7633
commit ab72611d7b
3 changed files with 113 additions and 16 deletions

View File

@@ -11,6 +11,7 @@ struct BookmarksView: View {
@State private var editingBookmark: Bookmark?
@State private var browsingBookmark: Bookmark?
@State private var showFullPlayer = false
@State private var showPodcastLibrary = false
@State private var readingProgress: [String: Double] = ReadingProgress.all()
var body: some View {
@@ -118,6 +119,11 @@ struct BookmarksView: View {
} label: {
Label("Enrich All with AI", systemImage: "wand.and.stars")
}
Button {
showPodcastLibrary = true
} label: {
Label("Podcasts", systemImage: "headphones")
}
} label: {
Image(systemName: "sparkles")
}
@@ -150,7 +156,7 @@ struct BookmarksView: View {
}
.overlay(alignment: .bottom) {
VStack(spacing: 8) {
if viewModel.podcastPlayer.player != nil {
if !viewModel.podcastPlayer.currentArticleUrl.isEmpty {
MiniPlayerView(vm: viewModel.podcastPlayer) {
showFullPlayer = true
}
@@ -160,7 +166,7 @@ struct BookmarksView: View {
enrichmentBanner
}
}
.animation(.spring(duration: 0.3), value: viewModel.podcastPlayer.player != nil)
.animation(.spring(duration: 0.3), value: !viewModel.podcastPlayer.currentArticleUrl.isEmpty)
.padding(.bottom, 8)
}
}
@@ -193,6 +199,9 @@ struct BookmarksView: View {
stopOnDismiss: false
)
}
.sheet(isPresented: $showPodcastLibrary) {
PodcastLibraryView(vm: viewModel.podcastPlayer, claude: viewModel.claude)
}
.refreshable {
await viewModel.load()
}