Skip to content

[RSM] Show summaries for episodes where available#5276

Merged
sztomek merged 17 commits into
mainfrom
feat/radical-episode-summaries
May 25, 2026
Merged

[RSM] Show summaries for episodes where available#5276
sztomek merged 17 commits into
mainfrom
feat/radical-episode-summaries

Conversation

@sztomek
Copy link
Copy Markdown
Contributor

@sztomek sztomek commented May 7, 2026

Description

This PR is part of the Radical Speed Month initiative, Pocket Casts AI project (phcsdm-1j8-p2)
Adds an AI-generated episode summary surface to the episode details screen. When an episode has a generated transcript, we fetch the existing -meta.json sidecar from S3 (no new backend work — the server lambda pipeline already produces these) and display the summary inline in a tabbed UI alongside Description and Transcript tabs. The summary markdown is rendered as formatted HTML using the existing HtmlText composable. When AI summaries are enabled, an "Ask this episode" input-style banner is shown above the tabs for quick access to the episode chat.

In sync with the Chat with Episode story, we agreed to have the following designs: p1779220475409719/1779212859.563619-slack-C05RR9P9RAT

Testing Instructions

  1. Enable the AI Summaries feature flag in dev settings
  2. Select a podcast that has generated transcripts (e.g. The American Life)
  3. Select a podcast episode to open its details
  4. Notice the tabbed UI: Description / Summary / Transcript tabs
  5. Tap the "Summary" tab
  6. Observe the summary rendered inline below the tabs

Screenshots or Screencast

Screen_recording_20260522_173934.mp4
Designs Actual
SCR-20260522-pmyq Screenshot_20260522_173923

Checklist

  • If this is a user-facing change, I have added an entry in CHANGELOG.md
  • Ensure the linter passes (./gradlew spotlessApply to automatically apply formatting/linting)
  • I have considered whether it makes sense to add tests for my changes
  • All strings that need to be localized are in modules/services/localization/src/main/res/values/strings.xml
  • Any jetpack compose components I added or changed are covered by compose previews
  • I have updated (or requested that someone edit) the spreadsheet to reflect any new or changed analytics.

I have tested any UI changes...

  • with different themes
  • with a landscape orientation
  • with the device set to have a large display and font size
  • for accessibility with TalkBack

@sztomek sztomek added this to the 8.12 milestone May 7, 2026
@sztomek sztomek requested a review from a team as a code owner May 7, 2026 09:05
@sztomek sztomek requested review from MiSikora and Copilot and removed request for a team May 7, 2026 09:05
@sztomek sztomek added [Type] Feature Adding a new feature. [Area] Episode Popup Episode popup shown when tapping an episode row labels May 7, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an AI-generated episode summary surface to the Episode Details screen by fetching an existing -meta.json sidecar for generated transcripts and displaying it via a new “View summary” banner that opens a bottom sheet.

Changes:

  • Extend TranscriptManager/TranscriptManagerImpl with a loadSummaryText(episodeUuid) API that fetches and parses summary from the transcript -meta.json.
  • Add Episode Details UI for summaries: a new ComposeView slot, a “View summary” banner, and a BaseDialogFragment bottom sheet to render the summary content.
  • Add localization strings for the banner and bottom-sheet title.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/transcript/TranscriptManagerImpl.kt Implements summary loading by locating a generated transcript and fetching/parsing its -meta.json.
modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/transcript/TranscriptManager.kt Adds a new repository API for loading episode summary text.
modules/services/localization/src/main/res/values/strings.xml Adds localized strings for “View summary” and the summary sheet title.
modules/features/podcasts/src/main/res/layout/fragment_episode.xml Adds a new ComposeView (episodeSummary) and moves transcript below it.
modules/features/podcasts/src/main/res/layout-land/fragment_episode.xml Same as portrait layout for landscape.
modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/episode/SummaryExcerptBanner.kt New Compose banner component for the summary call-to-action.
modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/episode/SummaryBottomSheet.kt New bottom sheet dialog that renders a markdown-like summary via HTML.
modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/episode/EpisodeFragmentViewModel.kt Loads summary text alongside transcripts and exposes it as a StateFlow.
modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/episode/EpisodeFragment.kt Wires the banner into the episode details UI and opens the summary bottom sheet on tap.

@CookieyedCodes
Copy link
Copy Markdown

Instead of two rows I would suggest two collumes as it would take up less space, for example.

(ai icon) Summary || (icon*) Transcript

@sztomek sztomek force-pushed the feat/radical-episode-summaries branch from 68420f4 to 477f062 Compare May 7, 2026 12:47
@sztomek
Copy link
Copy Markdown
Contributor Author

sztomek commented May 7, 2026

Instead of two rows I would suggest two collumes as it would take up less space, for example.

Thanks for your feedback, this approach is under active discussion with our design team 🙏

Copilot AI review requested due to automatic review settings May 7, 2026 13:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

@wpmobilebot wpmobilebot modified the milestones: 8.12, 8.13 May 11, 2026
@wpmobilebot
Copy link
Copy Markdown
Collaborator

Version 8.12 has now entered code-freeze, so the milestone of this PR has been updated to 8.13.

Copilot AI review requested due to automatic review settings May 12, 2026 14:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.

Comment thread modules/services/localization/src/main/res/values/strings.xml Outdated
Copilot AI review requested due to automatic review settings May 12, 2026 15:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Copilot AI review requested due to automatic review settings May 12, 2026 15:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

