Add radio stations to search results#166
Conversation
Display radio stations alongside songs, albums, artists, and podcasts in the search screen, parsed from the API's radio_stations field.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds radio station support to search: extends SearchResult with a radioStations list, parses radio_stations in SearchProvider, introduces RadioStationCard widget and UI section, exports the widget, and adds unit/widget tests. Changes
Sequence DiagramsequenceDiagram
participant User
participant SearchScreen
participant SearchProvider
participant AppState
participant RadioStationCard
participant RadioPlayerProvider
User->>SearchScreen: Enter query
SearchScreen->>SearchProvider: searchExcerpts(query)
SearchProvider->>SearchProvider: Parse API response (includes radio_stations)
SearchProvider->>AppState: Save SearchResult (with radioStations)
SearchScreen->>AppState: Read updated SearchResult
SearchScreen->>SearchScreen: Update _radioStations and rebuild UI
SearchScreen->>RadioStationCard: Render card per station
User->>RadioStationCard: Tap card
RadioStationCard->>RadioPlayerProvider: play(station)
RadioPlayerProvider->>RadioPlayerProvider: Start playback
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (5)
lib/ui/widgets/radio_station_card.dart (1)
47-53: Consider adding a placeholder for the loading state.
CachedNetworkImagehas anerrorWidgetfor failed loads, but noplaceholderfor the loading state. Users will see an empty area while the logo loads. Adding a placeholder improves perceived performance.♻️ Suggested improvement
? CachedNetworkImage( imageUrl: widget.station.logo!, width: _cardWidth, height: _cardWidth, fit: BoxFit.cover, + placeholder: (_, __) => _defaultIcon(), errorWidget: (_, __, ___) => _defaultIcon(), )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/ui/widgets/radio_station_card.dart` around lines 47 - 53, The CachedNetworkImage usage inside the RadioStationCard currently supplies only errorWidget and no placeholder, causing an empty area while the logo loads; update the CachedNetworkImage (in the RadioStationCard widget where imageUrl: widget.station.logo! is used) to include a placeholder parameter that returns a lightweight loading UI (e.g., the same _defaultIcon() or a SizedBox with a spinner) so users see a fallback during network fetch; ensure the placeholder is fast and visually consistent with the errorWidget.lib/providers/search_provider.dart (1)
40-40: Pre-existing: Search keywords are not URL-encoded.The
keywordsvariable is interpolated directly into the URL without encoding. Special characters like&,=,#, or spaces could break the query or cause unexpected behavior. While this is pre-existing code, consider URL-encoding for robustness.♻️ Suggested fix
- final res = await get('search?q=$keywords'); + final res = await get('search?q=${Uri.encodeQueryComponent(keywords)}');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/providers/search_provider.dart` at line 40, The search URL interpolates keywords directly into get('search?q=$keywords'), which can break on spaces or reserved characters; update the call to URL-encode the query (use Uri.encodeQueryComponent or build the URI with queryParameters) so the keywords are safely encoded before calling get — change the invocation that references keywords in search_provider.dart to use the encoded value (e.g., replace the direct interpolation of keywords with an encoded query component or a Uri with queryParameters).test/ui/widgets/radio_station_card_test.dart (2)
42-51: Consider adding a test for logo image rendering.The tests verify the default icon when no logo is present, but there's no test verifying that
CachedNetworkImageis rendered whenstation.logois provided. This would increase coverage of the conditional logo rendering logic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/ui/widgets/radio_station_card_test.dart` around lines 42 - 51, Add a new widget test mirroring the existing 'shows default icon when no logo' test that sets a RadioStation with a non-null logo (use RadioStation.fake(name: ..., logo: 'https://...') or the factory field) and pumps RadioStationCard(station: station); then assert the logo branch renders by expecting find.byType(CachedNetworkImage) (and optionally verify the image URL via widget inspection) to cover the conditional rendering in RadioStationCard and ensure CachedNetworkImage is used when station.logo is provided.
31-40: Brittle assertion on Text widget count.The assertion
find.byType(Text), findsOneWidget(line 39) is fragile. If the widget hierarchy changes or the test harness adds surrounding Text widgets, this test will break unexpectedly. Consider a more targeted assertion.♻️ Suggested alternative
- // Only the name text widget should be present - expect(find.byType(Text), findsOneWidget); + // Description should not be rendered + expect(find.text('The best jazz station'), findsNothing);This directly asserts the absence of a description rather than relying on Text widget counts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/ui/widgets/radio_station_card_test.dart` around lines 31 - 40, The test "does not render description when absent" uses a brittle assertion checking Text widget count; instead, update this test (the testWidgets block that instantiates RadioStationCard with RadioStation.fake) to assert explicitly that the description string is not present (e.g., expect a finder for the expected description text to findNothing) or use a more specific finder targeting the description widget, rather than relying on find.byType(Text) to equal one.lib/ui/screens/search.dart (1)
182-197: Add Feature.radioStations enum and feature-gate the radio stations UI section.The backend defensively handles missing
radio_stations(similar to podcasts with its "backward compatibility" comment), but the UI lacks feature-gating. Since podcasts are feature-gated withFeature.podcasts.isSupported()at line 165, radio stations should follow the same pattern—add aFeature.radioStationsenum entry tolib/utils/features.dartwith the appropriate version, then wrap the radio stations section (lines 182-197) with a feature check for consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/ui/screens/search.dart` around lines 182 - 197, Add a new Feature enum member named radioStations (e.g., Feature.radioStations) in the features enum with the appropriate introduced version number (matching how podcasts is declared) and ensure it exposes isSupported(); then wrap the radio stations UI block (the Heading5 'Radio Stations', the _radioStations.isEmpty check, and the HorizontalCardScroller that builds RadioStationCard with station) in a feature gate identical to podcasts (if (Feature.radioStations.isSupported()) { ... }) so the section only renders when the feature is supported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/providers/search_provider.dart`:
- Line 40: The search URL interpolates keywords directly into
get('search?q=$keywords'), which can break on spaces or reserved characters;
update the call to URL-encode the query (use Uri.encodeQueryComponent or build
the URI with queryParameters) so the keywords are safely encoded before calling
get — change the invocation that references keywords in search_provider.dart to
use the encoded value (e.g., replace the direct interpolation of keywords with
an encoded query component or a Uri with queryParameters).
In `@lib/ui/screens/search.dart`:
- Around line 182-197: Add a new Feature enum member named radioStations (e.g.,
Feature.radioStations) in the features enum with the appropriate introduced
version number (matching how podcasts is declared) and ensure it exposes
isSupported(); then wrap the radio stations UI block (the Heading5 'Radio
Stations', the _radioStations.isEmpty check, and the HorizontalCardScroller that
builds RadioStationCard with station) in a feature gate identical to podcasts
(if (Feature.radioStations.isSupported()) { ... }) so the section only renders
when the feature is supported.
In `@lib/ui/widgets/radio_station_card.dart`:
- Around line 47-53: The CachedNetworkImage usage inside the RadioStationCard
currently supplies only errorWidget and no placeholder, causing an empty area
while the logo loads; update the CachedNetworkImage (in the RadioStationCard
widget where imageUrl: widget.station.logo! is used) to include a placeholder
parameter that returns a lightweight loading UI (e.g., the same _defaultIcon()
or a SizedBox with a spinner) so users see a fallback during network fetch;
ensure the placeholder is fast and visually consistent with the errorWidget.
In `@test/ui/widgets/radio_station_card_test.dart`:
- Around line 42-51: Add a new widget test mirroring the existing 'shows default
icon when no logo' test that sets a RadioStation with a non-null logo (use
RadioStation.fake(name: ..., logo: 'https://...') or the factory field) and
pumps RadioStationCard(station: station); then assert the logo branch renders by
expecting find.byType(CachedNetworkImage) (and optionally verify the image URL
via widget inspection) to cover the conditional rendering in RadioStationCard
and ensure CachedNetworkImage is used when station.logo is provided.
- Around line 31-40: The test "does not render description when absent" uses a
brittle assertion checking Text widget count; instead, update this test (the
testWidgets block that instantiates RadioStationCard with RadioStation.fake) to
assert explicitly that the description string is not present (e.g., expect a
finder for the expected description text to findNothing) or use a more specific
finder targeting the description widget, rather than relying on
find.byType(Text) to equal one.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2909e546-7b62-4c91-abee-484f5397530d
📒 Files selected for processing (6)
lib/providers/search_provider.dartlib/ui/screens/search.dartlib/ui/widgets/radio_station_card.dartlib/ui/widgets/widgets.darttest/providers/search_provider_test.darttest/ui/widgets/radio_station_card_test.dart
Add loading placeholder to RadioStationCard's CachedNetworkImage, matching the existing pattern in AlbumArtistThumbnail. Improve test coverage with a CachedNetworkImage rendering test and fix brittle Text widget count assertion.
Summary
radio_stationsfrom the search API response intoSearchResultRadioStationCardwidget with logo support and fallback antenna iconHorizontalCardScrollerTest plan
SearchResultcorrectly stores radio stations (3 tests)RadioStationCardrenders name, description, icon, and handles tap (5 tests)ExcerptSearchTestupdated to assertradio_stationsin responseSummary by CodeRabbit
Release Notes
New Features
Tests