Add pagination support for discover catalog browsing

Discover only ever fetched the first page (typically 20-50 items) of a
catalog and had no way to request more. Add a discoverPageRequested
action, a fetchDiscoverPage effect, and a dedicated DiscoverPaging
generation key so an in-flight "load more" fetch can't clobber a fresh
query (or vice versa) when the user changes catalog/genre mid-fetch.
This commit is contained in:
KhooLy 2026-07-12 00:53:53 +03:00
parent 323b46d9ae
commit 24a0e0dcb2
5 changed files with 96 additions and 1 deletions

View file

@ -235,6 +235,14 @@ pub(super) enum AppAction {
profile: Option<Value>,
language: Option<String>,
},
#[serde(rename = "discoverPageRequested")]
DiscoverPageRequested {
transport_url: Option<String>,
content_type: String,
catalog_id: String,
skip: Option<i32>,
genre: Option<String>,
},
#[serde(rename = "catalogPageRequested")]
CatalogPageRequested {
category_id: String,

View file

@ -18,6 +18,25 @@ pub(super) struct DiscoverState {
genres: Value,
error: Value,
generation: u64,
paging: DiscoverPaging,
}
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", default)]
pub(super) struct DiscoverPaging {
is_loading: bool,
items: Value,
error: Value,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct FetchDiscoverPagePayload {
transport_url: Option<String>,
content_type: String,
catalog_id: String,
skip: i32,
genre: Option<String>,
}
#[derive(Serialize)]
@ -48,6 +67,7 @@ pub(super) fn dispatch_discover(
language: Option<String>,
) -> Vec<EffectEnvelope> {
let generation = engine.bump_generation(GenerationKey::Discover);
engine.bump_generation(GenerationKey::DiscoverPaging);
let profile_value = profile.unwrap_or_else(|| engine.state.profile.active.clone());
let profile_id = active_profile_id(&engine.state, &profile_value);
let filters_value = filters.unwrap_or(Value::Null);
@ -62,6 +82,7 @@ pub(super) fn dispatch_discover(
genres: engine.state.discover.genres.clone(),
error: Value::Null,
generation,
paging: DiscoverPaging::default(),
};
vec![engine.effect(
EffectKind::RunDiscover,
@ -102,6 +123,33 @@ pub(super) fn dispatch_catalog_filters(
)]
}
pub(super) fn dispatch_discover_page(
engine: &mut HeadlessEngine,
transport_url: Option<String>,
content_type: String,
catalog_id: String,
skip: Option<i32>,
genre: Option<String>,
) -> Vec<EffectEnvelope> {
let generation = engine.bump_generation(GenerationKey::DiscoverPaging);
engine.state.discover.paging = DiscoverPaging {
is_loading: true,
items: Value::Null,
error: Value::Null,
};
vec![engine.effect(
EffectKind::FetchDiscoverPage,
generation,
FetchDiscoverPagePayload {
transport_url,
content_type,
catalog_id,
skip: skip.unwrap_or(0).max(0),
genre,
},
)]
}
pub(super) fn complete(
engine: &mut HeadlessEngine,
effect_type: &str,
@ -149,6 +197,21 @@ pub(super) fn complete(
}
}
}
"fetchDiscoverPage" => {
if generation == engine.state.runtime.get(GenerationKey::DiscoverPaging) {
engine.state.discover.paging.is_loading = false;
if result.status.is_ok() {
engine.state.discover.paging.items = result
.value
.get("items")
.cloned()
.unwrap_or_else(|| serde_json::json!([]));
engine.state.discover.paging.error = Value::Null;
} else {
engine.state.discover.paging.error = normalize_error(result.error.clone());
}
}
}
_ => {}
}
vec![]

View file

@ -442,6 +442,20 @@ impl HeadlessEngine {
profile,
language,
),
AppAction::DiscoverPageRequested {
transport_url,
content_type,
catalog_id,
skip,
genre,
} => discover::dispatch_discover_page(
self,
transport_url,
content_type,
catalog_id,
skip,
genre,
),
AppAction::CatalogPageRequested {
category_id,
transport_url,
@ -605,7 +619,9 @@ impl HeadlessEngine {
EffectKind::RunSearch => search::complete(self, generation, &result),
EffectKind::RunDiscover | EffectKind::ReadDiscoverCatalogFilters => {
EffectKind::RunDiscover
| EffectKind::ReadDiscoverCatalogFilters
| EffectKind::FetchDiscoverPage => {
discover::complete(self, effect_type, generation, &result)
}

View file

@ -89,6 +89,7 @@ pub(super) enum GenerationKey {
Addon,
Search,
Discover,
DiscoverPaging,
Sync,
Auth,
Settings,
@ -110,6 +111,7 @@ pub(super) struct RuntimeGenerations {
addon_generation: u64,
search_generation: u64,
discover_generation: u64,
discover_paging_generation: u64,
sync_generation: u64,
auth_generation: u64,
settings_generation: u64,
@ -131,6 +133,7 @@ impl RuntimeGenerations {
GenerationKey::Addon => self.addon_generation,
GenerationKey::Search => self.search_generation,
GenerationKey::Discover => self.discover_generation,
GenerationKey::DiscoverPaging => self.discover_paging_generation,
GenerationKey::Sync => self.sync_generation,
GenerationKey::Auth => self.auth_generation,
GenerationKey::Settings => self.settings_generation,
@ -152,6 +155,7 @@ impl RuntimeGenerations {
GenerationKey::Addon => &mut self.addon_generation,
GenerationKey::Search => &mut self.search_generation,
GenerationKey::Discover => &mut self.discover_generation,
GenerationKey::DiscoverPaging => &mut self.discover_paging_generation,
GenerationKey::Sync => &mut self.sync_generation,
GenerationKey::Auth => &mut self.auth_generation,
GenerationKey::Settings => &mut self.settings_generation,

View file

@ -15,6 +15,7 @@ pub enum EffectKind {
FetchAddonManifest,
FetchAddonResource,
FetchCatalogPage,
FetchDiscoverPage,
FetchDetailSecondary,
FetchDetailStreams,
FetchIntroSegments,
@ -63,6 +64,7 @@ impl EffectKind {
EffectKind::FetchAddonManifest => "fetchAddonManifest",
EffectKind::FetchAddonResource => "fetchAddonResource",
EffectKind::FetchCatalogPage => "fetchCatalogPage",
EffectKind::FetchDiscoverPage => "fetchDiscoverPage",
EffectKind::FetchDetailSecondary => "fetchDetailSecondary",
EffectKind::FetchDetailStreams => "fetchDetailStreams",
EffectKind::FetchIntroSegments => "fetchIntroSegments",
@ -112,6 +114,7 @@ impl EffectKind {
"fetchAddonManifest" => EffectKind::FetchAddonManifest,
"fetchAddonResource" => EffectKind::FetchAddonResource,
"fetchCatalogPage" => EffectKind::FetchCatalogPage,
"fetchDiscoverPage" => EffectKind::FetchDiscoverPage,
"fetchDetailSecondary" => EffectKind::FetchDetailSecondary,
"fetchDetailStreams" => EffectKind::FetchDetailStreams,
"fetchIntroSegments" => EffectKind::FetchIntroSegments,
@ -212,6 +215,7 @@ mod tests {
EffectKind::FetchAddonManifest,
EffectKind::FetchAddonResource,
EffectKind::FetchCatalogPage,
EffectKind::FetchDiscoverPage,
EffectKind::FetchDetailSecondary,
EffectKind::FetchDetailStreams,
EffectKind::FetchIntroSegments,