140 lines
5.9 KiB
Markdown
140 lines
5.9 KiB
Markdown
# Station Discovery & Browse Specification
|
|
|
|
## Purpose
|
|
|
|
Discovery content as the Buscar tab's landing state, active-filter pills,
|
|
results counter, clear-all-filters, the Países browser over the verified
|
|
`/json/countries` contract, and a client-side search-sort control. Traceable
|
|
to WU5 (relocation), WU6 (Buscar), WU7 (Países + `_get` transport
|
|
extraction).
|
|
|
|
## Requirements
|
|
|
|
### Requirement: Buscar Landing State Shows Discovery Content
|
|
|
|
Buscar MUST render the existing discovery content (near-you, genres,
|
|
trending, countries entry point) as its landing state whenever no search
|
|
query is entered. Entering a query MUST replace that landing state with
|
|
search results.
|
|
|
|
#### Scenario: Empty query shows discovery content
|
|
|
|
- GIVEN the user opens the Buscar tab with no prior query
|
|
- WHEN the screen renders
|
|
- THEN near-you, genre, trending, and a Países entry point are visible
|
|
|
|
#### Scenario: Entering a query replaces the landing state
|
|
|
|
- GIVEN the Buscar tab is showing discovery content
|
|
- WHEN the user types a non-empty query
|
|
- THEN discovery content is replaced by the search-results view
|
|
|
|
### Requirement: Active-Filter Pills and Results Counter
|
|
|
|
The search-results view MUST show each active filter (country, language,
|
|
minimum quality) as a removable pill, and MUST show a results counter
|
|
("N RESULTADOS").
|
|
|
|
#### Scenario: Applying a filter shows a removable pill
|
|
|
|
- GIVEN the user has an active search
|
|
- WHEN they apply a country filter
|
|
- THEN a pill labeled with that country and a close (x) affordance appears
|
|
- AND the results counter reflects the filtered count
|
|
|
|
#### Scenario: Removing a pill re-runs the search without that filter
|
|
|
|
- GIVEN a country filter pill is active
|
|
- WHEN the user taps its close affordance
|
|
- THEN the filter is cleared and results/count update accordingly
|
|
|
|
### Requirement: One-Tap Clear-All-Filters on Empty Results
|
|
|
|
When 1 or more filters are active and the resulting search has zero results,
|
|
the system MUST offer a single action that clears every active filter at
|
|
once ("Quitar los N filtros").
|
|
|
|
#### Scenario: Two active filters, zero results
|
|
|
|
- GIVEN country and quality filters are both active and yield 0 results
|
|
- WHEN the empty state renders
|
|
- THEN a "Quitar los 2 filtros" action is shown
|
|
- AND tapping it clears both filters in one action, not one at a time
|
|
|
|
### Requirement: Países Browser Over the Verified Countries Contract
|
|
|
|
The Países screen (new second-level screen) MUST fetch from
|
|
**`/json/countries`** (optional substring filter `/json/countries/{filter}`)
|
|
and MUST NOT use `/json/countrycodes`, which is deprecated. It MUST parse
|
|
the `stationcount` field as a **string** via `int.tryParse`, never as a
|
|
direct `int` cast. Its request MUST NOT include `lastcheckok`, since that
|
|
parameter is meaningful only for station-listing calls.
|
|
Source: Engram `reference/radio-browser-countries-endpoint` (id 2500),
|
|
verified against <https://api.radio-browser.info/>.
|
|
|
|
#### Scenario: Countries list renders with counts
|
|
|
|
- GIVEN `/json/countries` returns a list of country objects
|
|
- WHEN the Países screen loads
|
|
- THEN "Tus idiomas" and the full alphabetical list render with each country's parsed station count
|
|
|
|
#### Scenario: `stationcount` arrives as a JSON string (edge case, critical)
|
|
|
|
- GIVEN a country entry has `"stationcount": "482"` (a JSON string, not a number)
|
|
- WHEN the screen parses and renders that entry
|
|
- THEN it displays `482` without throwing a type-cast error
|
|
|
|
#### Scenario: Countries request omits the station-only filter
|
|
|
|
- GIVEN the Países screen issues its `/json/countries` request
|
|
- WHEN the outgoing request is inspected
|
|
- THEN it does NOT include `lastcheckok`
|
|
|
|
### Requirement: Client-Side Search Sort Only
|
|
|
|
Buscar MUST expose an "Ordenar" control backed exclusively by client-side
|
|
ordering (`OrdenEmisoras`). The system MUST NOT adopt the Radio Browser
|
|
server-side `order`/`reverse` parameters for this control, because
|
|
server-side ordering sorts the full result set before `limit` is applied
|
|
while the app's client-side sort only reorders the page already fetched —
|
|
adopting it would silently change which stations users see, not just their
|
|
order. Every sort option rendered in the UI MUST map to a real
|
|
`OrdenEmisoras` case; the system MUST NOT render an option that does not
|
|
actually sort.
|
|
Source: Engram `reference/radio-browser-sort-order` (id 2505), verified
|
|
against <https://api.radio-browser.info/>.
|
|
|
|
#### Scenario: Selecting a sort option reorders the current page only
|
|
|
|
- GIVEN a set of search results is already fetched
|
|
- WHEN the user selects "Ordenar: calidad"
|
|
- THEN the currently-displayed page is reordered via `OrdenEmisoras.calidad`
|
|
- AND no new network request with an `order` parameter is issued
|
|
|
|
#### Scenario: Every rendered sort option is backed by a real case (regression guard)
|
|
|
|
- GIVEN the "Ordenar" control's rendered option list
|
|
- WHEN each option is checked against `OrdenEmisoras`
|
|
- THEN every option maps to an existing enum case with test coverage; no decorative option that does not sort is rendered
|
|
|
|
### Requirement: Existing Station Calls Unchanged by Transport Extraction
|
|
|
|
Extracting `_get`'s transport loop (server discovery, host rotation,
|
|
bounded retries, User-Agent, timeout, status check, `json.decode`) into a
|
|
shared helper for the Países call MUST NOT change the observable behavior
|
|
of the 8 existing `ServicioRadio` station methods: `obtenerPopulares`,
|
|
`obtenerTendencias`, `buscarPorNombre`, `buscarPorPais`, `buscarPorIdioma`,
|
|
`buscarPorTag`, `buscar`, `registrarClick`.
|
|
|
|
#### Scenario: Characterization tests pass unchanged before and after extraction
|
|
|
|
- GIVEN characterization tests exist for all 8 station methods
|
|
- WHEN the transport loop is extracted from `_get`
|
|
- THEN all 8 tests pass identically before and after the extraction
|
|
|
|
#### Scenario: Station calls still send `lastcheckok`
|
|
|
|
- GIVEN any of the 8 station methods issues a request
|
|
- WHEN the outgoing request is inspected
|
|
- THEN it still includes `lastcheckok=1`, unchanged from before the extraction
|