diff --git a/dolby/res/values/strings.xml b/dolby/res/values/strings.xml index 534e8ff..9130d0d 100644 --- a/dolby/res/values/strings.xml +++ b/dolby/res/values/strings.xml @@ -18,7 +18,7 @@ Max Unknown On (%1$s) - Settings + Audio tuning Advanced settings Choose a different profile to show advanced settings. Bass enhancer diff --git a/dolby/src/org/lunaris/dolby/ui/components/InteractiveFrequencyResponseCurve.kt b/dolby/src/org/lunaris/dolby/ui/components/InteractiveFrequencyResponseCurve.kt index 11deb2b..1946281 100644 --- a/dolby/src/org/lunaris/dolby/ui/components/InteractiveFrequencyResponseCurve.kt +++ b/dolby/src/org/lunaris/dolby/ui/components/InteractiveFrequencyResponseCurve.kt @@ -5,8 +5,10 @@ package org.lunaris.dolby.ui.components +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -16,29 +18,47 @@ import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.lunaris.dolby.domain.models.BandGain import kotlin.math.abs -import kotlin.math.pow @Composable fun InteractiveFrequencyResponseCurve( bandGains: List, onBandGainChange: (index: Int, newGain: Int) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + isActive: Boolean = false ) { val primaryColor = MaterialTheme.colorScheme.primary val surfaceColor = MaterialTheme.colorScheme.surfaceVariant val onSurfaceColor = MaterialTheme.colorScheme.onSurface val secondaryColor = MaterialTheme.colorScheme.secondary + val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer + + val backgroundColor = if (isActive) { + primaryContainerColor + } else { + surfaceColor.copy(alpha = 0.2f) + } + + val borderColor = if (isActive) { + primaryColor + } else { + Color.Transparent + } + + val borderWidth = if (isActive) 2.dp else 0.dp var draggedIndex by remember { mutableStateOf(null) } @@ -58,7 +78,13 @@ fun InteractiveFrequencyResponseCurve( Canvas( modifier = Modifier .fillMaxSize() - .background(surfaceColor.copy(alpha = 0.2f)) + .clip(RoundedCornerShape(16.dp)) + .background(backgroundColor) + .border( + width = borderWidth, + color = borderColor, + shape = RoundedCornerShape(16.dp) + ) .pointerInput(Unit) { detectDragGestures( onDragStart = { offset -> @@ -111,8 +137,20 @@ fun InteractiveFrequencyResponseCurve( val height = size.height val centerY = height / 2 + val gridColor = if (isActive) { + surfaceColor.copy(alpha = 0.4f) + } else { + surfaceColor + } + + val gridVerticalColor = if (isActive) { + surfaceColor.copy(alpha = 0.3f) + } else { + surfaceColor.copy(alpha = 0.2f) + } + drawLine( - color = surfaceColor, + color = gridColor, start = Offset(0f, centerY), end = Offset(width, centerY), strokeWidth = 2f @@ -121,7 +159,7 @@ fun InteractiveFrequencyResponseCurve( for (i in 1..4) { val y = (height / 5) * i drawLine( - color = surfaceColor.copy(alpha = 0.3f), + color = gridColor.copy(alpha = 0.3f), start = Offset(0f, y), end = Offset(width, y), strokeWidth = 1f @@ -132,7 +170,7 @@ fun InteractiveFrequencyResponseCurve( bandGains.forEachIndexed { index, _ -> val x = index * stepX drawLine( - color = surfaceColor.copy(alpha = 0.2f), + color = gridVerticalColor, start = Offset(x, 0f), end = Offset(x, height), strokeWidth = 1f @@ -166,8 +204,8 @@ fun InteractiveFrequencyResponseCurve( drawPath( path = path, - color = primaryColor, - style = Stroke(width = 4f) + color = if (isActive) primaryColor else primaryColor.copy(alpha = 0.8f), + style = Stroke(width = if (isActive) 5f else 4f) ) val fillPath = Path().apply { @@ -180,10 +218,17 @@ fun InteractiveFrequencyResponseCurve( drawPath( path = fillPath, brush = Brush.verticalGradient( - colors = listOf( - primaryColor.copy(alpha = 0.3f), - primaryColor.copy(alpha = 0.05f) - ) + colors = if (isActive) { + listOf( + primaryColor.copy(alpha = 0.4f), + primaryColor.copy(alpha = 0.08f) + ) + } else { + listOf( + primaryColor.copy(alpha = 0.3f), + primaryColor.copy(alpha = 0.05f) + ) + } ) ) @@ -210,7 +255,7 @@ fun InteractiveFrequencyResponseCurve( ) drawCircle( - color = primaryColor, + color = if (isActive) primaryColor else primaryColor.copy(alpha = 0.8f), radius = pointRadius - 2f, center = Offset(x, y) ) @@ -233,7 +278,11 @@ fun InteractiveFrequencyResponseCurve( "${bandGain.frequency}" }, style = MaterialTheme.typography.labelSmall, - color = onSurfaceColor.copy(alpha = 0.7f), + color = if (isActive) { + onSurfaceColor.copy(alpha = 0.8f) + } else { + onSurfaceColor.copy(alpha = 0.7f) + }, fontSize = 10.sp, fontWeight = FontWeight.Medium ) @@ -249,7 +298,11 @@ fun InteractiveFrequencyResponseCurve( Text( text = "+15", style = MaterialTheme.typography.labelSmall, - color = secondaryColor.copy(alpha = 0.8f), + color = if (isActive) { + secondaryColor + } else { + secondaryColor.copy(alpha = 0.8f) + }, fontSize = 10.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(vertical = 4.dp) @@ -257,14 +310,22 @@ fun InteractiveFrequencyResponseCurve( Text( text = "0", style = MaterialTheme.typography.labelSmall, - color = secondaryColor.copy(alpha = 0.8f), + color = if (isActive) { + secondaryColor + } else { + secondaryColor.copy(alpha = 0.8f) + }, fontSize = 10.sp, fontWeight = FontWeight.SemiBold ) Text( text = "-15", style = MaterialTheme.typography.labelSmall, - color = secondaryColor.copy(alpha = 0.8f), + color = if (isActive) { + secondaryColor + } else { + secondaryColor.copy(alpha = 0.8f) + }, fontSize = 10.sp, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(vertical = 4.dp) diff --git a/dolby/src/org/lunaris/dolby/ui/screens/ModernAdvancedSettingsScreen.kt b/dolby/src/org/lunaris/dolby/ui/screens/ModernAdvancedSettingsScreen.kt new file mode 100644 index 0000000..fc52ae5 --- /dev/null +++ b/dolby/src/org/lunaris/dolby/ui/screens/ModernAdvancedSettingsScreen.kt @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2024-2025 Lunaris AOSP + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.lunaris.dolby.ui.screens + +import androidx.compose.animation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import org.lunaris.dolby.R +import org.lunaris.dolby.domain.models.DolbyUiState +import org.lunaris.dolby.ui.components.* +import org.lunaris.dolby.ui.viewmodel.DolbyViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ModernAdvancedSettingsScreen( + viewModel: DolbyViewModel, + navController: NavController +) { + val uiState by viewModel.uiState.collectAsState() + val currentRoute by navController.currentBackStackEntryFlow.collectAsState(null) + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + stringResource(R.string.dolby_category_adv_settings), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface + ) + ) + }, + bottomBar = { + BottomNavigationBar( + currentRoute = currentRoute?.destination?.route ?: Screen.Advanced.route, + onNavigate = { route -> + if (currentRoute?.destination?.route != route) { + navController.navigate(route) { + popUpTo(Screen.Settings.route) { saveState = true } + launchSingleTop = true + restoreState = true + } + } + } + ) + } + ) { paddingValues -> + when (val state = uiState) { + is DolbyUiState.Loading -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + is DolbyUiState.Success -> { + ModernAdvancedSettingsContent( + state = state, + viewModel = viewModel, + modifier = Modifier.padding(paddingValues) + ) + } + is DolbyUiState.Error -> { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Default.Error, + contentDescription = null, + modifier = Modifier.size(48.dp), + tint = MaterialTheme.colorScheme.error + ) + Text( + text = state.message, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error + ) + } + } + } + } + } +} + +@Composable +private fun ModernAdvancedSettingsContent( + state: DolbyUiState.Success, + viewModel: DolbyViewModel, + modifier: Modifier = Modifier +) { + LazyColumn( + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (state.settings.enabled) { + item { + ModernSettingsCard( + title = stringResource(R.string.dolby_category_settings), + icon = Icons.Default.Tune + ) { + Column { + ModernSettingSwitch( + title = stringResource(R.string.dolby_bass_enhancer), + subtitle = stringResource(R.string.dolby_bass_enhancer_summary), + checked = state.profileSettings.bassLevel > 0, + onCheckedChange = { enabled -> + if (enabled && state.profileSettings.bassLevel == 0) { + viewModel.setBassLevel(50) + } else if (!enabled) { + viewModel.setBassLevel(0) + } + }, + icon = Icons.Default.MusicNote + ) + + AnimatedVisibility(visible = state.profileSettings.bassLevel > 0) { + Column { + Spacer(modifier = Modifier.height(8.dp)) + ModernSettingSelector( + title = stringResource(R.string.dolby_bass_curve), + currentValue = state.profileSettings.bassCurve, + entries = R.array.dolby_bass_curve_entries, + values = R.array.dolby_bass_curve_values, + onValueChange = { viewModel.setBassCurve(it) }, + icon = Icons.Default.Equalizer + ) + Spacer(modifier = Modifier.height(8.dp)) + ModernSettingSlider( + title = stringResource(R.string.dolby_bass_level), + value = state.profileSettings.bassLevel, + onValueChange = { viewModel.setBassLevel(it.toInt()) }, + valueRange = 0f..100f, + steps = 19, + valueLabel = { "$it%" } + ) + } + } + } + + Spacer(modifier = Modifier.height(12.dp)) + Column { + ModernSettingSwitch( + title = stringResource(R.string.dolby_treble_enhancer), + subtitle = stringResource(R.string.dolby_treble_enhancer_summary), + checked = state.profileSettings.trebleLevel > 0, + onCheckedChange = { enabled -> + if (enabled && state.profileSettings.trebleLevel == 0) { + viewModel.setTrebleLevel(30) + } else if (!enabled) { + viewModel.setTrebleLevel(0) + } + }, + icon = Icons.Default.GraphicEq + ) + + AnimatedVisibility(visible = state.profileSettings.trebleLevel > 0) { + Column { + Spacer(modifier = Modifier.height(8.dp)) + ModernSettingSlider( + title = stringResource(R.string.dolby_treble_level), + value = state.profileSettings.trebleLevel, + onValueChange = { viewModel.setTrebleLevel(it.toInt()) }, + valueRange = 0f..100f, + steps = 19, + valueLabel = { "$it%" } + ) + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + ModernSettingSwitch( + title = stringResource(R.string.dolby_volume_leveler), + subtitle = stringResource(R.string.dolby_volume_leveler_summary), + checked = state.settings.volumeLevelerEnabled, + onCheckedChange = { viewModel.setVolumeLeveler(it) }, + icon = Icons.Default.BarChart + ) + } + } + + if (state.settings.currentProfile != 0) { + item { + ModernSettingsCard( + title = "Surround Virtualizer", + icon = Icons.Default.Headphones + ) { + if (state.isOnSpeaker) { + ModernSettingSwitch( + title = stringResource(R.string.dolby_spk_virtualizer), + subtitle = stringResource(R.string.dolby_spk_virtualizer_summary), + checked = state.profileSettings.speakerVirtualizerEnabled, + onCheckedChange = { viewModel.setSpeakerVirtualizer(it) }, + icon = Icons.Default.Speaker + ) + } else { + ModernSettingSwitch( + title = stringResource(R.string.dolby_hp_virtualizer), + subtitle = stringResource(R.string.dolby_hp_virtualizer_summary), + checked = state.profileSettings.headphoneVirtualizerEnabled, + onCheckedChange = { viewModel.setHeadphoneVirtualizer(it) }, + icon = Icons.Default.Headphones + ) + + AnimatedVisibility(visible = state.profileSettings.headphoneVirtualizerEnabled) { + Column { + Spacer(modifier = Modifier.height(16.dp)) + ModernSettingSlider( + title = stringResource(R.string.dolby_hp_virtualizer_dolby_strength), + value = state.profileSettings.stereoWideningAmount, + onValueChange = { viewModel.setStereoWidening(it.toInt()) }, + valueRange = 4f..64f, + steps = 59 + ) + } + } + } + } + } + + item { + ModernSettingsCard( + title = "Dialogue Enhancement", + icon = Icons.Default.RecordVoiceOver + ) { + ModernSettingSwitch( + title = stringResource(R.string.dolby_dialogue_enhancer), + subtitle = stringResource(R.string.dolby_dialogue_enhancer_summary), + checked = state.profileSettings.dialogueEnhancerEnabled, + onCheckedChange = { viewModel.setDialogueEnhancer(it) }, + icon = Icons.Default.RecordVoiceOver + ) + + AnimatedVisibility(visible = state.profileSettings.dialogueEnhancerEnabled) { + Column { + Spacer(modifier = Modifier.height(16.dp)) + ModernSettingSlider( + title = stringResource(R.string.dolby_dialogue_enhancer_dolby_strength), + value = state.profileSettings.dialogueEnhancerAmount, + onValueChange = { viewModel.setDialogueEnhancerAmount(it.toInt()) }, + valueRange = 1f..12f, + steps = 10 + ) + } + } + } + } + } + } else if (state.settings.currentProfile == 0 && state.settings.enabled) { + item { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.medium, + color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f) + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = stringResource(R.string.dolby_adv_settings_footer), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + } + } else if (!state.settings.enabled) { + item { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.medium, + color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f) + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSecondaryContainer, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = "Enable Dolby Atmos to access advanced settings", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } + } + } + + item { + Spacer(modifier = Modifier.height(80.dp)) + } + } +} + +@Composable +private fun BottomNavigationBar( + currentRoute: String, + onNavigate: (String) -> Unit +) { + NavigationBar( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + tonalElevation = 3.dp + ) { + NavigationBarItem( + icon = { + Icon( + Icons.Default.Home, + contentDescription = null + ) + }, + label = { Text("Home") }, + selected = currentRoute == Screen.Settings.route, + onClick = { onNavigate(Screen.Settings.route) } + ) + + NavigationBarItem( + icon = { + AnimatedEqualizerIconDynamic( + color = if (currentRoute == Screen.Equalizer.route) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + size = 24.dp + ) + }, + label = { Text("Equalizer") }, + selected = currentRoute == Screen.Equalizer.route, + onClick = { onNavigate(Screen.Equalizer.route) } + ) + + NavigationBarItem( + icon = { + Icon( + Icons.Default.Settings, + contentDescription = null + ) + }, + label = { Text("Advanced") }, + selected = currentRoute == Screen.Advanced.route, + onClick = { onNavigate(Screen.Advanced.route) } + ) + } +} \ No newline at end of file diff --git a/dolby/src/org/lunaris/dolby/ui/screens/ModernDolbySettingsScreen.kt b/dolby/src/org/lunaris/dolby/ui/screens/ModernDolbySettingsScreen.kt index 8da9496..63cd496 100644 --- a/dolby/src/org/lunaris/dolby/ui/screens/ModernDolbySettingsScreen.kt +++ b/dolby/src/org/lunaris/dolby/ui/screens/ModernDolbySettingsScreen.kt @@ -17,19 +17,20 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.navigation.NavController import org.lunaris.dolby.R import org.lunaris.dolby.domain.models.DolbyUiState import org.lunaris.dolby.ui.components.* -import org.lunaris.dolby.ui.viewmodel.DolbyViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun ModernDolbySettingsScreen( - viewModel: DolbyViewModel, - onNavigateToEqualizer: () -> Unit + viewModel: org.lunaris.dolby.ui.viewmodel.DolbyViewModel, + navController: NavController ) { val uiState by viewModel.uiState.collectAsState() var showResetDialog by remember { mutableStateOf(false) } + val currentRoute by navController.currentBackStackEntryFlow.collectAsState(null) Scaffold( topBar = { @@ -55,21 +56,20 @@ fun ModernDolbySettingsScreen( ) ) }, - floatingActionButton = { + bottomBar = { if (uiState is DolbyUiState.Success) { - val state = uiState as DolbyUiState.Success - if (state.settings.enabled) { - FloatingActionButton( - onClick = onNavigateToEqualizer, - containerColor = MaterialTheme.colorScheme.primaryContainer, - shape = MaterialTheme.shapes.large - ) { - AnimatedEqualizerIconDynamic( - color = MaterialTheme.colorScheme.onPrimaryContainer, - size = 24.dp - ) + BottomNavigationBar( + currentRoute = currentRoute?.destination?.route ?: Screen.Settings.route, + onNavigate = { route -> + if (currentRoute?.destination?.route != route) { + navController.navigate(route) { + popUpTo(Screen.Settings.route) { saveState = true } + launchSingleTop = true + restoreState = true + } + } } - } + ) } } ) { paddingValues -> @@ -98,7 +98,6 @@ fun ModernDolbySettingsScreen( ModernDolbySettingsContent( state = state, viewModel = viewModel, - onNavigateToEqualizer = onNavigateToEqualizer, modifier = Modifier.padding(paddingValues) ) } @@ -147,8 +146,7 @@ fun ModernDolbySettingsScreen( @Composable private fun ModernDolbySettingsContent( state: DolbyUiState.Success, - viewModel: DolbyViewModel, - onNavigateToEqualizer: () -> Unit, + viewModel: org.lunaris.dolby.ui.viewmodel.DolbyViewModel, modifier: Modifier = Modifier ) { LazyColumn( @@ -178,241 +176,75 @@ private fun ModernDolbySettingsContent( item { AnimatedVisibility( - visible = state.settings.enabled, + visible = state.settings.enabled && state.settings.currentProfile != 0, enter = fadeIn() + expandVertically(), exit = fadeOut() + shrinkVertically() ) { ModernSettingsCard( - title = stringResource(R.string.dolby_category_settings), - icon = Icons.Default.Tune + title = "Intelligent Equalizer", + icon = Icons.Default.GraphicEq ) { - Surface( - onClick = onNavigateToEqualizer, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 12.dp), - shape = MaterialTheme.shapes.medium, - color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = stringResource(R.string.dolby_preset), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text( - text = state.currentPresetName, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.SemiBold - ) - } - Icon( - imageVector = Icons.Default.ChevronRight, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } - - Column { - ModernSettingSwitch( - title = stringResource(R.string.dolby_bass_enhancer), - subtitle = stringResource(R.string.dolby_bass_enhancer_summary), - checked = state.profileSettings.bassLevel > 0, - onCheckedChange = { enabled -> - if (enabled && state.profileSettings.bassLevel == 0) { - viewModel.setBassLevel(50) - } else if (!enabled) { - viewModel.setBassLevel(0) - } - }, - icon = Icons.Default.MusicNote - ) - - AnimatedVisibility(visible = state.profileSettings.bassLevel > 0) { - Column { - Spacer(modifier = Modifier.height(8.dp)) - ModernSettingSelector( - title = stringResource(R.string.dolby_bass_curve), - currentValue = state.profileSettings.bassCurve, - entries = R.array.dolby_bass_curve_entries, - values = R.array.dolby_bass_curve_values, - onValueChange = { viewModel.setBassCurve(it) }, - icon = Icons.Default.Equalizer - ) - Spacer(modifier = Modifier.height(8.dp)) - ModernSettingSlider( - title = stringResource(R.string.dolby_bass_level), - value = state.profileSettings.bassLevel, - onValueChange = { viewModel.setBassLevel(it.toInt()) }, - valueRange = 0f..100f, - steps = 19, - valueLabel = { "$it%" } - ) - } - } - } - - Spacer(modifier = Modifier.height(12.dp)) - Column { - ModernSettingSwitch( - title = stringResource(R.string.dolby_treble_enhancer), - subtitle = stringResource(R.string.dolby_treble_enhancer_summary), - checked = state.profileSettings.trebleLevel > 0, - onCheckedChange = { enabled -> - if (enabled && state.profileSettings.trebleLevel == 0) { - viewModel.setTrebleLevel(30) - } else if (!enabled) { - viewModel.setTrebleLevel(0) - } - }, - icon = Icons.Default.GraphicEq - ) - - AnimatedVisibility(visible = state.profileSettings.trebleLevel > 0) { - Column { - Spacer(modifier = Modifier.height(8.dp)) - ModernSettingSlider( - title = stringResource(R.string.dolby_treble_level), - value = state.profileSettings.trebleLevel, - onValueChange = { viewModel.setTrebleLevel(it.toInt()) }, - valueRange = 0f..100f, - steps = 19, - valueLabel = { "$it%" } - ) - } - } - } - - Spacer(modifier = Modifier.height(8.dp)) - ModernSettingSwitch( - title = stringResource(R.string.dolby_volume_leveler), - subtitle = stringResource(R.string.dolby_volume_leveler_summary), - checked = state.settings.volumeLevelerEnabled, - onCheckedChange = { viewModel.setVolumeLeveler(it) }, - icon = Icons.Default.BarChart + ModernIeqSelector( + currentPreset = state.profileSettings.ieqPreset, + onPresetChange = { viewModel.setIeqPreset(it) } ) } } } - - if (state.settings.currentProfile != 0) { - item { - AnimatedVisibility( - visible = state.settings.enabled, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically() - ) { - ModernSettingsCard( - title = stringResource(R.string.dolby_category_adv_settings), - icon = Icons.Default.Settings - ) { - ModernIeqSelector( - currentPreset = state.profileSettings.ieqPreset, - onPresetChange = { viewModel.setIeqPreset(it) } - ) - - Spacer(modifier = Modifier.height(16.dp)) - HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) - Spacer(modifier = Modifier.height(16.dp)) - - if (state.isOnSpeaker) { - ModernSettingSwitch( - title = stringResource(R.string.dolby_spk_virtualizer), - subtitle = stringResource(R.string.dolby_spk_virtualizer_summary), - checked = state.profileSettings.speakerVirtualizerEnabled, - onCheckedChange = { viewModel.setSpeakerVirtualizer(it) }, - icon = Icons.Default.Speaker - ) - } else { - ModernSettingSwitch( - title = stringResource(R.string.dolby_hp_virtualizer), - subtitle = stringResource(R.string.dolby_hp_virtualizer_summary), - checked = state.profileSettings.headphoneVirtualizerEnabled, - onCheckedChange = { viewModel.setHeadphoneVirtualizer(it) }, - icon = Icons.Default.Headphones - ) - - AnimatedVisibility(visible = state.profileSettings.headphoneVirtualizerEnabled) { - Column { - Spacer(modifier = Modifier.height(16.dp)) - ModernSettingSlider( - title = stringResource(R.string.dolby_hp_virtualizer_dolby_strength), - value = state.profileSettings.stereoWideningAmount, - onValueChange = { viewModel.setStereoWidening(it.toInt()) }, - valueRange = 4f..64f, - steps = 59 - ) - } - } - } - - Spacer(modifier = Modifier.height(16.dp)) - HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) - Spacer(modifier = Modifier.height(16.dp)) - - ModernSettingSwitch( - title = stringResource(R.string.dolby_dialogue_enhancer), - subtitle = stringResource(R.string.dolby_dialogue_enhancer_summary), - checked = state.profileSettings.dialogueEnhancerEnabled, - onCheckedChange = { viewModel.setDialogueEnhancer(it) }, - icon = Icons.Default.RecordVoiceOver - ) - - AnimatedVisibility(visible = state.profileSettings.dialogueEnhancerEnabled) { - Column { - Spacer(modifier = Modifier.height(16.dp)) - ModernSettingSlider( - title = stringResource(R.string.dolby_dialogue_enhancer_dolby_strength), - value = state.profileSettings.dialogueEnhancerAmount, - onValueChange = { viewModel.setDialogueEnhancerAmount(it.toInt()) }, - valueRange = 1f..12f, - steps = 10 - ) - } - } - } - } - } - } - - if (state.settings.currentProfile == 0 && state.settings.enabled) { - item { - Surface( - modifier = Modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.medium, - color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.5f) - ) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Default.Info, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSecondaryContainer, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = stringResource(R.string.dolby_adv_settings_footer), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - } - } - } - } item { Spacer(modifier = Modifier.height(80.dp)) } } } + +@Composable +private fun BottomNavigationBar( + currentRoute: String, + onNavigate: (String) -> Unit +) { + NavigationBar( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + tonalElevation = 3.dp + ) { + NavigationBarItem( + icon = { + Icon( + Icons.Default.Home, + contentDescription = null + ) + }, + label = { Text("Home") }, + selected = currentRoute == Screen.Settings.route, + onClick = { onNavigate(Screen.Settings.route) } + ) + + NavigationBarItem( + icon = { + AnimatedEqualizerIconDynamic( + color = if (currentRoute == Screen.Equalizer.route) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + size = 24.dp + ) + }, + label = { Text("Equalizer") }, + selected = currentRoute == Screen.Equalizer.route, + onClick = { onNavigate(Screen.Equalizer.route) } + ) + + NavigationBarItem( + icon = { + Icon( + Icons.Default.Settings, + contentDescription = null + ) + }, + label = { Text("Advanced") }, + selected = currentRoute == Screen.Advanced.route, + onClick = { onNavigate(Screen.Advanced.route) } + ) + } +} diff --git a/dolby/src/org/lunaris/dolby/ui/screens/ModernEqualizerScreen.kt b/dolby/src/org/lunaris/dolby/ui/screens/ModernEqualizerScreen.kt index 88ea6b0..ef1367e 100644 --- a/dolby/src/org/lunaris/dolby/ui/screens/ModernEqualizerScreen.kt +++ b/dolby/src/org/lunaris/dolby/ui/screens/ModernEqualizerScreen.kt @@ -8,7 +8,6 @@ package org.lunaris.dolby.ui.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* @@ -17,22 +16,25 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.navigation.NavController import org.lunaris.dolby.R import org.lunaris.dolby.domain.models.EqualizerUiState import org.lunaris.dolby.ui.components.ModernConfirmDialog import org.lunaris.dolby.ui.components.InteractiveFrequencyResponseCurve +import org.lunaris.dolby.ui.components.AnimatedEqualizerIconDynamic import org.lunaris.dolby.ui.viewmodel.EqualizerViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun ModernEqualizerScreen( viewModel: EqualizerViewModel, - onNavigateBack: () -> Unit + navController: NavController ) { val uiState by viewModel.uiState.collectAsState() var showSaveDialog by remember { mutableStateOf(false) } var showDeleteDialog by remember { mutableStateOf(false) } var showResetDialog by remember { mutableStateOf(false) } + val currentRoute by navController.currentBackStackEntryFlow.collectAsState(null) Scaffold( topBar = { @@ -44,11 +46,6 @@ fun ModernEqualizerScreen( fontWeight = FontWeight.Bold ) }, - navigationIcon = { - IconButton(onClick = onNavigateBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - }, actions = { IconButton(onClick = { showSaveDialog = true }) { Icon(Icons.Default.Save, contentDescription = "Save") @@ -69,6 +66,20 @@ fun ModernEqualizerScreen( containerColor = MaterialTheme.colorScheme.surface ) ) + }, + bottomBar = { + BottomNavigationBar( + currentRoute = currentRoute?.destination?.route ?: Screen.Equalizer.route, + onNavigate = { route -> + if (currentRoute?.destination?.route != route) { + navController.navigate(route) { + popUpTo(Screen.Settings.route) { saveState = true } + launchSingleTop = true + restoreState = true + } + } + } + ) } ) { paddingValues -> when (val state = uiState) { @@ -164,6 +175,9 @@ private fun ModernEqualizerContent( viewModel: EqualizerViewModel, modifier: Modifier = Modifier ) { + val isFlatPreset = state.currentPreset.name == stringResource(R.string.dolby_preset_default) + val isActive = !isFlatPreset + Column( modifier = modifier .fillMaxSize() @@ -214,6 +228,7 @@ private fun ModernEqualizerContent( onBandGainChange = { index, newGain -> viewModel.setBandGain(index, newGain) }, + isActive = isActive, modifier = Modifier .fillMaxWidth() .weight(1f) @@ -389,3 +404,54 @@ private fun SavePresetDialog( shape = RoundedCornerShape(28.dp) ) } + +@Composable +private fun BottomNavigationBar( + currentRoute: String, + onNavigate: (String) -> Unit +) { + NavigationBar( + containerColor = MaterialTheme.colorScheme.surfaceContainer, + tonalElevation = 3.dp + ) { + NavigationBarItem( + icon = { + Icon( + Icons.Default.Home, + contentDescription = null + ) + }, + label = { Text("Home") }, + selected = currentRoute == Screen.Settings.route, + onClick = { onNavigate(Screen.Settings.route) } + ) + + NavigationBarItem( + icon = { + AnimatedEqualizerIconDynamic( + color = if (currentRoute == Screen.Equalizer.route) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + }, + size = 24.dp + ) + }, + label = { Text("Equalizer") }, + selected = currentRoute == Screen.Equalizer.route, + onClick = { onNavigate(Screen.Equalizer.route) } + ) + + NavigationBarItem( + icon = { + Icon( + Icons.Default.Settings, + contentDescription = null + ) + }, + label = { Text("Advanced") }, + selected = currentRoute == Screen.Advanced.route, + onClick = { onNavigate(Screen.Advanced.route) } + ) + } +} diff --git a/dolby/src/org/lunaris/dolby/ui/screens/Navigation.kt b/dolby/src/org/lunaris/dolby/ui/screens/Navigation.kt index 657fccc..6d76be9 100644 --- a/dolby/src/org/lunaris/dolby/ui/screens/Navigation.kt +++ b/dolby/src/org/lunaris/dolby/ui/screens/Navigation.kt @@ -15,6 +15,7 @@ import org.lunaris.dolby.ui.viewmodel.EqualizerViewModel sealed class Screen(val route: String) { object Settings : Screen("settings") object Equalizer : Screen("equalizer") + object Advanced : Screen("advanced") } @Composable @@ -31,18 +32,21 @@ fun DolbyNavHost( composable(Screen.Settings.route) { ModernDolbySettingsScreen( viewModel = dolbyViewModel, - onNavigateToEqualizer = { - navController.navigate(Screen.Equalizer.route) - } + navController = navController ) } composable(Screen.Equalizer.route) { ModernEqualizerScreen( viewModel = equalizerViewModel, - onNavigateBack = { - navController.popBackStack() - } + navController = navController + ) + } + + composable(Screen.Advanced.route) { + ModernAdvancedSettingsScreen( + viewModel = dolbyViewModel, + navController = navController ) } }