Skip to content

Conversation

sztomek
Copy link
Contributor

@sztomek sztomek commented Oct 10, 2025

Description

This PR adds the search/combined API and wires it up for the improved results screen.
It also includes some design updates (see referenced slack thread) and functional improvements:

  • Local search lists folders
  • Episode results now properly track playback status
  • Filter chips are now floating above the results with a faded background

Fixes https://linear.app/a8c/issue/PCDROID-171/filter-search-results
Slack: p1760036879103319-slack-C09ENPJ5D2R
Designs: 9aiIqQo7ibm7R1AyIYr8fH-fi-1_35

Testing Instructions

  1. Open Discover tab
  2. Tap search bar
  3. Select a suggestion or hit enter to land on the results screen
  • filtering works
  • filter chips float above the list
  1. Select an episode for playback
  • play button updates its state properly
  1. Follow some podcasts
  • followed state updates
  1. Now sign in with a Plus account that has some folders
  2. Navigate to Podcasts and tap the magnifier glass in the toolbar
  3. Enter a query that would yield some folder results
  • folders and local podcasts appear the top of the list
  1. Filtering also works in local search mode
  2. Now Disable Imroved search results FF
  3. Play the previous scenarios to see if search still works

Screenshots or Screencast

Screen_recording_20251010_221526.mp4

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.

@sztomek sztomek added this to the 7.100 milestone Oct 10, 2025
@sztomek sztomek requested a review from a team as a code owner October 10, 2025 20:17
@sztomek sztomek requested review from MiSikora and Copilot and removed request for a team October 10, 2025 20:17
@sztomek sztomek added [Type] Feature Adding a new feature. [Area] Search labels Oct 10, 2025
@dangermattic
Copy link
Collaborator

dangermattic commented Oct 10, 2025

2 Errors
🚫 Please add tests for class ImprovedEpisodeRowViewModel (or add unit-tests-exemption label to ignore this).
🚫 Please add tests for class ImprovedSearchManagerImpl (or add unit-tests-exemption label to ignore this).
1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

Copy link

@Copilot 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

This PR adds combined search API support and implements the improved search results screen. It replaces the previous separate search managers with a unified approach that combines local and remote search results, while adding enhanced UI features like floating filter chips and proper episode playback status tracking.

  • Implements search/combined API endpoint and data models for unified search results
  • Refactors search managers to support both auto-complete and combined search functionality
  • Updates UI components to handle improved search results with floating filters and playback status

Reviewed Changes

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

