feat(podcasts): played/unplayed queue, background generation, share-sheet audio
All checks were successful
CI / build-and-deploy (push) Successful in 24s
CI / build-and-deploy (pull_request) Successful in 15s

Turns the Podcasts tab into a proper podcast-app experience across three
features that compose on a shared background-generation service.

Background generation (#2)
- New PodcastGenerationManager runs generate→poll→download off the player,
  so producing a new episode never stops current playback.
- Headphone taps never interrupt: idle → generate-and-play in the foreground
  (unchanged); already playing → generate in the background, episode lands in
  the library. A "Generating…" banner surfaces active jobs.

Played/unplayed + queue (#1)
- PodcastEntry gains playedAt (old index.json decodes as unplayed).
- Podcasts tab splits into Up Next / Played with a Play All queue that
  auto-advances; finishing marks played; swipe to toggle played state.

Share-extension audio (#3)
- "Create podcast" toggle on the save card, plus configurable Auto-Podcast
  Tags in Settings. The extension enqueues a request into the App Group; the
  main app drains it on launch/foreground and generates in the background.

Closes #1
Closes #2
Closes #3

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krishna Kumar
2026-07-01 11:37:42 -05:00
parent f8de444f8f
commit af3112530e
15 changed files with 439 additions and 59 deletions

View File

@@ -6,6 +6,8 @@ struct SettingsView: View {
@Environment(\.dismiss) private var dismiss
@State private var autoTagText = PodcastRequests.autoTags.joined(separator: " ")
var body: some View {
NavigationStack {
List {
@@ -15,6 +17,21 @@ struct SettingsView: View {
.foregroundStyle(.secondary)
}
Section {
TextField("e.g. listen podcast", text: $autoTagText)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.onChange(of: autoTagText) { _, new in
PodcastRequests.autoTags = new
.split(whereSeparator: { $0 == " " || $0 == "," })
.map(String.init)
}
} header: {
Text("Auto-Podcast Tags")
} footer: {
Text("Saving a bookmark with any of these tags automatically generates a podcast for it.")
}
Section("Server") {
Button(role: .destructive) {
dismiss()