mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-07-26 22:42:17 +00:00
adjusting addonscreen UI
This commit is contained in:
parent
7ea57547a8
commit
930ef3503a
4 changed files with 130 additions and 68 deletions
|
|
@ -27,6 +27,7 @@ internal object AddonManifestParser {
|
|||
name = root.requiredString("name"),
|
||||
description = root.requiredString("description"),
|
||||
version = root.requiredString("version"),
|
||||
logoUrl = root.optionalString("logo")?.resolveAgainstManifest(manifestUrl),
|
||||
resources = root.resources(defaultTypes, defaultPrefixes),
|
||||
types = defaultTypes,
|
||||
idPrefixes = defaultPrefixes,
|
||||
|
|
@ -110,3 +111,25 @@ internal object AddonManifestParser {
|
|||
private fun JsonObject.int(name: String): Int? =
|
||||
this[name]?.jsonPrimitive?.contentOrNull?.toIntOrNull()
|
||||
}
|
||||
|
||||
private fun String.resolveAgainstManifest(manifestUrl: String): String =
|
||||
when {
|
||||
startsWith("http://") || startsWith("https://") || startsWith("data:") -> this
|
||||
startsWith("//") -> "https:$this"
|
||||
else -> {
|
||||
val manifestBase = manifestUrl.substringBefore("?").substringBeforeLast('/', "")
|
||||
if (startsWith('/')) {
|
||||
val origin = manifestBase.substringBefore("/", missingDelimiterValue = manifestBase)
|
||||
val schemeAndHost = if (origin.contains("://")) {
|
||||
val scheme = origin.substringBefore("://")
|
||||
val host = manifestBase.substringAfter("://").substringBefore("/")
|
||||
"$scheme://$host"
|
||||
} else {
|
||||
manifestBase
|
||||
}
|
||||
"$schemeAndHost$this"
|
||||
} else {
|
||||
"$manifestBase/$this"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ data class AddonManifest(
|
|||
val name: String,
|
||||
val description: String,
|
||||
val version: String,
|
||||
val logoUrl: String? = null,
|
||||
val resources: List<AddonResource>,
|
||||
val types: List<String>,
|
||||
val idPrefixes: List<String> = emptyList(),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Extension
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material.icons.rounded.SwapVert
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -35,9 +34,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.core.ui.NuvioIconActionButton
|
||||
import com.nuvio.app.core.ui.NuvioInfoBadge
|
||||
import com.nuvio.app.core.ui.NuvioInputField
|
||||
|
|
@ -81,15 +82,10 @@ internal fun AddonsSettingsPageContent(
|
|||
val coroutineScope = rememberCoroutineScope()
|
||||
var addonUrl by rememberSaveable { mutableStateOf("") }
|
||||
var formMessage by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var sortAscending by rememberSaveable { mutableStateOf(true) }
|
||||
var installModalState by remember { mutableStateOf<AddonInstallModalState?>(null) }
|
||||
|
||||
val sortedAddons = remember(uiState.addons, sortAscending) {
|
||||
if (sortAscending) {
|
||||
uiState.addons.sortedBy { it.displayTitle.lowercase() }
|
||||
} else {
|
||||
uiState.addons.sortedByDescending { it.displayTitle.lowercase() }
|
||||
}
|
||||
val sortedAddons = remember(uiState.addons) {
|
||||
uiState.addons.sortedBy { it.displayTitle.lowercase() }
|
||||
}
|
||||
val overview = remember(sortedAddons) { sortedAddons.toOverview() }
|
||||
|
||||
|
|
@ -98,22 +94,6 @@ internal fun AddonsSettingsPageContent(
|
|||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
SectionHeader("OVERVIEW")
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
NuvioIconActionButton(
|
||||
icon = Icons.Rounded.SwapVert,
|
||||
contentDescription = if (sortAscending) "Sort descending" else "Sort ascending",
|
||||
onClick = { sortAscending = !sortAscending },
|
||||
)
|
||||
NuvioIconActionButton(
|
||||
icon = Icons.Rounded.Refresh,
|
||||
contentDescription = "Refresh all addons",
|
||||
onClick = { AddonRepository.refreshAll() },
|
||||
)
|
||||
}
|
||||
|
||||
OverviewCard(overview = overview)
|
||||
|
||||
SectionHeader("ADD ADDON")
|
||||
|
|
@ -345,6 +325,7 @@ private fun InstalledAddonCard(
|
|||
verticalAlignment = Alignment.Top,
|
||||
) {
|
||||
AddonIconBadge(
|
||||
imageUrl = manifest?.logoUrl,
|
||||
icon = Icons.Rounded.Extension,
|
||||
tint = if (manifest != null) Color(0xFF71BDE8) else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -357,12 +338,6 @@ private fun InstalledAddonCard(
|
|||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = addon.manifestUrl,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
manifest?.version?.let { version ->
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
|
|
@ -462,6 +437,7 @@ private fun InstalledAddonCard(
|
|||
|
||||
@Composable
|
||||
private fun AddonIconBadge(
|
||||
imageUrl: String?,
|
||||
icon: ImageVector,
|
||||
tint: Color,
|
||||
) {
|
||||
|
|
@ -472,12 +448,21 @@ private fun AddonIconBadge(
|
|||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = tint,
|
||||
modifier = Modifier.size(34.dp),
|
||||
)
|
||||
if (!imageUrl.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.matchParentSize(),
|
||||
contentScale = ContentScale.Crop,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = tint,
|
||||
modifier = Modifier.size(34.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
package com.nuvio.app.features.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.rounded.KeyboardArrowUp
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -51,42 +58,19 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
}
|
||||
}
|
||||
item {
|
||||
if (items.isNotEmpty()) {
|
||||
if (heroEnabled && items.isNotEmpty()) {
|
||||
var heroSourcesExpanded by remember { mutableStateOf(false) }
|
||||
SettingsSection(
|
||||
title = "HERO SOURCES",
|
||||
isTablet = isTablet,
|
||||
) {
|
||||
SettingsGroup(isTablet = isTablet) {
|
||||
Text(
|
||||
text = "$selectedHeroSourceCount of ${HomeCatalogSettingsRepository.HERO_SOURCE_SELECTION_LIMIT} selected",
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
if (items.isNotEmpty()) {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
}
|
||||
items.forEachIndexed { index, item ->
|
||||
if (index > 0) {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
}
|
||||
SettingsSwitchRow(
|
||||
title = item.displayTitle,
|
||||
description = if (!item.heroSourceEnabled &&
|
||||
selectedHeroSourceCount >= HomeCatalogSettingsRepository.HERO_SOURCE_SELECTION_LIMIT
|
||||
) {
|
||||
"${item.addonName} • Limit reached (max 2)"
|
||||
} else {
|
||||
item.addonName
|
||||
},
|
||||
checked = item.heroSourceEnabled,
|
||||
enabled = item.heroSourceEnabled ||
|
||||
selectedHeroSourceCount < HomeCatalogSettingsRepository.HERO_SOURCE_SELECTION_LIMIT,
|
||||
isTablet = isTablet,
|
||||
onCheckedChange = { HomeCatalogSettingsRepository.setHeroSourceEnabled(item.key, it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
HeroSourcesDropdown(
|
||||
isTablet = isTablet,
|
||||
items = items,
|
||||
selectedHeroSourceCount = selectedHeroSourceCount,
|
||||
expanded = heroSourcesExpanded,
|
||||
onExpandedChange = { heroSourcesExpanded = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,6 +95,75 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HeroSourcesDropdown(
|
||||
isTablet: Boolean,
|
||||
items: List<HomeCatalogSettingsItem>,
|
||||
selectedHeroSourceCount: Int,
|
||||
expanded: Boolean,
|
||||
onExpandedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
SettingsGroup(isTablet = isTablet) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp)
|
||||
.clickable { onExpandedChange(!expanded) },
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "$selectedHeroSourceCount of ${HomeCatalogSettingsRepository.HERO_SOURCE_SELECTION_LIMIT} selected",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
Text(
|
||||
text = items.filter { it.heroSourceEnabled }
|
||||
.joinToString(separator = ", ") { it.displayTitle }
|
||||
.ifBlank { "No hero sources selected" },
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
imageVector = if (expanded) Icons.Rounded.KeyboardArrowUp else Icons.Rounded.KeyboardArrowDown,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
Column {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
items.forEachIndexed { index, item ->
|
||||
if (index > 0) {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
}
|
||||
SettingsSwitchRow(
|
||||
title = item.displayTitle,
|
||||
description = if (!item.heroSourceEnabled &&
|
||||
selectedHeroSourceCount >= HomeCatalogSettingsRepository.HERO_SOURCE_SELECTION_LIMIT
|
||||
) {
|
||||
"${item.addonName} • Limit reached (max 2)"
|
||||
} else {
|
||||
item.addonName
|
||||
},
|
||||
checked = item.heroSourceEnabled,
|
||||
enabled = item.heroSourceEnabled ||
|
||||
selectedHeroSourceCount < HomeCatalogSettingsRepository.HERO_SOURCE_SELECTION_LIMIT,
|
||||
isTablet = isTablet,
|
||||
onCheckedChange = { HomeCatalogSettingsRepository.setHeroSourceEnabled(item.key, it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HomescreenSummaryCard(
|
||||
isTablet: Boolean,
|
||||
|
|
|
|||
Loading…
Reference in a new issue