dolby: Redesign dolby interface

Signed-off-by: Ghosuto <clash.raja10@gmail.com>
This commit is contained in:
Ghosuto
2026-01-02 13:33:10 +00:00
committed by pabloescobar-reborn
parent 5c91dcd7c9
commit 9bba67c6bb
6 changed files with 622 additions and 273 deletions

View File

@@ -18,7 +18,7 @@
<string name="dolby_max">Max</string>
<string name="dolby_unknown">Unknown</string>
<string name="dolby_on_with_profile">On (%1$s)</string>
<string name="dolby_category_settings">Settings</string>
<string name="dolby_category_settings">Audio tuning</string>
<string name="dolby_category_adv_settings">Advanced settings</string>
<string name="dolby_adv_settings_footer">Choose a different profile to show advanced settings.</string>
<string name="dolby_bass_enhancer">Bass enhancer</string>

View File

@@ -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<BandGain>,
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<Int?>(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)

View File

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

View File

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

View File

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

View File

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