feat: add show catalog type option

Closes #1556
This commit is contained in:
tapframe 2026-07-18 14:17:39 +05:30
parent 99d1cf345f
commit 65e38b0da6
10 changed files with 94 additions and 1 deletions

View file

@ -605,6 +605,8 @@
<string name="settings_homescreen_show_hero_description">Display hero carousel at top of home.</string>
<string name="layout_hide_unreleased">Hide Unreleased Content</string>
<string name="layout_hide_unreleased_sub">Hide movies and shows that haven't been released yet.</string>
<string name="layout_catalog_type">Show Catalog Type</string>
<string name="layout_catalog_type_sub">Show type suffix next to catalog name (Movie/Series).</string>
<string name="settings_homescreen_hide_catalog_underline">Hide Catalog Underline</string>
<string name="settings_homescreen_hide_catalog_underline_description">Remove the accent line under catalog and collection titles throughout the app.</string>
<string name="settings_homescreen_summary">%1$d of %2$d catalogs visible • %3$d hero sources selected</string>

View file

@ -14,6 +14,7 @@ import org.jetbrains.compose.resources.getString
data class HomeCatalogDefinition(
val key: String,
val defaultTitle: String,
val catalogName: String,
val addonName: String,
val manifestUrl: String,
val type: String,
@ -23,6 +24,9 @@ data class HomeCatalogDefinition(
) {
val cacheKey: String
get() = "$key|$descriptorSignature"
fun titleFor(showCatalogType: Boolean): String =
if (showCatalogType) defaultTitle else catalogName
}
fun buildHomeCatalogRefreshSignature(addons: List<ManagedAddon>): List<String> =
@ -52,6 +56,7 @@ fun buildHomeCatalogDefinitions(addons: List<ManagedAddon>): List<HomeCatalogDef
localizedMediaTypeLabel(catalog.type),
)
},
catalogName = catalog.name,
addonName = addon.displayTitle,
manifestUrl = addon.manifestUrl,
type = catalog.type,

View file

@ -32,6 +32,7 @@ data class HomeCatalogSettingsItem(
data class HomeCatalogSettingsUiState(
val heroEnabled: Boolean = true,
val showCatalogType: Boolean = true,
val hideUnreleasedContent: Boolean = false,
val hideCatalogUnderline: Boolean = false,
val items: List<HomeCatalogSettingsItem> = emptyList(),
@ -40,6 +41,8 @@ data class HomeCatalogSettingsUiState(
get() = buildString {
append(heroEnabled)
append('|')
append(showCatalogType)
append('|')
append(hideUnreleasedContent)
append('|')
append(hideCatalogUnderline)
@ -61,6 +64,7 @@ internal data class HomeCatalogPreference(
internal data class HomeCatalogSettingsSnapshot(
val heroEnabled: Boolean,
val showCatalogType: Boolean,
val hideUnreleasedContent: Boolean,
val hideCatalogUnderline: Boolean,
val preferences: Map<String, HomeCatalogPreference>,
@ -78,6 +82,7 @@ private data class StoredHomeCatalogPreference(
@Serializable
private data class StoredHomeCatalogSettingsPayload(
val heroEnabled: Boolean = true,
val showCatalogType: Boolean = true,
val hideUnreleasedContent: Boolean = false,
val hideCatalogUnderline: Boolean = false,
val items: List<StoredHomeCatalogPreference> = emptyList(),
@ -99,6 +104,7 @@ object HomeCatalogSettingsRepository {
private var collectionDefinitions: List<CollectionCatalogDefinition> = emptyList()
private var preferences: MutableMap<String, StoredHomeCatalogPreference> = mutableMapOf()
private var heroEnabled = true
private var showCatalogType = true
private var hideUnreleasedContent = false
private var hideCatalogUnderline = false
@ -106,6 +112,7 @@ object HomeCatalogSettingsRepository {
hasLoaded = false
preferences.clear()
heroEnabled = true
showCatalogType = true
hideUnreleasedContent = false
hideCatalogUnderline = false
definitions = emptyList()
@ -119,6 +126,7 @@ object HomeCatalogSettingsRepository {
collectionDefinitions = emptyList()
preferences.clear()
heroEnabled = true
showCatalogType = true
hideUnreleasedContent = false
hideCatalogUnderline = false
_uiState.value = HomeCatalogSettingsUiState()
@ -152,6 +160,7 @@ object HomeCatalogSettingsRepository {
ensureLoaded()
return HomeCatalogSettingsSnapshot(
heroEnabled = heroEnabled,
showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
preferences = preferences.mapValues { (_, value) ->
@ -173,6 +182,16 @@ object HomeCatalogSettingsRepository {
HomeRepository.applyCurrentSettings()
}
fun setShowCatalogType(enabled: Boolean) {
ensureLoaded()
if (showCatalogType == enabled) return
showCatalogType = enabled
publish()
persist()
HomeRepository.applyCurrentSettings()
HomeCatalogSettingsSyncService.triggerPush()
}
fun setHideUnreleasedContent(enabled: Boolean) {
ensureLoaded()
if (hideUnreleasedContent == enabled) return
@ -219,6 +238,7 @@ object HomeCatalogSettingsRepository {
fun resetToDefaults() {
ensureLoaded()
heroEnabled = true
showCatalogType = true
hideUnreleasedContent = false
hideCatalogUnderline = false
preferences.clear()
@ -268,6 +288,7 @@ object HomeCatalogSettingsRepository {
if (parsedPayload != null) {
heroEnabled = parsedPayload.heroEnabled
showCatalogType = parsedPayload.showCatalogType
hideUnreleasedContent = parsedPayload.hideUnreleasedContent
hideCatalogUnderline = parsedPayload.hideCatalogUnderline
preferences = parsedPayload.items.associateBy { it.key }.toMutableMap()
@ -368,6 +389,7 @@ object HomeCatalogSettingsRepository {
_uiState.value = HomeCatalogSettingsUiState(
heroEnabled = heroEnabled,
showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
items = items,
@ -379,6 +401,7 @@ object HomeCatalogSettingsRepository {
json.encodeToString(
StoredHomeCatalogSettingsPayload(
heroEnabled = heroEnabled,
showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
items = preferences.values.sortedBy { it.order },
@ -475,6 +498,7 @@ object HomeCatalogSettingsRepository {
}
}
return SyncHomeCatalogPayload(
showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
items = items,
@ -483,6 +507,7 @@ object HomeCatalogSettingsRepository {
fun applyFromRemote(payload: SyncHomeCatalogPayload) {
ensureLoaded()
showCatalogType = payload.showCatalogType
hideUnreleasedContent = payload.hideUnreleasedContent
hideCatalogUnderline = payload.hideCatalogUnderline
if (payload.items.isNotEmpty()) {

View file

@ -40,6 +40,7 @@ data class SyncCatalogItem(
@Serializable
data class SyncHomeCatalogPayload(
@SerialName("show_catalog_type") val showCatalogType: Boolean = true,
@SerialName("hide_unreleased_content") val hideUnreleasedContent: Boolean = false,
@SerialName("hide_catalog_underline") val hideCatalogUnderline: Boolean = false,
val items: List<SyncCatalogItem> = emptyList(),
@ -56,6 +57,7 @@ private data class RemoteHomeCatalogSettings(
val platform: String,
val payload: SyncHomeCatalogPayload,
val updatedAt: String?,
val hasShowCatalogType: Boolean,
val hasHideUnreleasedContent: Boolean,
val hasHideCatalogUnderline: Boolean,
)
@ -74,6 +76,7 @@ object HomeCatalogSettingsSyncService {
}
private const val HIDE_UNRELEASED_CONTENT_KEY = "hide_unreleased_content"
private const val SHOW_CATALOG_TYPE_KEY = "show_catalog_type"
private const val HIDE_CATALOG_UNDERLINE_KEY = "hide_catalog_underline"
@Volatile
@ -216,6 +219,7 @@ object HomeCatalogSettingsSyncService {
platform = platform,
payload = payload,
updatedAt = blob.updatedAt,
hasShowCatalogType = blob.settingsJson.containsKey(SHOW_CATALOG_TYPE_KEY),
hasHideUnreleasedContent = blob.settingsJson.containsKey(HIDE_UNRELEASED_CONTENT_KEY),
hasHideCatalogUnderline = blob.settingsJson.containsKey(HIDE_CATALOG_UNDERLINE_KEY),
)
@ -227,12 +231,17 @@ object HomeCatalogSettingsSyncService {
val hideUnreleasedSource = rows
.filter { it.hasHideUnreleasedContent }
.maxByOrNull { it.updatedAt.orEmpty() }
val showCatalogTypeSource = rows
.filter { it.hasShowCatalogType }
.maxByOrNull { it.updatedAt.orEmpty() }
val hideUnderlineSource = rows
.filter { it.hasHideCatalogUnderline }
.maxByOrNull { it.updatedAt.orEmpty() }
return copy(
payload = payload.copy(
showCatalogType = showCatalogTypeSource?.payload?.showCatalogType
?: payload.showCatalogType,
hideUnreleasedContent = hideUnreleasedSource?.payload?.hideUnreleasedContent
?: payload.hideUnreleasedContent,
hideCatalogUnderline = hideUnderlineSource?.payload?.hideCatalogUnderline
@ -259,6 +268,11 @@ object HomeCatalogSettingsSyncService {
): SyncHomeCatalogPayload? = runCatching {
val decoded = json.decodeFromJsonElement(SyncHomeCatalogPayload.serializer(), settingsJson)
decoded.copy(
showCatalogType = if (settingsJson.containsKey(SHOW_CATALOG_TYPE_KEY)) {
decoded.showCatalogType
} else {
localPayload.showCatalogType
},
hideUnreleasedContent = if (settingsJson.containsKey(HIDE_UNRELEASED_CONTENT_KEY)) {
decoded.hideUnreleasedContent
} else {

View file

@ -208,7 +208,7 @@ object HomeRepository {
if (section.items.isEmpty()) return@mapNotNull null
val customTitle = preference?.customTitle.orEmpty()
section.copy(
title = customTitle.ifBlank { section.title },
title = customTitle.ifBlank { definition.titleFor(snapshot.showCatalogType) },
)
}

View file

@ -40,6 +40,8 @@ import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.action_reset
import nuvio.composeapp.generated.resources.layout_hide_unreleased
import nuvio.composeapp.generated.resources.layout_hide_unreleased_sub
import nuvio.composeapp.generated.resources.layout_catalog_type
import nuvio.composeapp.generated.resources.layout_catalog_type_sub
import nuvio.composeapp.generated.resources.settings_homescreen_empty_message
import nuvio.composeapp.generated.resources.settings_homescreen_empty_title
import nuvio.composeapp.generated.resources.settings_homescreen_hide_catalog_underline
@ -66,6 +68,7 @@ import sh.calvin.reorderable.rememberReorderableLazyListState
internal fun LazyListScope.homescreenSettingsContent(
isTablet: Boolean,
heroEnabled: Boolean,
showCatalogType: Boolean,
hideUnreleasedContent: Boolean,
hideCatalogUnderline: Boolean,
items: List<HomeCatalogSettingsItem>,
@ -94,6 +97,14 @@ internal fun LazyListScope.homescreenSettingsContent(
onCheckedChange = HomeCatalogSettingsRepository::setHeroEnabled,
)
SettingsGroupDivider(isTablet = isTablet)
SettingsSwitchRow(
title = stringResource(Res.string.layout_catalog_type),
description = stringResource(Res.string.layout_catalog_type_sub),
checked = showCatalogType,
isTablet = isTablet,
onCheckedChange = HomeCatalogSettingsRepository::setShowCatalogType,
)
SettingsGroupDivider(isTablet = isTablet)
SettingsSwitchRow(
title = stringResource(Res.string.layout_hide_unreleased),
description = stringResource(Res.string.layout_hide_unreleased_sub),

View file

@ -79,6 +79,7 @@ fun HomescreenSettingsScreen(
homescreenSettingsContent(
isTablet = false,
heroEnabled = homescreenSettingsUiState.heroEnabled,
showCatalogType = homescreenSettingsUiState.showCatalogType,
hideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
hideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
items = homescreenSettingsUiState.items,

View file

@ -403,6 +403,7 @@ fun SettingsScreen(
traktCommentsEnabled = traktCommentsEnabled,
traktSettingsUiState = traktSettingsUiState,
homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled,
homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType,
homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
homescreenHideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
homescreenItems = homescreenSettingsUiState.items,
@ -463,6 +464,7 @@ fun SettingsScreen(
traktCommentsEnabled = traktCommentsEnabled,
traktSettingsUiState = traktSettingsUiState,
homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled,
homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType,
homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
homescreenHideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
homescreenItems = homescreenSettingsUiState.items,
@ -533,6 +535,7 @@ private fun MobileSettingsScreen(
traktCommentsEnabled: Boolean,
traktSettingsUiState: TraktSettingsUiState,
homescreenHeroEnabled: Boolean,
homescreenShowCatalogType: Boolean,
homescreenHideUnreleasedContent: Boolean,
homescreenHideCatalogUnderline: Boolean,
homescreenItems: List<HomeCatalogSettingsItem>,
@ -763,6 +766,7 @@ private fun MobileSettingsScreen(
SettingsPage.Homescreen -> homescreenSettingsContent(
isTablet = false,
heroEnabled = homescreenHeroEnabled,
showCatalogType = homescreenShowCatalogType,
hideUnreleasedContent = homescreenHideUnreleasedContent,
hideCatalogUnderline = homescreenHideCatalogUnderline,
items = homescreenItems,
@ -889,6 +893,7 @@ private fun TabletSettingsScreen(
traktCommentsEnabled: Boolean,
traktSettingsUiState: TraktSettingsUiState,
homescreenHeroEnabled: Boolean,
homescreenShowCatalogType: Boolean,
homescreenHideUnreleasedContent: Boolean,
homescreenHideCatalogUnderline: Boolean,
homescreenItems: List<HomeCatalogSettingsItem>,
@ -1175,6 +1180,7 @@ private fun TabletSettingsScreen(
SettingsPage.Homescreen -> homescreenSettingsContent(
isTablet = true,
heroEnabled = homescreenHeroEnabled,
showCatalogType = homescreenShowCatalogType,
hideUnreleasedContent = homescreenHideUnreleasedContent,
hideCatalogUnderline = homescreenHideCatalogUnderline,
items = homescreenItems,

View file

@ -730,6 +730,7 @@ internal fun settingsSearchEntries(
val homeLayoutSection = stringResource(Res.string.settings_homescreen_section_hero)
listOf(
PlaybackSearchRow("home-hero", stringResource(Res.string.settings_homescreen_show_hero), stringResource(Res.string.settings_homescreen_show_hero_description)),
PlaybackSearchRow("home-catalog-type", stringResource(Res.string.layout_catalog_type), stringResource(Res.string.layout_catalog_type_sub)),
PlaybackSearchRow("home-hide-unreleased", stringResource(Res.string.layout_hide_unreleased), stringResource(Res.string.layout_hide_unreleased_sub)),
PlaybackSearchRow("home-hide-catalog-underline", stringResource(Res.string.settings_homescreen_hide_catalog_underline), stringResource(Res.string.settings_homescreen_hide_catalog_underline_description)),
PlaybackSearchRow("home-hero-sources", stringResource(Res.string.settings_homescreen_section_hero_sources)),

View file

@ -0,0 +1,28 @@
package com.nuvio.app.features.home
import kotlin.test.Test
import kotlin.test.assertEquals
class HomeCatalogDefinitionTest {
private val definition = HomeCatalogDefinition(
key = "addon:movie:popular",
defaultTitle = "Popular - Movie",
catalogName = "Popular",
addonName = "Addon",
manifestUrl = "https://example.com/manifest.json",
type = "movie",
catalogId = "popular",
supportsPagination = true,
descriptorSignature = "signature",
)
@Test
fun `shows the type suffix by default`() {
assertEquals("Popular - Movie", definition.titleFor(showCatalogType = true))
}
@Test
fun `omits the type suffix when disabled`() {
assertEquals("Popular", definition.titleFor(showCatalogType = false))
}
}