feat(podcasts): #6 quick wins — tab badge, remember speed, sleep timer, persist queue
- Unread badge on the Podcasts tab via a new observable PodcastLibrary store that mirrors PodcastIndex; all played/remove/generation mutations route through it so the count stays live. - Persist playback speed across sessions (UserDefaults), applied on setup. - Sleep timer (15/30/45 min or end-of-episode) with a moon menu in the full player; end-of-episode stops instead of auto-advancing. - Persist the current episode + Up Next queue and restore a *paused* session on launch; save position when backgrounding. Audio session is only claimed on actual playback so a restored session never interrupts other audio. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,7 @@ struct MainContainer: View {
|
||||
@State private var showDeepLinkPlayer = false
|
||||
@State private var selectedTab: AppTab = .bookmarks
|
||||
@State private var router = IntentRouter.shared
|
||||
@State private var library = PodcastLibrary.shared
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
init(config: ServerConfig, onDisconnect: @escaping () -> Void) {
|
||||
@@ -58,6 +59,7 @@ struct MainContainer: View {
|
||||
Tab("Podcasts", systemImage: "headphones", value: AppTab.podcasts) {
|
||||
PodcastLibraryView(vm: viewModel.podcastPlayer, claude: viewModel.claude)
|
||||
}
|
||||
.badge(library.unplayedCount)
|
||||
Tab(value: AppTab.search, role: .search) {
|
||||
SearchView(viewModel: viewModel)
|
||||
}
|
||||
@@ -65,7 +67,11 @@ struct MainContainer: View {
|
||||
.onOpenURL { url in
|
||||
handleDeepLink(url)
|
||||
}
|
||||
.task { applyPendingIntent(); viewModel.processPendingPodcastRequests() }
|
||||
.task {
|
||||
applyPendingIntent()
|
||||
viewModel.processPendingPodcastRequests()
|
||||
viewModel.podcastPlayer.restoreSession(claude: viewModel.claude)
|
||||
}
|
||||
.onChange(of: router.openBookmarkURL) { _, _ in applyPendingIntent() }
|
||||
.onChange(of: router.searchRequest) { _, _ in applyPendingIntent() }
|
||||
.onChange(of: scenePhase) { old, new in
|
||||
@@ -75,6 +81,11 @@ struct MainContainer: View {
|
||||
viewModel.processPendingPodcastRequests()
|
||||
Task { await viewModel.load() }
|
||||
}
|
||||
// Persist playback position + session when leaving the foreground so a
|
||||
// relaunch can resume where you left off.
|
||||
if new == .background {
|
||||
viewModel.podcastPlayer.saveProgress()
|
||||
}
|
||||
}
|
||||
.sheet(item: $deepLinkBrowser) { item in
|
||||
BrowserView(url: item.url, title: item.url.host ?? "", claude: viewModel.claude, podcastPlayer: viewModel.podcastPlayer, podcastGenerator: viewModel.podcastGenerator)
|
||||
|
||||
Reference in New Issue
Block a user