Collection UI fixes: focus tracking, WebUI improvements, hideTitle in Modern
- Fix collection row focus restoration using key-based matching instead of positional index (survives row shifts during catalog loading) - Add per-item FocusRequesters to CollectionRowSection for proper focus restore when navigating back from folder detail - Remove Copy JSON and Paste import from Android TV (keep in WebUI) - Default import mode to File instead of Paste - WebUI: add Collection badge pill in Home Layout tab, send to top/bottom buttons, tab renamed to Home Layout, 2x2 grid for order buttons - Update subtitle to "Manage addons, catalogs, and collections" - Collections tab: rename Enable/Disable to Show/Hide All - Folder detail header icon respects tileShape aspect ratio - Respect hideTitle in Modern layout hero and card labels Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
47aba50061
commit
09c5480593
10 changed files with 156 additions and 50 deletions
|
|
@ -192,9 +192,9 @@ object AddonWebPage {
|
|||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
.addon-order {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btn-order {
|
||||
|
|
@ -317,6 +317,19 @@ object AddonWebPage {
|
|||
margin-left: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.badge-collection {
|
||||
display: inline-block;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(130, 170, 255, 0.95);
|
||||
border: 1px solid rgba(130, 170, 255, 0.35);
|
||||
padding: 0.12rem 0.45rem;
|
||||
border-radius: 100px;
|
||||
margin-left: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.status-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
|
@ -859,7 +872,7 @@ object AddonWebPage {
|
|||
|
||||
<div class="tabs">
|
||||
<button class="tab active" type="button" onclick="switchTab('addons')">Addons</button>
|
||||
<button class="tab" type="button" onclick="switchTab('catalogs')">Catalogs</button>
|
||||
<button class="tab" type="button" onclick="switchTab('catalogs')">Home Layout</button>
|
||||
<button class="tab" type="button" onclick="switchTab('collections')">Collections</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -894,8 +907,8 @@ object AddonWebPage {
|
|||
<div class="section-block">
|
||||
<div class="section-label">Collections</div>
|
||||
<div class="add-section" style="display:flex;gap:0.5rem">
|
||||
<button class="btn" onclick="enableAllCollections()" style="flex:1">Enable All</button>
|
||||
<button class="btn" onclick="disableAllCollections()" style="flex:1">Disable All</button>
|
||||
<button class="btn" onclick="enableAllCollections()" style="flex:1">Show All</button>
|
||||
<button class="btn" onclick="disableAllCollections()" style="flex:1">Hide All</button>
|
||||
</div>
|
||||
<div class="add-section" style="display:flex;gap:0.5rem">
|
||||
<button class="btn" onclick="addCollection()" style="flex:1">+ New Collection</button>
|
||||
|
|
@ -1219,15 +1232,22 @@ function renderCatalogs() {
|
|||
|
||||
li.innerHTML =
|
||||
'<div class="addon-order">' +
|
||||
'<button class="btn-order" onclick="moveCatalog(' + i + ',-1)"' + (isFirst ? ' disabled' : '') + '>' +
|
||||
'<button class="btn-order" onclick="moveCatalogToTop(' + i + ')"' + (isFirst ? ' disabled' : '') + ' title="Send to top">' +
|
||||
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11l-6-6-6 6"/><path d="M18 18l-6-6-6 6"/></svg>' +
|
||||
'</button>' +
|
||||
'<button class="btn-order" onclick="moveCatalog(' + i + ',-1)"' + (isFirst ? ' disabled' : '') + ' title="Move up">' +
|
||||
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15l-6-6-6 6"/></svg>' +
|
||||
'</button>' +
|
||||
'<button class="btn-order" onclick="moveCatalog(' + i + ',1)"' + (isLast ? ' disabled' : '') + '>' +
|
||||
'<button class="btn-order" onclick="moveCatalog(' + i + ',1)"' + (isLast ? ' disabled' : '') + ' title="Move down">' +
|
||||
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>' +
|
||||
'</button>' +
|
||||
'<button class="btn-order" onclick="moveCatalogToBottom(' + i + ')"' + (isLast ? ' disabled' : '') + ' title="Send to bottom">' +
|
||||
'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6l6 6 6-6"/><path d="M6 13l6 6 6-6"/></svg>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div class="catalog-info">' +
|
||||
'<div class="catalog-name">' + escapeHtml(formatCatalogTitle(catalog.catalogName, catalog.type)) +
|
||||
(isCollection ? '<span class="badge-collection">Collection</span>' : '') +
|
||||
(catalog.isDisabled ? '<span class="badge-disabled">${context.getString(R.string.web_badge_disabled).replace("'", "\\'")}</span>' : '') +
|
||||
'</div>' +
|
||||
'<div class="catalog-meta">' + escapeHtml(catalog.addonName) + '</div>' +
|
||||
|
|
@ -1258,6 +1278,20 @@ function moveCatalog(index, direction) {
|
|||
renderCatalogs();
|
||||
}
|
||||
|
||||
function moveCatalogToTop(index) {
|
||||
if (index <= 0) return;
|
||||
var item = catalogs.splice(index, 1)[0];
|
||||
catalogs.unshift(item);
|
||||
renderCatalogs();
|
||||
}
|
||||
|
||||
function moveCatalogToBottom(index) {
|
||||
if (index >= catalogs.length - 1) return;
|
||||
var item = catalogs.splice(index, 1)[0];
|
||||
catalogs.push(item);
|
||||
renderCatalogs();
|
||||
}
|
||||
|
||||
function toggleCatalog(index) {
|
||||
var item = catalogs[index];
|
||||
if (!item) return;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
@file:OptIn(
|
||||
androidx.compose.ui.ExperimentalComposeUiApi::class,
|
||||
androidx.tv.material3.ExperimentalTvMaterial3Api::class
|
||||
)
|
||||
|
||||
package com.nuvio.tv.ui.components
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
|
|
@ -17,12 +22,14 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.withFrameNanos
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -63,8 +70,42 @@ fun CollectionRowSection(
|
|||
val currentOnItemFocused by rememberUpdatedState(onItemFocused)
|
||||
val currentOnFolderFocused by rememberUpdatedState(onFolderFocused)
|
||||
val rowFocusRequester = remember { FocusRequester() }
|
||||
val itemFocusRequesters = remember { mutableMapOf<String, FocusRequester>() }
|
||||
var lastRequestedFocusKey by remember { mutableStateOf<String?>(null) }
|
||||
var lastFocusedItemIndex by remember { mutableIntStateOf(-1) }
|
||||
|
||||
fun folderFocusKey(index: Int, folder: CollectionFolder): String {
|
||||
return "collection_${collection.id}_folder_${folder.id}"
|
||||
}
|
||||
|
||||
// Clean up stale focus requesters when folders change
|
||||
LaunchedEffect(collection.folders) {
|
||||
val validKeys = collection.folders.mapIndexedTo(mutableSetOf()) { index, folder ->
|
||||
folderFocusKey(index, folder)
|
||||
}
|
||||
itemFocusRequesters.keys.retainAll(validKeys)
|
||||
if (lastRequestedFocusKey !in validKeys) {
|
||||
lastRequestedFocusKey = null
|
||||
}
|
||||
}
|
||||
|
||||
// Request focus on the target item when focusedItemIndex is set
|
||||
LaunchedEffect(focusedItemIndex, collection.folders) {
|
||||
if (focusedItemIndex >= 0 && focusedItemIndex < collection.folders.size) {
|
||||
val targetFolder = collection.folders[focusedItemIndex]
|
||||
val targetKey = folderFocusKey(focusedItemIndex, targetFolder)
|
||||
if (lastRequestedFocusKey == targetKey) return@LaunchedEffect
|
||||
val requester = itemFocusRequesters.getOrPut(targetKey) { FocusRequester() }
|
||||
repeat(2) { withFrameNanos { } }
|
||||
val focused = runCatching { requester.requestFocus() }.isSuccess
|
||||
if (focused) {
|
||||
lastRequestedFocusKey = targetKey
|
||||
}
|
||||
} else {
|
||||
lastRequestedFocusKey = null
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -87,13 +128,28 @@ fun CollectionRowSection(
|
|||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(rowFocusRequester)
|
||||
.focusRestorer(),
|
||||
.then(
|
||||
if (focusedItemIndex < 0 && collection.folders.isNotEmpty()) {
|
||||
Modifier.focusRestorer {
|
||||
val fallbackIndex = listState.firstVisibleItemIndex
|
||||
.coerceIn(0, (collection.folders.size - 1).coerceAtLeast(0))
|
||||
val fallbackFolder = collection.folders.getOrNull(fallbackIndex)
|
||||
if (fallbackFolder != null) {
|
||||
itemFocusRequesters.getOrPut(folderFocusKey(fallbackIndex, fallbackFolder)) { FocusRequester() }
|
||||
} else {
|
||||
rowFocusRequester
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Modifier.focusRestorer()
|
||||
}
|
||||
),
|
||||
contentPadding = PaddingValues(start = 48.dp, end = 200.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = collection.folders,
|
||||
key = { _, folder -> "collection_${collection.id}_folder_${folder.id}" },
|
||||
key = { index, folder -> folderFocusKey(index, folder) },
|
||||
contentType = { _, _ -> "collection_folder" }
|
||||
) { index, folder ->
|
||||
FolderCard(
|
||||
|
|
@ -106,7 +162,10 @@ fun CollectionRowSection(
|
|||
currentOnItemFocused(index)
|
||||
}
|
||||
currentOnFolderFocused(collection, folder)
|
||||
}
|
||||
},
|
||||
focusRequester = itemFocusRequesters.getOrPut(
|
||||
folderFocusKey(index, folder)
|
||||
) { FocusRequester() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -120,7 +179,8 @@ private fun FolderCard(
|
|||
collection: Collection,
|
||||
onClick: () -> Unit,
|
||||
onFocused: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
focusRequester: FocusRequester = remember { FocusRequester() }
|
||||
) {
|
||||
val tileWidth: Dp
|
||||
val tileHeight: Dp
|
||||
|
|
@ -143,6 +203,7 @@ private fun FolderCard(
|
|||
modifier = modifier
|
||||
.width(tileWidth)
|
||||
.height(tileHeight)
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged {
|
||||
isFocused = it.isFocused
|
||||
if (it.isFocused) onFocused()
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ fun CollectionManagementScreen(
|
|||
onBack: () -> Unit
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -127,10 +127,7 @@ fun CollectionManagementScreen(
|
|||
importError = uiState.importError,
|
||||
onTextChange = { viewModel.updateImportText(it) },
|
||||
onImport = { viewModel.importCollections() },
|
||||
onPaste = {
|
||||
val clip = clipboardManager.getText()?.text ?: ""
|
||||
viewModel.updateImportText(clip)
|
||||
},
|
||||
onPaste = {},
|
||||
onBack = { viewModel.hideImportDialog() },
|
||||
importMode = uiState.importMode,
|
||||
onModeChange = { viewModel.setImportMode(it) },
|
||||
|
|
@ -171,13 +168,6 @@ fun CollectionManagementScreen(
|
|||
repeat(3) { withFrameNanos { } }
|
||||
try { newButtonFocusRequester.requestFocus() } catch (_: Exception) {}
|
||||
}
|
||||
var showCopied by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(showCopied) {
|
||||
if (showCopied) {
|
||||
kotlinx.coroutines.delay(2000)
|
||||
showCopied = false
|
||||
}
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (uiState.collections.isNotEmpty()) {
|
||||
NuvioButton(onClick = {
|
||||
|
|
@ -209,13 +199,6 @@ fun CollectionManagementScreen(
|
|||
}) {
|
||||
Text(exportMessage ?: "Export File")
|
||||
}
|
||||
NuvioButton(onClick = {
|
||||
val json = viewModel.exportCollections()
|
||||
clipboardManager.setText(AnnotatedString(json))
|
||||
showCopied = true
|
||||
}) {
|
||||
Text(if (showCopied) "Copied!" else "Copy JSON")
|
||||
}
|
||||
}
|
||||
NuvioButton(onClick = { viewModel.showImportDialog() }) {
|
||||
Text("Import")
|
||||
|
|
@ -326,7 +309,7 @@ private fun ImportContent(
|
|||
|
||||
// Mode tabs
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
ImportMode.entries.forEach { mode ->
|
||||
ImportMode.entries.filter { it != ImportMode.PASTE }.forEach { mode ->
|
||||
val label = when (mode) {
|
||||
ImportMode.PASTE -> "Paste JSON"
|
||||
ImportMode.FILE -> "From File"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ data class CollectionManagementUiState(
|
|||
val importText: String = "",
|
||||
val importError: String? = null,
|
||||
val exportedJson: String? = null,
|
||||
val importMode: ImportMode = ImportMode.PASTE,
|
||||
val importMode: ImportMode = ImportMode.FILE,
|
||||
val importUrl: String = "",
|
||||
val validationResult: ValidationResult? = null,
|
||||
val validatedJson: String? = null,
|
||||
|
|
@ -95,7 +95,7 @@ class CollectionManagementViewModel @Inject constructor(
|
|||
_uiState.update {
|
||||
it.copy(
|
||||
showImportDialog = true, importText = "", importError = null,
|
||||
importMode = ImportMode.PASTE, importUrl = "",
|
||||
importMode = ImportMode.FILE, importUrl = "",
|
||||
validationResult = null, validatedJson = null, isLoadingImport = false
|
||||
)
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ class CollectionManagementViewModel @Inject constructor(
|
|||
_uiState.update {
|
||||
it.copy(
|
||||
showImportDialog = false, importText = "", importError = null,
|
||||
importMode = ImportMode.PASTE, importUrl = "",
|
||||
importMode = ImportMode.FILE, importUrl = "",
|
||||
validationResult = null, validatedJson = null, isLoadingImport = false
|
||||
)
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ class CollectionManagementViewModel @Inject constructor(
|
|||
it.copy(
|
||||
showImportDialog = false, importText = "", importError = null,
|
||||
validationResult = null, validatedJson = null, importUrl = "",
|
||||
importMode = ImportMode.PASTE
|
||||
importMode = ImportMode.FILE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,12 +144,19 @@ private fun FolderHeader(folder: com.nuvio.tv.domain.model.CollectionFolder) {
|
|||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
if (!folder.coverImageUrl.isNullOrBlank()) {
|
||||
val iconWidth: androidx.compose.ui.unit.Dp
|
||||
val iconHeight: androidx.compose.ui.unit.Dp
|
||||
when (folder.tileShape) {
|
||||
com.nuvio.tv.domain.model.PosterShape.POSTER -> { iconWidth = 32.dp; iconHeight = 48.dp }
|
||||
com.nuvio.tv.domain.model.PosterShape.LANDSCAPE -> { iconWidth = 64.dp; iconHeight = 36.dp }
|
||||
com.nuvio.tv.domain.model.PosterShape.SQUARE -> { iconWidth = 48.dp; iconHeight = 48.dp }
|
||||
}
|
||||
AsyncImage(
|
||||
model = folder.coverImageUrl,
|
||||
contentDescription = folder.title,
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.height(48.dp)
|
||||
.width(iconWidth)
|
||||
.height(iconHeight)
|
||||
.clip(RoundedCornerShape(8.dp)),
|
||||
contentScale = ContentScale.FillBounds
|
||||
)
|
||||
|
|
@ -189,12 +196,19 @@ private fun TabbedGridContent(
|
|||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
if (!folder.coverImageUrl.isNullOrBlank()) {
|
||||
val iconWidth: androidx.compose.ui.unit.Dp
|
||||
val iconHeight: androidx.compose.ui.unit.Dp
|
||||
when (folder.tileShape) {
|
||||
com.nuvio.tv.domain.model.PosterShape.POSTER -> { iconWidth = 32.dp; iconHeight = 48.dp }
|
||||
com.nuvio.tv.domain.model.PosterShape.LANDSCAPE -> { iconWidth = 64.dp; iconHeight = 36.dp }
|
||||
com.nuvio.tv.domain.model.PosterShape.SQUARE -> { iconWidth = 48.dp; iconHeight = 48.dp }
|
||||
}
|
||||
AsyncImage(
|
||||
model = folder.coverImageUrl,
|
||||
contentDescription = folder.title,
|
||||
modifier = Modifier
|
||||
.width(48.dp)
|
||||
.height(48.dp)
|
||||
.width(iconWidth)
|
||||
.height(iconHeight)
|
||||
.clip(RoundedCornerShape(8.dp)),
|
||||
contentScale = ContentScale.FillBounds
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ private const val KEY_REPEAT_THROTTLE_MS = 80L
|
|||
|
||||
private class FocusSnapshot(
|
||||
var rowIndex: Int,
|
||||
var itemIndex: Int
|
||||
var itemIndex: Int,
|
||||
var rowKey: String? = null
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
|
|
@ -298,7 +299,9 @@ fun ClassicHomeContent(
|
|||
is HomeRow.Catalog -> {
|
||||
val catalogRow = homeRow.row
|
||||
val catalogKey = "${catalogRow.addonId}_${catalogRow.apiType}_${catalogRow.catalogId}"
|
||||
val shouldRestoreFocus = restoringFocus && index == focusState.focusedRowIndex
|
||||
// Match by saved row key first, fall back to index
|
||||
val shouldRestoreFocus = restoringFocus &&
|
||||
(currentFocusSnapshot.rowKey == catalogKey || index == focusState.focusedRowIndex)
|
||||
val shouldInitialFocusFirstCatalogRow =
|
||||
shouldRequestInitialFocus &&
|
||||
!heroVisible &&
|
||||
|
|
@ -351,12 +354,21 @@ fun ClassicHomeContent(
|
|||
if (restoringFocus) restoringFocus = false
|
||||
currentFocusSnapshot.rowIndex = index
|
||||
currentFocusSnapshot.itemIndex = itemIndex
|
||||
currentFocusSnapshot.rowKey = catalogKey
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is HomeRow.CollectionRow -> {
|
||||
val collectionKey = "collection_${homeRow.collection.id}"
|
||||
// Match by saved row key first, fall back to index
|
||||
val shouldRestoreCollectionFocus = restoringFocus &&
|
||||
(currentFocusSnapshot.rowKey == collectionKey || index == focusState.focusedRowIndex)
|
||||
val collectionFocusedItemIndex = if (shouldRestoreCollectionFocus) {
|
||||
focusState.focusedItemIndex
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
val listState = rowStates.getOrPut(collectionKey) {
|
||||
LazyListState(
|
||||
firstVisibleItemIndex = focusState.catalogRowScrollStates[collectionKey] ?: 0
|
||||
|
|
@ -367,10 +379,12 @@ fun ClassicHomeContent(
|
|||
collection = homeRow.collection,
|
||||
onFolderClick = onNavigateToFolderDetail,
|
||||
listState = listState,
|
||||
focusedItemIndex = collectionFocusedItemIndex,
|
||||
onItemFocused = { itemIndex ->
|
||||
if (restoringFocus) restoringFocus = false
|
||||
currentFocusSnapshot.rowIndex = index
|
||||
currentFocusSnapshot.itemIndex = itemIndex
|
||||
currentFocusSnapshot.rowKey = collectionKey
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ private fun HeroTitleContent(
|
|||
contentScale = ContentScale.Fit,
|
||||
alignment = Alignment.CenterStart
|
||||
)
|
||||
} else {
|
||||
} else if (preview.title.isNotBlank()) {
|
||||
Text(
|
||||
text = preview.title,
|
||||
style = scaledTitleStyle,
|
||||
|
|
|
|||
|
|
@ -474,14 +474,14 @@ internal fun buildCollectionFolderItem(
|
|||
|
||||
return ModernCarouselItem(
|
||||
key = "collection_${collection.id}_${folder.id}_$occurrence",
|
||||
title = folder.title,
|
||||
subtitle = collection.title,
|
||||
title = if (folder.hideTitle) "" else folder.title,
|
||||
subtitle = if (folder.hideTitle) null else collection.title,
|
||||
imageUrl = heroImageUrl,
|
||||
heroPreview = HeroPreview(
|
||||
title = title,
|
||||
title = if (folder.hideTitle) "" else title,
|
||||
logo = null,
|
||||
description = null,
|
||||
contentTypeText = collection.title,
|
||||
contentTypeText = if (folder.hideTitle) null else collection.title,
|
||||
yearText = null,
|
||||
imdbText = null,
|
||||
genres = emptyList(),
|
||||
|
|
|
|||
|
|
@ -1013,7 +1013,7 @@ private fun ModernCarouselCard(
|
|||
}
|
||||
}
|
||||
|
||||
if (showLabels && !isBackdropExpanded) {
|
||||
if (showLabels && !isBackdropExpanded && item.title.isNotBlank()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
<!-- WebConfigServers -->
|
||||
<string name="web_manage_addons_title">Manage Addons</string>
|
||||
<string name="web_manage_addons_subtitle">Manage addons and home catalogs</string>
|
||||
<string name="web_manage_addons_subtitle">Manage addons, catalogs, and collections</string>
|
||||
<string name="web_add_addon_url">Add addon by URL</string>
|
||||
<string name="web_placeholder_url">https://example.com/manifest.json</string>
|
||||
<string name="web_btn_add">Add</string>
|
||||
<string name="web_installed_addons">Installed Addons</string>
|
||||
<string name="web_no_addons">No addons installed</string>
|
||||
<string name="web_home_catalogs">Home Catalogs</string>
|
||||
<string name="web_home_catalogs">Home Catalogs and Collections</string>
|
||||
<string name="web_no_catalogs">No home catalogs available</string>
|
||||
<string name="web_btn_save">Save Changes</string>
|
||||
<string name="web_connection_lost">Connection to TV lost</string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue