Revert PR #507: Move addons into Settings and enforce profile-based access UI

This reverts only the changes introduced by PR #507 (commits 5b24848, aa4aa45, db7a37b) while preserving all other commits from the dev merge.
This commit is contained in:
tapframe 2026-03-05 06:24:24 +05:30
parent 7b55ac41cf
commit 66a02f306f
13 changed files with 217 additions and 337 deletions

View file

@ -2,5 +2,17 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Player" vcs="Git" />
<mapping directory="$PROJECT_DIR$/android-tv-samples" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/ass" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/expat" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/fontconfig" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/freetype" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/freetype/subprojects/dlg" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/fribidi" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/harfbuzz" vcs="Git" />
<mapping directory="$PROJECT_DIR$/libass-android/lib_ass/src/main/cpp/libass-cmake/src/unibreak" vcs="Git" />
</component>
</project>

View file

@ -62,7 +62,8 @@ android {
buildTypes {
debug {
isDebuggable = true
signingConfig = signingConfigs.getByName("release")
isDebuggable = false
isMinifyEnabled = false
buildConfigField("boolean", "IS_DEBUG_BUILD", "true")

View file

@ -362,50 +362,50 @@ class MainActivity : ComponentActivity() {
Screen.Home.route,
Screen.Search.route,
Screen.Library.route,
Screen.Settings.route
Screen.Settings.route,
Screen.AddonManager.route
)
}
val strNavHome = stringResource(R.string.nav_home)
val strNavSearch = stringResource(R.string.nav_search)
val strNavLibrary = stringResource(R.string.nav_library)
val strNavAddons = stringResource(R.string.nav_addons)
val strNavSettings = stringResource(R.string.nav_settings)
val drawerItems = remember(
strNavHome,
strNavSearch,
strNavLibrary,
strNavAddons,
strNavSettings
) {
buildList {
add(
DrawerItem(
route = Screen.Home.route,
label = strNavHome,
icon = Icons.Default.Home
)
listOf(
DrawerItem(
route = Screen.Home.route,
label = strNavHome,
icon = Icons.Default.Home
),
DrawerItem(
route = Screen.Search.route,
label = strNavSearch,
iconRes = R.raw.sidebar_search
),
DrawerItem(
route = Screen.Library.route,
label = strNavLibrary,
iconRes = R.raw.sidebar_library
),
DrawerItem(
route = Screen.AddonManager.route,
label = strNavAddons,
iconRes = R.raw.sidebar_plugin
),
DrawerItem(
route = Screen.Settings.route,
label = strNavSettings,
iconRes = R.raw.sidebar_settings
)
add(
DrawerItem(
route = Screen.Search.route,
label = strNavSearch,
iconRes = R.raw.sidebar_search
)
)
add(
DrawerItem(
route = Screen.Library.route,
label = strNavLibrary,
iconRes = R.raw.sidebar_library
)
)
add(
DrawerItem(
route = Screen.Settings.route,
label = strNavSettings,
iconRes = R.raw.sidebar_settings
)
)
}
)
}
val selectedDrawerRoute = drawerItems.firstOrNull { item ->
currentRoute == item.route || currentRoute?.startsWith("${item.route}/") == true
@ -424,8 +424,6 @@ class MainActivity : ComponentActivity() {
sidebarCollapsed = sidebarCollapsed,
modernSidebarBlurEnabled = modernSidebarBlurEnabled,
hideBuiltInHeaders = hideBuiltInHeadersForFloatingPill,
activeProfileUsesPrimaryAddons = activeProfile?.usesPrimaryAddons == true,
activeProfileUsesPrimaryPlugins = activeProfile?.usesPrimaryPlugins == true,
activeProfileName = activeProfile?.name ?: "",
activeProfileColorHex = activeProfile?.avatarColorHex ?: "#1E88E5",
showProfileSelector = profiles.size > 1,
@ -445,8 +443,6 @@ class MainActivity : ComponentActivity() {
selectedDrawerRoute = selectedDrawerRoute,
sidebarCollapsed = sidebarCollapsed,
hideBuiltInHeaders = false,
activeProfileUsesPrimaryAddons = activeProfile?.usesPrimaryAddons == true,
activeProfileUsesPrimaryPlugins = activeProfile?.usesPrimaryPlugins == true,
activeProfileName = activeProfile?.name ?: "",
activeProfileColorHex = activeProfile?.avatarColorHex ?: "#1E88E5",
showProfileSelector = profiles.size > 1,
@ -511,8 +507,6 @@ private fun LegacySidebarScaffold(
selectedDrawerRoute: String?,
sidebarCollapsed: Boolean,
hideBuiltInHeaders: Boolean,
activeProfileUsesPrimaryAddons: Boolean,
activeProfileUsesPrimaryPlugins: Boolean,
activeProfileName: String,
activeProfileColorHex: String,
showProfileSelector: Boolean,
@ -713,9 +707,7 @@ private fun LegacySidebarScaffold(
NuvioNavHost(
navController = navController,
startDestination = startDestination,
hideBuiltInHeaders = hideBuiltInHeaders,
activeProfileUsesPrimaryAddons = activeProfileUsesPrimaryAddons,
activeProfileUsesPrimaryPlugins = activeProfileUsesPrimaryPlugins
hideBuiltInHeaders = hideBuiltInHeaders
)
}
}
@ -803,8 +795,6 @@ private fun ModernSidebarScaffold(
sidebarCollapsed: Boolean,
modernSidebarBlurEnabled: Boolean,
hideBuiltInHeaders: Boolean,
activeProfileUsesPrimaryAddons: Boolean,
activeProfileUsesPrimaryPlugins: Boolean,
activeProfileName: String,
activeProfileColorHex: String,
showProfileSelector: Boolean,
@ -1060,9 +1050,7 @@ private fun ModernSidebarScaffold(
NuvioNavHost(
navController = navController,
startDestination = startDestination,
hideBuiltInHeaders = hideBuiltInHeaders,
activeProfileUsesPrimaryAddons = activeProfileUsesPrimaryAddons,
activeProfileUsesPrimaryPlugins = activeProfileUsesPrimaryPlugins
hideBuiltInHeaders = hideBuiltInHeaders
)
}
}

View file

@ -26,9 +26,7 @@ import androidx.tv.material3.FilterChip
import androidx.tv.material3.FilterChipDefaults
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.nuvio.tv.domain.model.AppTheme
import com.nuvio.tv.ui.theme.NuvioColors
import com.nuvio.tv.ui.theme.NuvioTheme
enum class SourceChipStatus {
LOADING,
@ -56,7 +54,6 @@ fun SourceStatusFilterChip(
val isError = status == SourceChipStatus.ERROR
val isLoading = status == SourceChipStatus.LOADING
val shouldUseNormalColors = !isError
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) androidx.compose.ui.graphics.Color.White else NuvioColors.Secondary
val shakeOffsetPx = remember { Animatable(0f) }
val alphaPulse = remember { Animatable(1f) }
@ -105,9 +102,9 @@ fun SourceStatusFilterChip(
},
colors = FilterChipDefaults.colors(
containerColor = if (shouldUseNormalColors) NuvioColors.BackgroundCard else NuvioColors.Error.copy(alpha = 0.05f),
focusedContainerColor = if (shouldUseNormalColors) selectedAccent else NuvioColors.Error.copy(alpha = 0.08f),
selectedContainerColor = if (shouldUseNormalColors) selectedAccent else NuvioColors.Error.copy(alpha = 0.07f),
focusedSelectedContainerColor = if (shouldUseNormalColors) selectedAccent else NuvioColors.Error.copy(alpha = 0.09f),
focusedContainerColor = if (shouldUseNormalColors) NuvioColors.Secondary else NuvioColors.Error.copy(alpha = 0.08f),
selectedContainerColor = if (shouldUseNormalColors) NuvioColors.Secondary else NuvioColors.Error.copy(alpha = 0.07f),
focusedSelectedContainerColor = if (shouldUseNormalColors) NuvioColors.Secondary else NuvioColors.Error.copy(alpha = 0.09f),
contentColor = textColor,
focusedContentColor = textColor,
selectedContentColor = textColor,

View file

@ -6,7 +6,6 @@ import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
@ -40,9 +39,7 @@ import com.nuvio.tv.ui.screens.cast.CastDetailScreen
fun NuvioNavHost(
navController: NavHostController,
startDestination: String = Screen.Home.route,
hideBuiltInHeaders: Boolean = false,
activeProfileUsesPrimaryAddons: Boolean = false,
activeProfileUsesPrimaryPlugins: Boolean = false
hideBuiltInHeaders: Boolean = false
) {
fun isStreamToPlayer(from: String, to: String): Boolean {
return from.startsWith("stream/") && to.startsWith("player/")
@ -704,8 +701,7 @@ fun NuvioNavHost(
SettingsScreen(
showBuiltInHeader = !hideBuiltInHeaders,
onNavigateToTrakt = { navController.navigate(Screen.Trakt.route) },
onNavigateToAuthQrSignIn = { navController.navigate(Screen.AuthQrSignIn.route) },
onNavigateToCatalogOrder = { navController.navigate(Screen.CatalogOrder.route) }
onNavigateToAuthQrSignIn = { navController.navigate(Screen.AuthQrSignIn.route) }
)
}
@ -740,19 +736,10 @@ fun NuvioNavHost(
}
composable(Screen.AddonManager.route) {
if (activeProfileUsesPrimaryAddons) {
LaunchedEffect(Unit) {
navController.navigate(Screen.Settings.route) {
popUpTo(Screen.AddonManager.route) { inclusive = true }
launchSingleTop = true
}
}
} else {
AddonManagerScreen(
showBuiltInHeader = !hideBuiltInHeaders,
onNavigateToCatalogOrder = { navController.navigate(Screen.CatalogOrder.route) }
)
}
AddonManagerScreen(
showBuiltInHeader = !hideBuiltInHeaders,
onNavigateToCatalogOrder = { navController.navigate(Screen.CatalogOrder.route) }
)
}
composable(Screen.CatalogOrder.route) {
@ -762,18 +749,9 @@ fun NuvioNavHost(
}
composable(Screen.Plugins.route) {
if (activeProfileUsesPrimaryPlugins) {
LaunchedEffect(Unit) {
navController.navigate(Screen.Settings.route) {
popUpTo(Screen.Plugins.route) { inclusive = true }
launchSingleTop = true
}
}
} else {
PluginScreen(
onBackPress = { navController.popBackStack() }
)
}
PluginScreen(
onBackPress = { navController.popBackStack() }
)
}
composable(Screen.Account.route) {

View file

@ -95,7 +95,6 @@ import com.nuvio.tv.R
fun AddonManagerScreen(
viewModel: AddonManagerViewModel = hiltViewModel(),
showBuiltInHeader: Boolean = true,
embeddedInSettings: Boolean = false,
onNavigateToCatalogOrder: () -> Unit = {}
) {
val uiState by viewModel.uiState.collectAsState()
@ -157,182 +156,156 @@ fun AddonManagerScreen(
Box(
modifier = Modifier
.fillMaxSize()
.let {
if (embeddedInSettings) it else it.background(NuvioColors.Background)
}
.background(NuvioColors.Background)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
start = if (embeddedInSettings) 0.dp else 36.dp,
end = if (embeddedInSettings) 0.dp else 36.dp,
top = if (showBuiltInHeader) 28.dp else 0.dp,
bottom = if (embeddedInSettings) 32.dp else 28.dp
),
verticalArrangement = Arrangement.spacedBy(if (embeddedInSettings) 16.dp else 20.dp)
contentPadding = PaddingValues(horizontal = 36.dp, vertical = 28.dp),
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
if (showBuiltInHeader) {
item {
Text(
text = stringResource(R.string.addon_title),
style = MaterialTheme.typography.headlineMedium,
color = NuvioColors.TextPrimary
)
}
item {
Text(
text = stringResource(R.string.addon_title),
style = MaterialTheme.typography.headlineMedium,
color = if (showBuiltInHeader) NuvioColors.TextPrimary else Color.Transparent
)
}
if (viewModel.isReadOnly) {
item {
if (embeddedInSettings) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A3A5C)),
shape = RoundedCornerShape(12.dp)
) {
Text(
text = stringResource(R.string.addon_readonly_notice),
style = MaterialTheme.typography.bodyMedium,
color = NuvioColors.TextSecondary,
modifier = Modifier.fillMaxWidth()
modifier = androidx.compose.ui.Modifier.padding(16.dp)
)
} else {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color(0xFF1A3A5C)),
shape = RoundedCornerShape(12.dp)
) {
Text(
text = stringResource(R.string.addon_readonly_notice),
style = MaterialTheme.typography.bodyMedium,
color = NuvioColors.TextSecondary,
modifier = androidx.compose.ui.Modifier.padding(16.dp)
)
}
}
}
}
if (!viewModel.isReadOnly) {
item {
val installContainerModifier = if (embeddedInSettings) {
Modifier
Card(
modifier = Modifier
.fillMaxWidth()
.animateContentSize()
} else {
Modifier
.fillMaxWidth()
.animateContentSize()
.background(
color = NuvioColors.BackgroundCard,
shape = RoundedCornerShape(12.dp)
)
}
Column(
modifier = installContainerModifier.padding(
if (embeddedInSettings) 0.dp else 20.dp
)
.animateContentSize(),
colors = CardDefaults.cardColors(containerColor = NuvioColors.BackgroundCard),
shape = RoundedCornerShape(12.dp)
) {
Text(
text = stringResource(R.string.addon_install_title),
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
color = NuvioColors.TextPrimary
)
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Surface(
onClick = { isEditing = true },
modifier = Modifier
.weight(1f)
.focusRequester(surfaceFocusRequester),
colors = ClickableSurfaceDefaults.colors(
containerColor = NuvioColors.BackgroundElevated,
focusedContainerColor = NuvioColors.BackgroundElevated
),
border = ClickableSurfaceDefaults.border(
border = Border(
border = BorderStroke(1.dp, NuvioColors.Border),
shape = RoundedCornerShape(12.dp)
),
focusedBorder = Border(
border = BorderStroke(2.dp, NuvioColors.FocusRing),
shape = RoundedCornerShape(12.dp)
)
),
shape = ClickableSurfaceDefaults.shape(RoundedCornerShape(12.dp)),
scale = ClickableSurfaceDefaults.scale(focusedScale = 1f)
Column(modifier = Modifier.padding(20.dp)) {
Text(
text = stringResource(R.string.addon_install_title),
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
color = NuvioColors.TextPrimary
)
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = Modifier.padding(12.dp)) {
BasicTextField(
value = uiState.installUrl,
onValueChange = viewModel::onInstallUrlChange,
modifier = Modifier
.fillMaxWidth()
.focusRequester(textFieldFocusRequester)
.onFocusChanged {
if (!it.isFocused && isEditing) {
// Surface always stays in the tree for stable D-pad focus
Surface(
onClick = { isEditing = true },
modifier = Modifier
.weight(1f)
.focusRequester(surfaceFocusRequester),
colors = ClickableSurfaceDefaults.colors(
containerColor = NuvioColors.BackgroundElevated,
focusedContainerColor = NuvioColors.BackgroundElevated
),
border = ClickableSurfaceDefaults.border(
border = Border(
border = BorderStroke(1.dp, NuvioColors.Border),
shape = RoundedCornerShape(12.dp)
),
focusedBorder = Border(
border = BorderStroke(2.dp, NuvioColors.FocusRing),
shape = RoundedCornerShape(12.dp)
)
),
shape = ClickableSurfaceDefaults.shape(RoundedCornerShape(12.dp)),
scale = ClickableSurfaceDefaults.scale(focusedScale = 1f)
) {
Box(modifier = Modifier.padding(12.dp)) {
BasicTextField(
value = uiState.installUrl,
onValueChange = viewModel::onInstallUrlChange,
modifier = Modifier
.fillMaxWidth()
.focusRequester(textFieldFocusRequester)
.onFocusChanged {
if (!it.isFocused && isEditing) {
isEditing = false
keyboardController?.hide()
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
viewModel.installAddon()
isEditing = false
keyboardController?.hide()
installButtonFocusRequester.requestFocus()
}
},
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
viewModel.installAddon()
isEditing = false
keyboardController?.hide()
installButtonFocusRequester.requestFocus()
),
textStyle = MaterialTheme.typography.bodyMedium.copy(
color = NuvioColors.TextPrimary
),
cursorBrush = SolidColor(if (isEditing) NuvioColors.Primary else Color.Transparent),
decorationBox = { innerTextField ->
if (uiState.installUrl.isEmpty()) {
Text(
text = stringResource(R.string.addon_install_placeholder),
style = MaterialTheme.typography.bodyMedium,
color = NuvioColors.TextTertiary
)
}
innerTextField()
}
),
textStyle = MaterialTheme.typography.bodyMedium.copy(
color = NuvioColors.TextPrimary
),
cursorBrush = SolidColor(if (isEditing) NuvioColors.Primary else Color.Transparent),
decorationBox = { innerTextField ->
if (uiState.installUrl.isEmpty()) {
Text(
text = stringResource(R.string.addon_install_placeholder),
style = MaterialTheme.typography.bodyMedium,
color = NuvioColors.TextTertiary
)
}
innerTextField()
}
)
)
}
}
Button(
onClick = {
viewModel.installAddon()
isEditing = false
keyboardController?.hide()
installButtonFocusRequester.requestFocus()
},
enabled = !uiState.isInstalling,
modifier = Modifier.focusRequester(installButtonFocusRequester),
colors = ButtonDefaults.colors(
containerColor = NuvioColors.BackgroundCard,
contentColor = NuvioColors.TextPrimary,
focusedContainerColor = NuvioColors.FocusBackground,
focusedContentColor = NuvioColors.Primary
),
shape = ButtonDefaults.shape(RoundedCornerShape(12.dp))
) {
Text(text = if (uiState.isInstalling) stringResource(R.string.addon_installing) else stringResource(R.string.addon_install_btn))
}
}
Button(
onClick = {
viewModel.installAddon()
isEditing = false
keyboardController?.hide()
installButtonFocusRequester.requestFocus()
},
enabled = !uiState.isInstalling,
modifier = Modifier.focusRequester(installButtonFocusRequester),
colors = ButtonDefaults.colors(
containerColor = NuvioColors.BackgroundCard,
contentColor = NuvioColors.TextPrimary,
focusedContainerColor = NuvioColors.FocusBackground,
focusedContentColor = NuvioColors.Primary
),
shape = ButtonDefaults.shape(RoundedCornerShape(12.dp))
) {
Text(text = if (uiState.isInstalling) stringResource(R.string.addon_installing) else stringResource(R.string.addon_install_btn))
AnimatedVisibility(visible = uiState.error != null) {
Text(
text = uiState.error.orEmpty(),
style = MaterialTheme.typography.bodyMedium,
color = NuvioColors.Error,
modifier = Modifier.padding(top = 10.dp)
)
}
}
AnimatedVisibility(visible = uiState.error != null) {
Text(
text = uiState.error.orEmpty(),
style = MaterialTheme.typography.bodyMedium,
color = NuvioColors.Error,
modifier = Modifier.padding(top = 10.dp)
)
}
}
}

View file

@ -49,14 +49,12 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.nuvio.tv.domain.model.AppTheme
import com.nuvio.tv.data.local.AVAILABLE_SUBTITLE_LANGUAGES
import com.nuvio.tv.data.local.AudioLanguageOption
import com.nuvio.tv.data.local.PlayerSettings
import com.nuvio.tv.data.local.TrailerSettings
import com.nuvio.tv.ui.components.NuvioDialog
import com.nuvio.tv.ui.theme.NuvioColors
import com.nuvio.tv.ui.theme.NuvioTheme
internal fun LazyListScope.trailerAndAudioSettingsItems(
playerSettings: PlayerSettings,
@ -295,7 +293,6 @@ private fun AudioLanguageSelectionDialog(
onDismiss: () -> Unit
) {
val focusRequester = remember { FocusRequester() }
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
val specialOptions = listOf(
AudioLanguageOption.DEFAULT to stringResource(R.string.audio_lang_default),
AudioLanguageOption.DEVICE to stringResource(R.string.audio_lang_device)
@ -337,8 +334,8 @@ private fun AudioLanguageSelectionDialog(
.then(if (index == 0) Modifier.focusRequester(focusRequester) else Modifier)
.onFocusChanged { isFocused = it.isFocused },
colors = CardDefaults.colors(
containerColor = if (isSelected) selectedAccent.copy(alpha = 0.22f) else NuvioColors.BackgroundCard,
focusedContainerColor = if (isSelected) selectedAccent.copy(alpha = 0.28f) else NuvioColors.FocusBackground
containerColor = if (isSelected) NuvioColors.FocusBackground else NuvioColors.BackgroundCard,
focusedContainerColor = NuvioColors.FocusBackground
),
shape = CardDefaults.shape(shape = RoundedCornerShape(10.dp)),
scale = CardDefaults.scale(focusedScale = 1f)
@ -352,7 +349,7 @@ private fun AudioLanguageSelectionDialog(
Text(
text = name,
style = MaterialTheme.typography.bodyLarge,
color = NuvioColors.TextPrimary,
color = if (isSelected) NuvioColors.Primary else NuvioColors.TextPrimary,
modifier = Modifier.weight(1f)
)
if (isSelected) {
@ -360,7 +357,7 @@ private fun AudioLanguageSelectionDialog(
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.cd_selected),
tint = selectedAccent,
tint = NuvioColors.Primary,
modifier = Modifier.size(20.dp)
)
}
@ -379,7 +376,6 @@ private fun DecoderPriorityDialog(
onDismiss: () -> Unit
) {
val focusRequester = remember { FocusRequester() }
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
val options = listOf(
Triple(0, stringResource(R.string.audio_decoder_device_only), stringResource(R.string.audio_decoder_device_only_desc)),
Triple(1, stringResource(R.string.audio_decoder_prefer_device), stringResource(R.string.audio_decoder_prefer_device_desc)),
@ -419,8 +415,8 @@ private fun DecoderPriorityDialog(
.fillMaxWidth()
.then(if (index == 0) Modifier.focusRequester(focusRequester) else Modifier),
colors = CardDefaults.colors(
containerColor = if (isSelected) selectedAccent.copy(alpha = 0.22f) else NuvioColors.BackgroundCard,
focusedContainerColor = if (isSelected) selectedAccent.copy(alpha = 0.28f) else NuvioColors.FocusBackground
containerColor = if (isSelected) NuvioColors.FocusBackground else NuvioColors.BackgroundCard,
focusedContainerColor = NuvioColors.FocusBackground
),
shape = CardDefaults.shape(shape = RoundedCornerShape(10.dp)),
scale = CardDefaults.scale(focusedScale = 1f)
@ -434,7 +430,7 @@ private fun DecoderPriorityDialog(
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
color = NuvioColors.TextPrimary,
color = if (isSelected) NuvioColors.Primary else NuvioColors.TextPrimary,
style = MaterialTheme.typography.bodyLarge
)
Spacer(modifier = Modifier.height(4.dp))
@ -449,7 +445,7 @@ private fun DecoderPriorityDialog(
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.cd_selected),
tint = selectedAccent,
tint = NuvioColors.Primary,
modifier = Modifier.size(20.dp)
)
}

View file

@ -66,7 +66,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.res.stringResource
import com.nuvio.tv.R
import com.nuvio.tv.domain.model.AppTheme
import androidx.compose.ui.text.input.KeyboardType
import android.view.KeyEvent
import androidx.compose.ui.input.key.onKeyEvent
@ -86,6 +85,8 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Icon
import androidx.tv.material3.IconButton
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Switch
import androidx.tv.material3.SwitchDefaults
import androidx.tv.material3.Text
import com.nuvio.tv.data.local.AVAILABLE_SUBTITLE_LANGUAGES
import com.nuvio.tv.data.local.AudioLanguageOption
@ -97,7 +98,6 @@ import com.nuvio.tv.data.local.StreamAutoPlaySource
import com.nuvio.tv.data.local.TrailerSettings
import com.nuvio.tv.ui.components.NuvioDialog
import com.nuvio.tv.ui.theme.NuvioColors
import com.nuvio.tv.ui.theme.NuvioTheme
import kotlinx.coroutines.launch
import androidx.compose.material.icons.filled.PlayCircle
import androidx.compose.material.icons.filled.PauseCircle
@ -423,9 +423,15 @@ internal fun ToggleSettingsItem(
Spacer(modifier = Modifier.width(16.dp))
SettingsTogglePill(
Switch(
checked = isChecked,
enabled = enabled
onCheckedChange = null, // Handled by Card onClick
colors = SwitchDefaults.colors(
checkedThumbColor = NuvioColors.Secondary.copy(alpha = contentAlpha),
checkedTrackColor = NuvioColors.Secondary.copy(alpha = 0.35f * contentAlpha),
uncheckedThumbColor = NuvioColors.TextSecondary.copy(alpha = contentAlpha),
uncheckedTrackColor = NuvioColors.Border
)
)
}
}
@ -441,7 +447,6 @@ internal fun RenderTypeSettingsItem(
enabled: Boolean = true
) {
var isFocused by remember { mutableStateOf(false) }
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
val contentAlpha = if (enabled) 1f else 0.4f
Card(
@ -457,12 +462,12 @@ internal fun RenderTypeSettingsItem(
},
colors = CardDefaults.colors(
containerColor = if (isSelected) {
selectedAccent.copy(alpha = 0.22f * contentAlpha)
NuvioColors.Primary.copy(alpha = 0.15f * contentAlpha)
} else {
NuvioColors.BackgroundCard
},
focusedContainerColor = if (isSelected) {
selectedAccent.copy(alpha = 0.28f * contentAlpha)
NuvioColors.Primary.copy(alpha = 0.15f * contentAlpha)
} else {
NuvioColors.BackgroundCard
}
@ -473,7 +478,7 @@ internal fun RenderTypeSettingsItem(
shape = RoundedCornerShape(SettingsSecondaryCardRadius)
),
border = if (isSelected) Border(
border = BorderStroke(2.dp, selectedAccent.copy(alpha = contentAlpha)),
border = BorderStroke(2.dp, NuvioColors.Primary.copy(alpha = contentAlpha)),
shape = RoundedCornerShape(SettingsSecondaryCardRadius)
) else Border.None
),
@ -490,9 +495,9 @@ internal fun RenderTypeSettingsItem(
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = NuvioColors.TextPrimary.copy(alpha = contentAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis
color = (if (isSelected) NuvioColors.Primary else NuvioColors.TextPrimary).copy(alpha = contentAlpha),
maxLines = 2,
overflow = TextOverflow.Clip
)
Spacer(modifier = Modifier.height(2.dp))
Text(
@ -507,7 +512,7 @@ internal fun RenderTypeSettingsItem(
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.cd_selected),
tint = selectedAccent.copy(alpha = contentAlpha),
tint = NuvioColors.Primary.copy(alpha = contentAlpha),
modifier = Modifier.size(24.dp)
)
}
@ -1001,7 +1006,6 @@ private fun LanguageOptionItem(
modifier: Modifier = Modifier
) {
var isFocused by remember { mutableStateOf(false) }
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
Card(
onClick = onClick,
@ -1010,8 +1014,8 @@ private fun LanguageOptionItem(
.then(modifier)
.onFocusChanged { isFocused = it.isFocused },
colors = CardDefaults.colors(
containerColor = if (isSelected) selectedAccent.copy(alpha = 0.22f) else NuvioColors.BackgroundCard,
focusedContainerColor = if (isSelected) selectedAccent.copy(alpha = 0.28f) else NuvioColors.FocusBackground
containerColor = if (isSelected) NuvioColors.FocusBackground else NuvioColors.BackgroundCard,
focusedContainerColor = NuvioColors.FocusBackground
),
shape = CardDefaults.shape(shape = RoundedCornerShape(10.dp)),
scale = CardDefaults.scale(focusedScale = 1f)
@ -1025,7 +1029,7 @@ private fun LanguageOptionItem(
Text(
text = name,
style = MaterialTheme.typography.bodyLarge,
color = NuvioColors.TextPrimary,
color = if (isSelected) NuvioColors.Primary else NuvioColors.TextPrimary,
modifier = Modifier.weight(1f)
)
@ -1042,7 +1046,7 @@ private fun LanguageOptionItem(
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.cd_selected),
tint = selectedAccent,
tint = NuvioColors.Primary,
modifier = Modifier.size(20.dp)
)
}

View file

@ -30,7 +30,6 @@ import com.nuvio.tv.R
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.nuvio.tv.domain.model.AppTheme
import com.nuvio.tv.data.local.AVAILABLE_SUBTITLE_LANGUAGES
import com.nuvio.tv.data.local.LibassRenderType
import com.nuvio.tv.data.local.PlayerSettings
@ -39,7 +38,6 @@ import com.nuvio.tv.data.local.SubtitleOrganizationMode
import com.nuvio.tv.data.local.SUBTITLE_LANGUAGE_FORCED
import com.nuvio.tv.ui.components.NuvioDialog
import com.nuvio.tv.ui.theme.NuvioColors
import com.nuvio.tv.ui.theme.NuvioTheme
private val subtitleColors = listOf(
Color.White,
@ -469,7 +467,6 @@ private fun SubtitleOrganizationModeDialog(
onModeSelected: (SubtitleOrganizationMode) -> Unit,
onDismiss: () -> Unit
) {
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
val options = listOf(
Triple(SubtitleOrganizationMode.NONE, stringResource(R.string.sub_org_none), stringResource(R.string.sub_org_none_desc)),
Triple(SubtitleOrganizationMode.BY_LANGUAGE, stringResource(R.string.sub_org_by_lang), stringResource(R.string.sub_org_by_lang_desc)),
@ -501,8 +498,8 @@ private fun SubtitleOrganizationModeDialog(
onClick = { onModeSelected(mode) },
modifier = androidx.compose.ui.Modifier.fillMaxWidth(),
colors = androidx.tv.material3.CardDefaults.colors(
containerColor = if (isSelected) selectedAccent.copy(alpha = 0.22f) else NuvioColors.BackgroundCard,
focusedContainerColor = if (isSelected) selectedAccent.copy(alpha = 0.28f) else NuvioColors.FocusBackground
containerColor = if (isSelected) NuvioColors.FocusBackground else NuvioColors.BackgroundCard,
focusedContainerColor = NuvioColors.FocusBackground
),
shape = androidx.tv.material3.CardDefaults.shape(
shape = androidx.compose.foundation.shape.RoundedCornerShape(10.dp)
@ -521,7 +518,7 @@ private fun SubtitleOrganizationModeDialog(
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = NuvioColors.TextPrimary
color = if (isSelected) NuvioColors.Primary else NuvioColors.TextPrimary
)
Spacer(modifier = androidx.compose.ui.Modifier.height(4.dp))
Text(
@ -534,7 +531,7 @@ private fun SubtitleOrganizationModeDialog(
androidx.tv.material3.Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(R.string.cd_selected),
tint = selectedAccent
tint = NuvioColors.Primary
)
}
}

View file

@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@ -28,13 +27,6 @@ class ProfileSettingsViewModel @Inject constructor(
.map { it == 1 }
.stateIn(viewModelScope, SharingStarted.Eagerly, true)
val activeProfile: StateFlow<UserProfile?> = combine(
profileManager.activeProfileId,
profileManager.profiles
) { activeId, profiles ->
profiles.firstOrNull { it.id == activeId }
}.stateIn(viewModelScope, SharingStarted.Eagerly, profileManager.activeProfile)
val canAddProfile: Boolean
get() = profileManager.profiles.value.size < 4

View file

@ -58,10 +58,8 @@ import androidx.tv.material3.Text
import coil.compose.rememberAsyncImagePainter
import coil.decode.SvgDecoder
import coil.request.ImageRequest
import com.nuvio.tv.domain.model.AppTheme
import com.nuvio.tv.R
import com.nuvio.tv.ui.theme.NuvioColors
import com.nuvio.tv.ui.theme.NuvioTheme
internal val SettingsContainerRadius = 28.dp
internal val SettingsPillRadius = 999.dp
@ -540,7 +538,6 @@ internal fun SettingsChoiceChip(
onFocused: () -> Unit = {}
) {
var isFocused by remember { mutableStateOf(false) }
val selectedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
Card(
onClick = onClick,
@ -552,12 +549,12 @@ internal fun SettingsChoiceChip(
}
},
colors = CardDefaults.colors(
containerColor = if (selected) selectedAccent.copy(alpha = 0.22f) else NuvioColors.Background,
focusedContainerColor = if (selected) selectedAccent.copy(alpha = 0.28f) else NuvioColors.Background
containerColor = if (selected) NuvioColors.FocusRing.copy(alpha = 0.2f) else NuvioColors.Background,
focusedContainerColor = if (selected) NuvioColors.FocusRing.copy(alpha = 0.2f) else NuvioColors.Background
),
border = CardDefaults.border(
border = if (selected) Border(
border = BorderStroke(1.dp, selectedAccent),
border = BorderStroke(1.dp, NuvioColors.FocusRing),
shape = RoundedCornerShape(SettingsPillRadius)
) else Border.None,
focusedBorder = Border(
@ -571,18 +568,17 @@ internal fun SettingsChoiceChip(
Text(
text = label,
style = MaterialTheme.typography.labelMedium,
color = if (selected) selectedAccent else if (isFocused) NuvioColors.TextPrimary else NuvioColors.TextSecondary,
color = if (selected || isFocused) NuvioColors.TextPrimary else NuvioColors.TextSecondary,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp)
)
}
}
@Composable
internal fun SettingsTogglePill(
private fun SettingsTogglePill(
checked: Boolean,
enabled: Boolean
) {
val checkedAccent = if (NuvioTheme.currentTheme == AppTheme.WHITE) Color.White else NuvioColors.Secondary
val alpha = if (enabled) 1f else 0.35f
Box(
modifier = Modifier
@ -591,7 +587,7 @@ internal fun SettingsTogglePill(
.clip(RoundedCornerShape(SettingsPillRadius))
.background(
if (checked) {
checkedAccent.copy(alpha = 0.42f * alpha)
NuvioColors.Secondary.copy(alpha = 0.35f * alpha)
} else {
NuvioColors.Border.copy(alpha = alpha)
}

View file

@ -54,7 +54,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.tv.material3.ExperimentalTvMaterial3Api
import com.nuvio.tv.BuildConfig
import com.nuvio.tv.R
import com.nuvio.tv.ui.screens.addon.AddonManagerScreen
import com.nuvio.tv.ui.screens.plugin.PluginScreenContent
import com.nuvio.tv.ui.theme.NuvioColors
import kotlinx.coroutines.delay
@ -64,7 +63,6 @@ internal enum class SettingsCategory {
PROFILES,
APPEARANCE,
LAYOUT,
ADDONS,
PLUGINS,
INTEGRATION,
PLAYBACK,
@ -128,13 +126,6 @@ private fun rememberSettingsSectionSpecs() = listOf(
subtitle = stringResource(R.string.settings_layout_subtitle),
destination = SettingsSectionDestination.Inline
),
SettingsSectionSpec(
category = SettingsCategory.ADDONS,
title = stringResource(R.string.addon_title),
rawIconRes = R.raw.sidebar_plugin,
subtitle = stringResource(R.string.settings_addons_subtitle),
destination = SettingsSectionDestination.Inline
),
SettingsSectionSpec(
category = SettingsCategory.PLUGINS,
title = stringResource(R.string.settings_plugins),
@ -184,29 +175,18 @@ fun SettingsScreen(
showBuiltInHeader: Boolean = true,
onNavigateToTrakt: () -> Unit = {},
onNavigateToAuthQrSignIn: () -> Unit = {},
onNavigateToCatalogOrder: () -> Unit = {},
profileViewModel: ProfileSettingsViewModel = hiltViewModel()
) {
val isPrimaryProfileActive by profileViewModel.isPrimaryProfileActive.collectAsStateWithLifecycle()
val activeProfile by profileViewModel.activeProfile.collectAsStateWithLifecycle()
val usesPrimaryProfileAddons = activeProfile?.let { !it.isPrimary && it.usesPrimaryAddons } == true
val usesPrimaryProfilePlugins = activeProfile?.let { !it.isPrimary && it.usesPrimaryPlugins } == true
val allSectionSpecs = rememberSettingsSectionSpecs()
val visibleSections = remember(
isPrimaryProfileActive,
usesPrimaryProfileAddons,
usesPrimaryProfilePlugins,
allSectionSpecs
) {
val visibleSections = remember(isPrimaryProfileActive, allSectionSpecs) {
allSectionSpecs.filter { section ->
when (section.category) {
SettingsCategory.DEBUG -> BuildConfig.IS_DEBUG_BUILD
SettingsCategory.PROFILES -> isPrimaryProfileActive
SettingsCategory.ACCOUNT -> isPrimaryProfileActive
SettingsCategory.TRAKT -> isPrimaryProfileActive
SettingsCategory.ADDONS -> !usesPrimaryProfileAddons
SettingsCategory.PLUGINS -> !usesPrimaryProfilePlugins
else -> true
}
}
@ -393,9 +373,6 @@ fun SettingsScreen(
null
}
)
SettingsCategory.ADDONS -> AddonsSettingsContent(
onNavigateToCatalogOrder = onNavigateToCatalogOrder
)
SettingsCategory.PLAYBACK -> PlaybackSettingsContent(
initialFocusRequester = if (allowDetailAutofocus) {
contentFocusRequesters[SettingsCategory.PLAYBACK]
@ -467,35 +444,6 @@ private fun PluginsSettingsContent() {
}
}
@Composable
private fun AddonsSettingsContent(
onNavigateToCatalogOrder: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
SettingsDetailHeader(
title = stringResource(R.string.addon_title),
subtitle = stringResource(R.string.settings_addons_section_subtitle)
)
SettingsGroupCard(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
contentAlignment = Alignment.TopStart
) {
AddonManagerScreen(
showBuiltInHeader = false,
embeddedInSettings = true,
onNavigateToCatalogOrder = onNavigateToCatalogOrder
)
}
}
}
}
@Composable
private fun AccountSettingsInline(
onNavigateToAuthQrSignIn: () -> Unit

View file

@ -127,7 +127,6 @@
<string name="settings_profiles_subtitle">Manage user profiles.</string>
<string name="settings_layout">Layout</string>
<string name="settings_layout_subtitle">Home structure and poster styles.</string>
<string name="settings_addons_subtitle">Addon installation and catalog sources.</string>
<string name="settings_plugins">Plugins</string>
<string name="settings_plugins_subtitle">Repositories and providers.</string>
<string name="settings_integration">Integration</string>
@ -137,7 +136,6 @@
<string name="settings_about_subtitle">Version and policies.</string>
<string name="settings_debug">Debug</string>
<string name="settings_debug_subtitle">Developer tools and feature flags.</string>
<string name="settings_addons_section_subtitle">Manage addon installation, updates, and catalog order.</string>
<string name="settings_plugins_section_subtitle">Manage repositories, providers, and plugin states.</string>
<string name="settings_account_section_subtitle">Account and sync status.</string>
<string name="account_stat_addons">addons</string>