@obsantos obsantos mentioned this pull request May 15, 2026
10 tasks
sztomek added 4 commits May 20, 2026 11:24
- Clear stale summary and only mark UUID after successful load so
  retries work and old data doesn't flash on episode change
- Reuse loadLocalTranscripts() in loadSummaryText() to keep transcript
  availability semantics consistent
- Use removeSuffix(".vtt") instead of replace() for the meta URL
- Wrap ResponseBody in use {} for proper resource cleanup
- Replace org.json.JSONObject with Moshi for unit-test compatibility
- Add unit tests for loadSummaryText: success, no generated transcript,
  blank summary, service failure, and timeout
- Fix spotless import ordering
@sztomek sztomek force-pushed the feat/radical-episode-summaries branch from 8ca8580 to be80772 Compare May 20, 2026 12:19
@dangermattic
Copy link
Copy Markdown
Collaborator

1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

Copilot AI review requested due to automatic review settings May 20, 2026 20:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (3)

modules/features/podcasts/src/main/res/layout/fragment_episode.xml:307

  • episodeContentTabs ComposeView is always visible in XML now. In the non-AI path the Compose content can be effectively empty (no transcript/chat), but still applies top padding, leaving a blank gap above the show notes. Consider restoring android:visibility="gone" and toggling it visible only when there’s content to show (or conditionally omit padding when no children are rendered).
        <androidx.compose.ui.platform.ComposeView
            android:id="@+id/episodeContentTabs"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/lblDate" />

modules/features/podcasts/src/main/res/layout-land/fragment_episode.xml:254

  • Same as portrait: episodeContentTabs ComposeView is always visible. When there’s no transcript/chat to render (non-AI path), the Compose content still reserves padding which can create an empty vertical gap. Consider setting initial visibility to gone and only showing it when needed, or avoid padding when there’s no visible content.
        <androidx.compose.ui.platform.ComposeView
            android:id="@+id/episodeContentTabs"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/lblDate" />

modules/features/podcasts/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/view/episode/EpisodeFragment.kt:931

  • formattedNotes?.let { loadShowNotes(it) } is now called immediately after adding the WebView, but createShowNotesWebView() also calls loadShowNotes() again later in the method. This results in duplicate loadDataWithBaseURL calls when the WebView is first created. Consider removing one of the calls to avoid redundant work.
                Timber.e(e)
                binding?.webViewLoader?.hide()
                val errorMessage = resources.getString(if (e.message?.contains("webview", ignoreCase = true) == true) LR.string.error_webview_not_installed else LR.string.error_loading_show_notes)
                binding?.webViewErrorText?.text = errorMessage

@sztomek sztomek requested a review from geekygecko May 21, 2026 15:25
@sztomek
Copy link
Copy Markdown
Contributor Author

sztomek commented May 21, 2026

@geekygecko could you please review this PR as Michal's review is not valid as he doesn't hold write rights on this repo any more

Copilot AI review requested due to automatic review settings May 21, 2026 15:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

@geekygecko geekygecko mentioned this pull request May 24, 2026
10 tasks
Copy link
Copy Markdown
Member

@geekygecko geekygecko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. I only had some minor suggestions which I have changed in a PR to help us get through the PRs quicker. #5335

_selectedContentTab.value = tab
}

data class DateDurationInfo(val publishedDate: java.util.Date?, val durationMs: Long)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data class DateDurationInfo(val publishedDate: java.util.Date?, val durationMs: Long)
data class DateDurationInfo(val publishedDate: Date?, val durationMs: Long)

enum class EpisodeContentTab { DESCRIPTION, SUMMARY }

private val _selectedContentTab = MutableStateFlow(EpisodeContentTab.DESCRIPTION)
val selectedContentTab = _selectedContentTab.asStateFlow()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea but how about having a page state rather than one for the tab and another for the date and duration? If we add more then it will also be easier.

@StringRes val labelResId: Int,
val onClick: () -> Unit,
@DrawableRes val iconResId: Int? = null,
@DrawableRes val trailingIconResId: Int? = null,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't seem to be being used. Should we remove them or add them to the preview?

visible = selectedTab == EpisodeFragmentViewModel.EpisodeContentTab.SUMMARY && summaryText != null,
enter = BannerEnterTransition,
exit = BannerExitTransition,
) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content sliding down doesn’t look great, and the similar button tabs on the podcast page don’t use this behaviour either. Could we remove this animation for consistency?

Screen.Recording.2026-05-23.at.7.52.05.am.mov

)
HtmlText(
html = markdownToHtml(summaryText.orEmpty()),
color = MaterialTheme.theme.colors.primaryText02,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
color = MaterialTheme.theme.colors.primaryText02,
color = MaterialTheme.theme.colors.primaryText01,

The text on the description page is darker and easier to read on the light theme. Maybe we should make this match.

}
},
),
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the dark theme would be good to make the unselected button the same colour as the background like the podcast page.

Image

@geekygecko
Copy link
Copy Markdown
Member

I just realised this might be missing analytics as tapping summary tab didn't trigger anything. This isn't included in my PR.

@sztomek
Copy link
Copy Markdown
Contributor Author

sztomek commented May 25, 2026

thanks for the changes @geekygecko , i'll raise new PR with the analytics!

@sztomek sztomek merged commit 435e25c into main May 25, 2026
20 checks passed
@sztomek sztomek deleted the feat/radical-episode-summaries branch May 25, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Area] Episode Popup Episode popup shown when tapping an episode row [Type] Feature Adding a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants