mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-06 19:38:58 +00:00
feat: collections reordering
This commit is contained in:
parent
74d07804dd
commit
9f3ead5221
6 changed files with 256 additions and 90 deletions
|
|
@ -1,8 +1,11 @@
|
|||
package com.nuvio.app.core.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -42,6 +45,7 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
|
|||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -430,3 +434,44 @@ fun NuvioStatusModal(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NuvioPinnedCollectionToast(
|
||||
visible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
message: String = "Remove pin to top from collection to move",
|
||||
) {
|
||||
LaunchedEffect(visible) {
|
||||
if (visible) {
|
||||
kotlinx.coroutines.delay(2500L)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn() + slideInVertically { -it },
|
||||
exit = fadeOut() + slideOutVertically { -it },
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = MaterialTheme.colorScheme.inverseSurface,
|
||||
tonalElevation = 6.dp,
|
||||
shadowElevation = 4.dp,
|
||||
) {
|
||||
Text(
|
||||
text = message,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.inverseOnSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.nuvio.app.features.home
|
||||
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.collection.Collection
|
||||
import com.nuvio.app.features.collection.CollectionRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -17,6 +19,9 @@ data class HomeCatalogSettingsItem(
|
|||
val enabled: Boolean = true,
|
||||
val heroSourceEnabled: Boolean = true,
|
||||
val order: Int = 0,
|
||||
val isCollection: Boolean = false,
|
||||
val collectionId: String? = null,
|
||||
val isPinnedToTop: Boolean = false,
|
||||
) {
|
||||
val displayTitle: String
|
||||
get() = customTitle.ifBlank { defaultTitle }
|
||||
|
|
@ -78,6 +83,7 @@ object HomeCatalogSettingsRepository {
|
|||
|
||||
private var hasLoaded = false
|
||||
private var definitions: List<HomeCatalogDefinition> = emptyList()
|
||||
private var collectionDefinitions: List<CollectionCatalogDefinition> = emptyList()
|
||||
private var preferences: MutableMap<String, StoredHomeCatalogPreference> = mutableMapOf()
|
||||
private var heroEnabled = true
|
||||
|
||||
|
|
@ -86,12 +92,14 @@ object HomeCatalogSettingsRepository {
|
|||
preferences.clear()
|
||||
heroEnabled = true
|
||||
definitions = emptyList()
|
||||
collectionDefinitions = emptyList()
|
||||
_uiState.value = HomeCatalogSettingsUiState()
|
||||
}
|
||||
|
||||
fun clearLocalState() {
|
||||
hasLoaded = false
|
||||
definitions = emptyList()
|
||||
collectionDefinitions = emptyList()
|
||||
preferences.clear()
|
||||
heroEnabled = true
|
||||
_uiState.value = HomeCatalogSettingsUiState()
|
||||
|
|
@ -100,7 +108,8 @@ object HomeCatalogSettingsRepository {
|
|||
fun syncCatalogs(addons: List<ManagedAddon>) {
|
||||
ensureLoaded()
|
||||
definitions = buildHomeCatalogDefinitions(addons)
|
||||
if (definitions.isEmpty()) {
|
||||
collectionDefinitions = buildCollectionDefinitions(CollectionRepository.collections.value)
|
||||
if (definitions.isEmpty() && collectionDefinitions.isEmpty()) {
|
||||
publish()
|
||||
return
|
||||
}
|
||||
|
|
@ -109,6 +118,14 @@ object HomeCatalogSettingsRepository {
|
|||
persist()
|
||||
}
|
||||
|
||||
fun syncCollections(collections: List<Collection>) {
|
||||
ensureLoaded()
|
||||
collectionDefinitions = buildCollectionDefinitions(collections)
|
||||
normalizePreferences()
|
||||
publish()
|
||||
persist()
|
||||
}
|
||||
|
||||
internal fun snapshot(): HomeCatalogSettingsSnapshot {
|
||||
ensureLoaded()
|
||||
return HomeCatalogSettingsSnapshot(
|
||||
|
|
@ -176,13 +193,11 @@ object HomeCatalogSettingsRepository {
|
|||
|
||||
fun moveByIndex(fromIndex: Int, toIndex: Int) {
|
||||
ensureLoaded()
|
||||
if (definitions.isEmpty()) return
|
||||
val orderedKeys = definitions
|
||||
.sortedBy { definition -> preferences[definition.key]?.order ?: Int.MAX_VALUE }
|
||||
.map { it.key }
|
||||
.toMutableList()
|
||||
if (fromIndex !in orderedKeys.indices || toIndex !in orderedKeys.indices) return
|
||||
val allKeys = allOrderedKeys()
|
||||
if (allKeys.isEmpty()) return
|
||||
if (fromIndex !in allKeys.indices || toIndex !in allKeys.indices) return
|
||||
if (fromIndex == toIndex) return
|
||||
val orderedKeys = allKeys.toMutableList()
|
||||
orderedKeys.add(toIndex, orderedKeys.removeAt(fromIndex))
|
||||
orderedKeys.forEachIndexed { index, itemKey ->
|
||||
val current = preferences[itemKey] ?: return@forEachIndexed
|
||||
|
|
@ -219,14 +234,19 @@ object HomeCatalogSettingsRepository {
|
|||
|
||||
private fun normalizePreferences() {
|
||||
val current = preferences
|
||||
val orderedDefinitions = definitions.mapIndexed { defaultIndex, definition ->
|
||||
data class UnifiedEntry(val key: String, val isCollection: Boolean)
|
||||
val catalogEntries = definitions.map { UnifiedEntry(it.key, false) }
|
||||
val collectionEntries = collectionDefinitions.map { UnifiedEntry(it.key, true) }
|
||||
val allEntries = catalogEntries + collectionEntries
|
||||
|
||||
val orderedEntries = allEntries.mapIndexed { defaultIndex, entry ->
|
||||
Triple(
|
||||
definition,
|
||||
current[definition.key]?.order ?: defaultIndex,
|
||||
entry,
|
||||
current[entry.key]?.order ?: (definitions.size + defaultIndex),
|
||||
defaultIndex,
|
||||
)
|
||||
}.sortedWith(
|
||||
compareBy<Triple<HomeCatalogDefinition, Int, Int>>(
|
||||
compareBy<Triple<UnifiedEntry, Int, Int>>(
|
||||
{ it.second },
|
||||
{ it.third },
|
||||
),
|
||||
|
|
@ -234,15 +254,19 @@ object HomeCatalogSettingsRepository {
|
|||
|
||||
val normalized = mutableMapOf<String, StoredHomeCatalogPreference>()
|
||||
var enabledHeroSourceCount = 0
|
||||
orderedDefinitions.forEachIndexed { index, definition ->
|
||||
val stored = current[definition.key]
|
||||
val heroSourceEnabled = (stored?.heroSourceEnabled ?: true) &&
|
||||
enabledHeroSourceCount < HERO_SOURCE_SELECTION_LIMIT
|
||||
orderedEntries.forEachIndexed { index, entry ->
|
||||
val stored = current[entry.key]
|
||||
val heroSourceEnabled = if (entry.isCollection) {
|
||||
false
|
||||
} else {
|
||||
(stored?.heroSourceEnabled ?: true) &&
|
||||
enabledHeroSourceCount < HERO_SOURCE_SELECTION_LIMIT
|
||||
}
|
||||
if (heroSourceEnabled) {
|
||||
enabledHeroSourceCount += 1
|
||||
}
|
||||
normalized[definition.key] = StoredHomeCatalogPreference(
|
||||
key = definition.key,
|
||||
normalized[entry.key] = StoredHomeCatalogPreference(
|
||||
key = entry.key,
|
||||
customTitle = stored?.customTitle.orEmpty(),
|
||||
enabled = stored?.enabled ?: true,
|
||||
heroSourceEnabled = heroSourceEnabled,
|
||||
|
|
@ -253,8 +277,8 @@ object HomeCatalogSettingsRepository {
|
|||
}
|
||||
|
||||
private fun publish() {
|
||||
val items = definitions
|
||||
.sortedBy { definition -> preferences[definition.key]?.order ?: Int.MAX_VALUE }
|
||||
val collectionMap = collectionDefinitions.associateBy { it.key }
|
||||
val catalogItems = definitions
|
||||
.map { definition ->
|
||||
val preference = preferences[definition.key]
|
||||
HomeCatalogSettingsItem(
|
||||
|
|
@ -268,6 +292,25 @@ object HomeCatalogSettingsRepository {
|
|||
)
|
||||
}
|
||||
|
||||
val collectionItems = collectionDefinitions.map { colDef ->
|
||||
val preference = preferences[colDef.key]
|
||||
HomeCatalogSettingsItem(
|
||||
key = colDef.key,
|
||||
defaultTitle = colDef.title,
|
||||
addonName = colDef.subtitle,
|
||||
customTitle = preference?.customTitle.orEmpty(),
|
||||
enabled = preference?.enabled ?: true,
|
||||
heroSourceEnabled = false,
|
||||
order = preference?.order ?: 0,
|
||||
isCollection = true,
|
||||
collectionId = colDef.collectionId,
|
||||
isPinnedToTop = colDef.isPinnedToTop,
|
||||
)
|
||||
}
|
||||
|
||||
val items = (catalogItems + collectionItems)
|
||||
.sortedBy { it.order }
|
||||
|
||||
_uiState.value = HomeCatalogSettingsUiState(
|
||||
heroEnabled = heroEnabled,
|
||||
items = items,
|
||||
|
|
@ -307,12 +350,8 @@ object HomeCatalogSettingsRepository {
|
|||
direction: Int,
|
||||
) {
|
||||
ensureLoaded()
|
||||
if (definitions.isEmpty()) return
|
||||
|
||||
val orderedKeys = definitions
|
||||
.sortedBy { definition -> preferences[definition.key]?.order ?: Int.MAX_VALUE }
|
||||
.map { it.key }
|
||||
.toMutableList()
|
||||
val orderedKeys = allOrderedKeys().toMutableList()
|
||||
if (orderedKeys.isEmpty()) return
|
||||
|
||||
val currentIndex = orderedKeys.indexOf(key)
|
||||
if (currentIndex == -1) return
|
||||
|
|
@ -332,4 +371,30 @@ object HomeCatalogSettingsRepository {
|
|||
persist()
|
||||
HomeRepository.applyCurrentSettings()
|
||||
}
|
||||
|
||||
private fun allOrderedKeys(): List<String> {
|
||||
val catalogKeys = definitions.map { it.key }
|
||||
val collectionKeys = collectionDefinitions.map { it.key }
|
||||
return (catalogKeys + collectionKeys)
|
||||
.sortedBy { key -> preferences[key]?.order ?: Int.MAX_VALUE }
|
||||
}
|
||||
}
|
||||
|
||||
internal data class CollectionCatalogDefinition(
|
||||
val key: String,
|
||||
val collectionId: String,
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val isPinnedToTop: Boolean,
|
||||
)
|
||||
|
||||
internal fun buildCollectionDefinitions(collections: List<Collection>): List<CollectionCatalogDefinition> =
|
||||
collections.filter { it.folders.isNotEmpty() }.map { collection ->
|
||||
CollectionCatalogDefinition(
|
||||
key = "collection_${collection.id}",
|
||||
collectionId = collection.id,
|
||||
title = collection.title,
|
||||
subtitle = "${collection.folders.size} folder${if (collection.folders.size != 1) "s" else ""}",
|
||||
isPinnedToTop = collection.pinToTop,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,10 @@ fun HomeScreen(
|
|||
HomeRepository.refresh(addonsUiState.addons)
|
||||
}
|
||||
|
||||
LaunchedEffect(collections) {
|
||||
HomeCatalogSettingsRepository.syncCollections(collections)
|
||||
}
|
||||
|
||||
LaunchedEffect(completedSeriesCandidates, metaProviderKey) {
|
||||
if (completedSeriesCandidates.isEmpty()) {
|
||||
nextUpItemsBySeries = emptyMap()
|
||||
|
|
@ -368,48 +372,43 @@ fun HomeScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
// Pin-to-top collection rows
|
||||
val pinnedCollections = collections.filter { it.pinToTop && it.folders.isNotEmpty() }
|
||||
val unpinnedCollections = collections.filter { !it.pinToTop && it.folders.isNotEmpty() }
|
||||
|
||||
pinnedCollections.forEach { collection ->
|
||||
item(key = "collection_${collection.id}") {
|
||||
HomeCollectionRowSection(
|
||||
collection = collection,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
onFolderClick = onFolderClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
val collectionsMap = collections.filter { it.folders.isNotEmpty() }
|
||||
.associateBy { "collection_${it.id}" }
|
||||
val sectionsMap = homeUiState.sections.associateBy { it.key }
|
||||
val orderedItems = homeSettingsUiState.items.filter { it.enabled }
|
||||
|
||||
items(
|
||||
count = homeUiState.sections.size,
|
||||
key = { index -> homeUiState.sections[index].key },
|
||||
) { index ->
|
||||
val section = homeUiState.sections[index]
|
||||
HomeCatalogRowSection(
|
||||
section = section,
|
||||
entries = section.items.take(HOME_CATALOG_PREVIEW_LIMIT),
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
onViewAllClick = if (section.canOpenCatalog(HOME_CATALOG_PREVIEW_LIMIT)) {
|
||||
onCatalogClick?.let { { it(section) } }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
onPosterClick = onPosterClick,
|
||||
onPosterLongClick = onPosterLongClick,
|
||||
)
|
||||
}
|
||||
|
||||
// Unpinned collection rows after catalog sections
|
||||
unpinnedCollections.forEach { collection ->
|
||||
item(key = "collection_${collection.id}") {
|
||||
HomeCollectionRowSection(
|
||||
collection = collection,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
onFolderClick = onFolderClick,
|
||||
)
|
||||
orderedItems.forEach { settingsItem ->
|
||||
if (settingsItem.isCollection) {
|
||||
val collection = collectionsMap[settingsItem.key]
|
||||
if (collection != null) {
|
||||
item(key = settingsItem.key) {
|
||||
HomeCollectionRowSection(
|
||||
collection = collection,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
onFolderClick = onFolderClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val section = sectionsMap[settingsItem.key]
|
||||
if (section != null && section.items.isNotEmpty()) {
|
||||
item(key = settingsItem.key) {
|
||||
HomeCatalogRowSection(
|
||||
section = section,
|
||||
entries = section.items.take(HOME_CATALOG_PREVIEW_LIMIT),
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
onViewAllClick = if (section.canOpenCatalog(HOME_CATALOG_PREVIEW_LIMIT)) {
|
||||
onCatalogClick?.let { { it(section) } }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
onPosterClick = onPosterClick,
|
||||
onPosterLongClick = onPosterLongClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.core.ui.NuvioActionLabel
|
||||
import com.nuvio.app.core.ui.NuvioPinnedCollectionToast
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsItem
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
|
||||
import com.nuvio.app.features.home.components.HomeEmptyStateCard
|
||||
|
|
@ -71,7 +72,8 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
}
|
||||
}
|
||||
item {
|
||||
if (heroEnabled && items.isNotEmpty()) {
|
||||
val catalogOnlyItems = items.filter { !it.isCollection }
|
||||
if (heroEnabled && catalogOnlyItems.isNotEmpty()) {
|
||||
var heroSourcesExpanded by remember { mutableStateOf(false) }
|
||||
SettingsSection(
|
||||
title = "HERO SOURCES",
|
||||
|
|
@ -79,7 +81,7 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
) {
|
||||
HeroSourcesDropdown(
|
||||
isTablet = isTablet,
|
||||
items = items,
|
||||
items = catalogOnlyItems,
|
||||
selectedHeroSourceCount = selectedHeroSourceCount,
|
||||
expanded = heroSourcesExpanded,
|
||||
onExpandedChange = { heroSourcesExpanded = it },
|
||||
|
|
@ -95,8 +97,15 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
message = "Install an addon with board-compatible catalogs to configure Homescreen rows.",
|
||||
)
|
||||
} else {
|
||||
val catalogCount = items.count { !it.isCollection }
|
||||
val collectionCount = items.count { it.isCollection }
|
||||
val sectionTitle = when {
|
||||
collectionCount > 0 && catalogCount > 0 -> "CATALOGS & COLLECTIONS"
|
||||
collectionCount > 0 -> "COLLECTIONS"
|
||||
else -> "CATALOGS"
|
||||
}
|
||||
SettingsSection(
|
||||
title = "CATALOGS",
|
||||
title = sectionTitle,
|
||||
isTablet = isTablet,
|
||||
actions = {
|
||||
NuvioActionLabel(
|
||||
|
|
@ -105,9 +114,21 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
)
|
||||
},
|
||||
) {
|
||||
var showPinnedToast by remember { mutableStateOf(false) }
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
NuvioPinnedCollectionToast(
|
||||
visible = showPinnedToast,
|
||||
onDismiss = { showPinnedToast = false },
|
||||
)
|
||||
|
||||
HomescreenCatalogList(
|
||||
isTablet = isTablet,
|
||||
items = items,
|
||||
onPinnedDragAttempt = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
showPinnedToast = true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -219,6 +240,7 @@ private fun HomescreenSummaryCard(
|
|||
private fun HomescreenCatalogList(
|
||||
isTablet: Boolean,
|
||||
items: List<HomeCatalogSettingsItem>,
|
||||
onPinnedDragAttempt: () -> Unit,
|
||||
) {
|
||||
var expandedKey by remember { mutableStateOf<String?>(null) }
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
|
@ -226,6 +248,11 @@ private fun HomescreenCatalogList(
|
|||
val reorderableLazyListState = rememberReorderableLazyListState(
|
||||
lazyListState = lazyListState,
|
||||
) { from, to ->
|
||||
val fromItem = items.getOrNull(from.index)
|
||||
val toItem = items.getOrNull(to.index)
|
||||
if (fromItem?.isPinnedToTop == true || toItem?.isPinnedToTop == true) {
|
||||
return@rememberReorderableLazyListState
|
||||
}
|
||||
HomeCatalogSettingsRepository.moveByIndex(from.index, to.index)
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
}
|
||||
|
|
@ -238,7 +265,11 @@ private fun HomescreenCatalogList(
|
|||
state = lazyListState,
|
||||
) {
|
||||
itemsIndexed(items, key = { _, item -> item.key }) { index, item ->
|
||||
ReorderableItem(reorderableLazyListState, key = item.key) { isDragging ->
|
||||
ReorderableItem(
|
||||
reorderableLazyListState,
|
||||
key = item.key,
|
||||
enabled = !item.isPinnedToTop,
|
||||
) { isDragging ->
|
||||
val elevation by animateDpAsState(if (isDragging) 4.dp else 0.dp)
|
||||
|
||||
Surface(shadowElevation = elevation) {
|
||||
|
|
@ -256,6 +287,7 @@ private fun HomescreenCatalogList(
|
|||
onTitleChange = { HomeCatalogSettingsRepository.setCustomTitle(item.key, it) },
|
||||
onEnabledChange = { HomeCatalogSettingsRepository.setEnabled(item.key, it) },
|
||||
dragHandleScope = this@ReorderableItem,
|
||||
onPinnedDragAttempt = onPinnedDragAttempt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.widthIn
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowForward
|
||||
import androidx.compose.material.icons.rounded.Lock
|
||||
import androidx.compose.material.icons.rounded.Menu
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
|
|
@ -342,6 +343,7 @@ internal fun HomescreenCatalogRow(
|
|||
onTitleChange: (String) -> Unit,
|
||||
onEnabledChange: (Boolean) -> Unit,
|
||||
dragHandleScope: ReorderableCollectionItemScope,
|
||||
onPinnedDragAttempt: () -> Unit = {},
|
||||
) {
|
||||
val horizontalPadding = if (isTablet) 20.dp else 16.dp
|
||||
val verticalPadding = if (isTablet) 18.dp else 16.dp
|
||||
|
|
@ -375,15 +377,19 @@ internal fun HomescreenCatalogRow(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = item.addonName,
|
||||
text = if (item.isCollection) "Collection • ${item.addonName}" else item.addonName,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Text(
|
||||
text = buildString {
|
||||
append(if (item.enabled) "Visible" else "Hidden")
|
||||
append(" • ")
|
||||
append(if (item.heroSourceEnabled) "Hero source" else "Not in hero")
|
||||
if (item.isCollection) {
|
||||
if (item.isPinnedToTop) append(" • Pinned to top")
|
||||
} else {
|
||||
append(" • ")
|
||||
append(if (item.heroSourceEnabled) "Hero source" else "Not in hero")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
|
|
@ -403,29 +409,41 @@ internal fun HomescreenCatalogRow(
|
|||
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
|
||||
),
|
||||
)
|
||||
IconButton(
|
||||
modifier = with(dragHandleScope) {
|
||||
Modifier.draggableHandle(
|
||||
onDragStarted = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onDragStopped = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
},
|
||||
if (item.isPinnedToTop) {
|
||||
IconButton(
|
||||
onClick = onPinnedDragAttempt,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Lock,
|
||||
contentDescription = "Pinned",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||
)
|
||||
},
|
||||
onClick = {},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Menu,
|
||||
contentDescription = "Reorder",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(
|
||||
modifier = with(dragHandleScope) {
|
||||
Modifier.draggableHandle(
|
||||
onDragStarted = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onDragStopped = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
|
||||
},
|
||||
)
|
||||
},
|
||||
onClick = {},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Menu,
|
||||
contentDescription = "Reorder",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
AnimatedVisibility(visible = expanded && !item.isCollection) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.nuvio.app.core.ui.NuvioScreen
|
||||
import com.nuvio.app.core.ui.NuvioScreenHeader
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.collection.CollectionRepository
|
||||
import com.nuvio.app.features.details.MetaScreenSettingsRepository
|
||||
import com.nuvio.app.features.plugins.PluginRepository
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
|
||||
|
|
@ -40,9 +41,11 @@ fun HomescreenSettingsScreen(
|
|||
}
|
||||
}
|
||||
val homescreenSettingsUiState by HomeCatalogSettingsRepository.uiState.collectAsStateWithLifecycle()
|
||||
val collections by CollectionRepository.collections.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
AddonRepository.initialize()
|
||||
CollectionRepository.initialize()
|
||||
}
|
||||
|
||||
LaunchedEffect(homescreenCatalogRefreshKey) {
|
||||
|
|
@ -50,6 +53,10 @@ fun HomescreenSettingsScreen(
|
|||
HomeCatalogSettingsRepository.syncCatalogs(addonsUiState.addons)
|
||||
}
|
||||
|
||||
LaunchedEffect(collections) {
|
||||
HomeCatalogSettingsRepository.syncCollections(collections)
|
||||
}
|
||||
|
||||
NuvioScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
|
|||
Loading…
Reference in a new issue