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
)
}
}