Show a summary per file
File Description
PlayButton.kt Adds playback position tracking for better episode state management
CombinedSearchResponse.kt Defines API response models for combined search results
CombinedSearchRequest.kt Defines API request model for combined search
AutoCompleteSearchService.kt Renames interface from SearchService for clarity
PodcastCacheService.kt Adds combined search endpoint definition
ServersModule.kt Updates DI configuration for new search services and JSON adapters
SearchAutoCompleteManagerImpl.kt Removes old implementation in favor of unified approach
ImprovedSearchManagerImpl.kt Implements new unified search manager
ImprovedSearchManager.kt Defines interface for combined search functionality
RepositoryModule.kt Updates DI bindings for new search manager
SearchHistoryEntry.kt Adds factory methods for improved search result items
ImprovedSearchResultItem.kt Defines data models for improved search results
SearchResultFilters.kt Adds background styling for floating filter chips
ImprovedSearchPodcastResultRow.kt Refactors to handle new podcast result types
ImprovedSearchEpisodeResultRow.kt Adds playback status tracking for episodes
SearchViewModel.kt Major refactor to support both old and improved search flows
SearchPodcastResultsPage.kt Updates to use new result types
SearchInlineResultsPage.kt Updates to use segregated result types
SearchHandler.kt Adds improved search results flow implementation
SearchFragment.kt Updates to handle both old and improved search UI states
SearchEpisodeResultsPage.kt Updates to use new result types
ImprovedSearchResultsPage.kt Major UI updates with floating filters and unified result handling

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +259 to +260
if (((viewModel.state.value as? SearchUiState.OldResults)?.operation as? SearchUiState.SearchOperation.Success)?.results?.podcasts?.isNotEmpty() == true) {
// TODO check this condition above
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

This complex nested safe-cast chain is difficult to read and maintain. Consider extracting this condition into a separate function or using a when expression for better readability.

Copilot uses AI. Check for mistakes.

},
update = { playButton ->
playButton.setButtonType(episode, buttonType = PlayButtonType.PLAY, color = buttonColor, null)
val theEpisode = episode.apply {
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Using apply on a parameter that might be null could cause issues. The episode parameter should be checked for null before applying mutations, or the function should ensure it's never null.

Suggested change
val theEpisode = episode.apply {
val theEpisode = it.apply {

Copilot uses AI. Check for mistakes.

@Copilot Copilot AI review requested due to automatic review settings October 10, 2025 20:19
Copy link

@Copilot 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 23 out of 23 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

modules/features/search/src/main/java/au/com/shiftyjelly/pocketcasts/search/SearchViewModel.kt:1

  • The nested property access state.results.results.lastIndex is confusing due to the repeated 'results' name. Consider renaming one of these properties for better clarity.
package au.com.shiftyjelly.pocketcasts.search

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@wpmobilebot wpmobilebot modified the milestones: 7.100, 7.101 Oct 13, 2025
@wpmobilebot
Copy link
Collaborator

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

@sztomek sztomek marked this pull request as draft October 17, 2025 11:56
@Copilot Copilot AI review requested due to automatic review settings October 17, 2025 15:48
Copy link

@Copilot 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 24 out of 24 changed files in this pull request and generated 4 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +112 to +117
fun fromImprovedEpisodeResult(episode: ImprovedSearchResultItem.EpisodeItem) = Episode(
uuid = episode.uuid,
title = episode.title,
duration = episode.duration.inWholeSeconds.toDouble(),
podcastUuid = episode.podcastUuid,
podcastTitle = "",
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

The empty string for podcastTitle could cause issues downstream. Consider adding a TODO comment or parameter to provide the actual podcast title.

Suggested change
fun fromImprovedEpisodeResult(episode: ImprovedSearchResultItem.EpisodeItem) = Episode(
uuid = episode.uuid,
title = episode.title,
duration = episode.duration.inWholeSeconds.toDouble(),
podcastUuid = episode.podcastUuid,
podcastTitle = "",
/**
* Constructs an Episode from an ImprovedSearchResultItem.EpisodeItem.
* @param episode The episode item.
* @param podcastTitle The title of the podcast. TODO: Provide the actual podcast title if available.
*/
fun fromImprovedEpisodeResult(
episode: ImprovedSearchResultItem.EpisodeItem,
podcastTitle: String // TODO: Provide the actual podcast title if available
) = Episode(
uuid = episode.uuid,
title = episode.title,
duration = episode.duration.inWholeSeconds.toDouble(),
podcastUuid = episode.podcastUuid,
podcastTitle = podcastTitle,

Copilot uses AI. Check for mistakes.

Comment on lines +91 to +92
flow<BaseEpisode> { emit(episodeManager.findByUuid(episodeUuid)!!) }
}
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

Force unwrapping with !! can cause crashes if the episode is not found. Use safe navigation or handle the null case gracefully.

Suggested change
flow<BaseEpisode> { emit(episodeManager.findByUuid(episodeUuid)!!) }
}
flow<BaseEpisode> {
val episode = episodeManager.findByUuid(episodeUuid)
if (episode != null) {
emit(episode)
} else {
throw NoSuchElementException("Episode not found for uuid: $episodeUuid")
}
}

Copilot uses AI. Check for mistakes.

playedUpToMs = if (state.playbackState.episodeUuid == episodeUuid) {
state.playbackState.positionMs
} else {
0
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

Setting playedUpToMs to 0 when the episode is not currently playing loses the actual progress. This should use the episode's stored playedUpToMs value instead.

Suggested change
0
state.episode.playedUpToMs

Copilot uses AI. Check for mistakes.

Comment on lines +259 to 261
if (((viewModel.state.value as? SearchUiState.OldResults)?.operation as? SearchUiState.SearchOperation.Success)?.results?.podcasts?.isNotEmpty() == true) {
// TODO check this condition above
binding.searchHistoryPanel.hide()
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

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

The TODO comment indicates incomplete logic. The condition for showing/hiding search history panel needs to be reviewed and implemented properly.

Suggested change
if (((viewModel.state.value as? SearchUiState.OldResults)?.operation as? SearchUiState.SearchOperation.Success)?.results?.podcasts?.isNotEmpty() == true) {
// TODO check this condition above
binding.searchHistoryPanel.hide()
// Hide search history panel if query is not blank (i.e., user is typing a feed URL)
if (characterCount > 0) {
binding.searchHistoryPanel.hide()
} else {
binding.searchHistoryPanel.show()

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Area] Search [Type] Feature Adding a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants