diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d6e51c05..56df41fa32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - #727 Actions to send SMS messages: "Send SMS" and "Compose SMS" - #1819 Explain how to enable the accessibility service restricted setting - #661 Action to execute shell commands. +- #991 Consolidated volume and stream actions. - #1066 Action to mute/unmute microphone. ## Removed diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt index e18e0cd71b..8f45a04801 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionData.kt @@ -98,46 +98,40 @@ sealed class ActionData : Comparable { @Serializable sealed class Volume : ActionData() { - sealed class Stream : Volume() { - abstract val volumeStream: VolumeStream - abstract val showVolumeUi: Boolean + @Serializable + data class Up( + val showVolumeUi: Boolean, + val volumeStream: VolumeStream? = null, + ) : Volume() { + override val id = ActionId.VOLUME_UP override fun compareTo(other: ActionData) = when (other) { - is Stream -> compareValuesBy( + is Up -> compareValuesBy( this, other, - { it.id }, + { it.showVolumeUi }, { it.volumeStream }, ) - else -> super.compareTo(other) } - - @Serializable - data class Increase( - override val showVolumeUi: Boolean, - override val volumeStream: VolumeStream, - ) : Stream() { - override val id = ActionId.VOLUME_INCREASE_STREAM - } - - @Serializable - data class Decrease( - override val showVolumeUi: Boolean, - override val volumeStream: VolumeStream, - ) : Stream() { - override val id = ActionId.VOLUME_DECREASE_STREAM - } } @Serializable - data class Up(val showVolumeUi: Boolean) : Volume() { - override val id = ActionId.VOLUME_UP - } - - @Serializable - data class Down(val showVolumeUi: Boolean) : Volume() { + data class Down( + val showVolumeUi: Boolean, + val volumeStream: VolumeStream? = null, + ) : Volume() { override val id = ActionId.VOLUME_DOWN + + override fun compareTo(other: ActionData) = when (other) { + is Down -> compareValuesBy( + this, + other, + { it.showVolumeUi }, + { it.volumeStream }, + ) + else -> super.compareTo(other) + } } @Serializable diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt index 94adb43b93..1cde9b892b 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionDataEntityMapper.kt @@ -283,12 +283,13 @@ object ActionDataEntityMapper { val showVolumeUi = entity.flags.hasFlag(ActionEntity.ACTION_FLAG_SHOW_VOLUME_UI) + // Convert old stream actions to new volume up/down with stream parameter when (actionId) { ActionId.VOLUME_INCREASE_STREAM -> - ActionData.Volume.Stream.Increase(showVolumeUi, stream) + ActionData.Volume.Up(showVolumeUi, stream) ActionId.VOLUME_DECREASE_STREAM -> - ActionData.Volume.Stream.Decrease(showVolumeUi, stream) + ActionData.Volume.Down(showVolumeUi, stream) else -> throw Exception("don't know how to create system action for $actionId") } @@ -303,9 +304,18 @@ object ActionDataEntityMapper { val showVolumeUi = entity.flags.hasFlag(ActionEntity.ACTION_FLAG_SHOW_VOLUME_UI) + // For VOLUME_UP and VOLUME_DOWN, optionally read the stream type + val volumeStream = if (actionId == ActionId.VOLUME_UP || actionId == ActionId.VOLUME_DOWN) { + entity.extras.getData(ActionEntity.EXTRA_STREAM_TYPE).then { + VOLUME_STREAM_MAP.getKey(it)?.success() ?: null.success() + }.valueOrNull() + } else { + null + } + when (actionId) { - ActionId.VOLUME_UP -> ActionData.Volume.Up(showVolumeUi) - ActionId.VOLUME_DOWN -> ActionData.Volume.Down(showVolumeUi) + ActionId.VOLUME_UP -> ActionData.Volume.Up(showVolumeUi, volumeStream) + ActionId.VOLUME_DOWN -> ActionData.Volume.Down(showVolumeUi, volumeStream) ActionId.VOLUME_TOGGLE_MUTE -> ActionData.Volume.ToggleMute( showVolumeUi, ) @@ -734,7 +744,6 @@ object ActionDataEntityMapper { var flags = 0 val showVolumeUiFlag = when (data) { - is ActionData.Volume.Stream -> data.showVolumeUi is ActionData.Volume.Up -> data.showVolumeUi is ActionData.Volume.Down -> data.showVolumeUi is ActionData.Volume.Mute -> data.showVolumeUi @@ -919,12 +928,31 @@ object ActionDataEntityMapper { is ActionData.Volume -> when (data) { - is ActionData.Volume.Stream -> listOf( - EntityExtra( - ActionEntity.EXTRA_STREAM_TYPE, - VOLUME_STREAM_MAP[data.volumeStream]!!, - ), - ) + is ActionData.Volume.Up -> buildList { + if (data.volumeStream != null) { + VOLUME_STREAM_MAP[data.volumeStream]?.let { streamValue -> + add( + EntityExtra( + ActionEntity.EXTRA_STREAM_TYPE, + streamValue, + ), + ) + } + } + } + + is ActionData.Volume.Down -> buildList { + if (data.volumeStream != null) { + VOLUME_STREAM_MAP[data.volumeStream]?.let { streamValue -> + add( + EntityExtra( + ActionEntity.EXTRA_STREAM_TYPE, + streamValue, + ), + ) + } + } + } else -> emptyList() } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt index ff3c192f52..c63bf66fb4 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionId.kt @@ -45,7 +45,9 @@ enum class ActionId { VOLUME_UP, VOLUME_DOWN, VOLUME_SHOW_DIALOG, + @Deprecated("Use VOLUME_DOWN with volumeStream parameter instead") VOLUME_DECREASE_STREAM, + @Deprecated("Use VOLUME_UP with volumeStream parameter instead") VOLUME_INCREASE_STREAM, CYCLE_RINGER_MODE, CHANGE_RINGER_MODE, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt index 70293169b5..f568eccb9b 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUiHelper.kt @@ -114,34 +114,20 @@ class ActionUiHelper( val string: String when (action) { - is ActionData.Volume.Stream -> { - val streamString = getString( - VolumeStreamStrings.getLabel(action.volumeStream), - ) - + is ActionData.Volume.Down -> { if (action.showVolumeUi) { hasShowVolumeUiFlag = true } - string = when (action) { - is ActionData.Volume.Stream.Decrease -> getString( + string = if (action.volumeStream != null) { + val streamString = getString(VolumeStreamStrings.getLabel(action.volumeStream)) + getString( R.string.action_decrease_stream_formatted, streamString, ) - - is ActionData.Volume.Stream.Increase -> getString( - R.string.action_increase_stream_formatted, - streamString, - ) - } - } - - is ActionData.Volume.Down -> { - if (action.showVolumeUi) { - hasShowVolumeUiFlag = true + } else { + getString(R.string.action_volume_down) } - - string = getString(R.string.action_volume_down) } is ActionData.Volume.Mute -> { @@ -173,7 +159,15 @@ class ActionUiHelper( hasShowVolumeUiFlag = true } - string = getString(R.string.action_volume_up) + string = if (action.volumeStream != null) { + val streamString = getString(VolumeStreamStrings.getLabel(action.volumeStream)) + getString( + R.string.action_increase_stream_formatted, + streamString, + ) + } else { + getString(R.string.action_volume_up) + } } ActionData.Volume.CycleRingerMode -> { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt index 644a9a3f06..c4970c098d 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ActionUtils.kt @@ -916,8 +916,6 @@ fun ActionData.isEditable(): Boolean = when (this) { is ActionData.Volume.Mute, is ActionData.Volume.UnMute, is ActionData.Volume.ToggleMute, - is ActionData.Volume.Stream.Increase, - is ActionData.Volume.Stream.Decrease, is ActionData.Volume.SetRingerMode, is ActionData.DoNotDisturb.Enable, is ActionData.DoNotDisturb.Toggle, diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt index 058f740be8..c8f63864c8 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ChooseActionScreen.kt @@ -55,6 +55,7 @@ fun HandleActionBottomSheets(delegate: CreateActionDelegate) { ChangeFlashlightStrengthActionBottomSheet(delegate) HttpRequestBottomSheet(delegate) SmsActionBottomSheet(delegate) + VolumeActionBottomSheet(delegate) } @Composable diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsUseCase.kt index 1b54e943df..e1ae87958e 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsUseCase.kt @@ -217,7 +217,7 @@ class ConfigActionsUseCaseImpl @Inject constructor( } } - if (data is ActionData.Volume.Down || data is ActionData.Volume.Up || data is ActionData.Volume.Stream) { + if (data is ActionData.Volume.Down || data is ActionData.Volume.Up) { repeat = true } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt index b1f4dfb2d6..d4d7f8b12a 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt @@ -82,6 +82,8 @@ class ConfigActionsViewModel @Inject constructor( combine(config.keyMap, actionOptionsUid, transform = ::buildOptionsState) .stateIn(viewModelScope, SharingStarted.Lazily, null) + private var editedActionUid: String? = null + private val actionErrorSnapshot: StateFlow = displayAction.actionErrorSnapshot.stateIn(viewModelScope, SharingStarted.Lazily, null) @@ -99,7 +101,7 @@ class ConfigActionsViewModel @Inject constructor( viewModelScope.launch { createActionDelegate.actionResult.filterNotNull().collect { action -> - val actionUid = actionOptionsUid.value ?: return@collect + val actionUid = editedActionUid ?: return@collect config.setActionData(actionUid, action) actionOptionsUid.update { null } } @@ -186,6 +188,7 @@ class ConfigActionsViewModel @Inject constructor( viewModelScope.launch { // Clear the bottom sheet so navigating back with predicted-back works actionOptionsUid.update { null } + editedActionUid = actionUid val keyMap = config.keyMap.first().dataOrNull() ?: return@launch diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt index 29b53d07d3..61b38602b0 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/CreateActionDelegate.kt @@ -11,7 +11,6 @@ import io.github.sds100.keymapper.base.actions.tapscreen.PickCoordinateResult import io.github.sds100.keymapper.base.system.intents.ConfigIntentResult import io.github.sds100.keymapper.base.utils.DndModeStrings import io.github.sds100.keymapper.base.utils.RingerModeStrings -import io.github.sds100.keymapper.base.utils.VolumeStreamStrings import io.github.sds100.keymapper.base.utils.navigation.NavDestination import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider import io.github.sds100.keymapper.base.utils.navigation.navigate @@ -54,6 +53,7 @@ class CreateActionDelegate( var httpRequestBottomSheetState: ActionData.HttpRequest? by mutableStateOf(null) var smsActionBottomSheetState: SmsActionBottomSheetState? by mutableStateOf(null) + var volumeActionState: VolumeActionBottomSheetState? by mutableStateOf(null) init { coroutineScope.launch { @@ -160,6 +160,25 @@ class CreateActionDelegate( actionResult.update { action } } + fun onDoneConfigVolumeClick() { + volumeActionState?.also { state -> + val action = when (state.actionId) { + ActionId.VOLUME_UP -> ActionData.Volume.Up( + showVolumeUi = state.showVolumeUi, + volumeStream = state.volumeStream, + ) + ActionId.VOLUME_DOWN -> ActionData.Volume.Down( + showVolumeUi = state.showVolumeUi, + volumeStream = state.volumeStream, + ) + else -> return + } + + volumeActionState = null + actionResult.update { action } + } + } + fun onTestSmsClick() { coroutineScope.launch { (smsActionBottomSheetState as? SmsActionBottomSheetState.SendSms)?.also { state -> @@ -263,8 +282,26 @@ class CreateActionDelegate( return action } - ActionId.VOLUME_UP, - ActionId.VOLUME_DOWN, + ActionId.VOLUME_UP -> { + val oldVolumeUpData = oldData as? ActionData.Volume.Up + volumeActionState = VolumeActionBottomSheetState( + actionId = ActionId.VOLUME_UP, + volumeStream = oldVolumeUpData?.volumeStream, + showVolumeUi = oldVolumeUpData?.showVolumeUi ?: false, + ) + return null + } + + ActionId.VOLUME_DOWN -> { + val oldVolumeDownData = oldData as? ActionData.Volume.Down + volumeActionState = VolumeActionBottomSheetState( + actionId = ActionId.VOLUME_DOWN, + volumeStream = oldVolumeDownData?.volumeStream, + showVolumeUi = oldVolumeDownData?.showVolumeUi ?: false, + ) + return null + } + ActionId.VOLUME_MUTE, ActionId.VOLUME_UNMUTE, ActionId.VOLUME_TOGGLE_MUTE, @@ -272,8 +309,6 @@ class CreateActionDelegate( val showVolumeUiId = 0 val isVolumeUiChecked = when (oldData) { - is ActionData.Volume.Up -> oldData.showVolumeUi - is ActionData.Volume.Down -> oldData.showVolumeUi is ActionData.Volume.Mute -> oldData.showVolumeUi is ActionData.Volume.UnMute -> oldData.showVolumeUi is ActionData.Volume.ToggleMute -> oldData.showVolumeUi @@ -295,8 +330,6 @@ class CreateActionDelegate( val showVolumeUi = chosenFlags.contains(showVolumeUiId) val action = when (actionId) { - ActionId.VOLUME_UP -> ActionData.Volume.Up(showVolumeUi) - ActionId.VOLUME_DOWN -> ActionData.Volume.Down(showVolumeUi) ActionId.VOLUME_MUTE -> ActionData.Volume.Mute(showVolumeUi) ActionId.VOLUME_UNMUTE -> ActionData.Volume.UnMute(showVolumeUi) ActionId.VOLUME_TOGGLE_MUTE -> ActionData.Volume.ToggleMute( @@ -324,45 +357,33 @@ class CreateActionDelegate( ActionId.VOLUME_INCREASE_STREAM, ActionId.VOLUME_DECREASE_STREAM, -> { - val showVolumeUiId = 0 - val isVolumeUiChecked = if (oldData is ActionData.Volume.Stream) { - oldData.showVolumeUi - } else { - false + // These deprecated actions are now converted to Volume.Up/Down with stream parameter + // Determine which action ID to use based on the old action + val newActionId = when (actionId) { + ActionId.VOLUME_INCREASE_STREAM -> ActionId.VOLUME_UP + ActionId.VOLUME_DECREASE_STREAM -> ActionId.VOLUME_DOWN + else -> return null } - val dialogItems = listOf( - MultiChoiceItem( - showVolumeUiId, - getString(R.string.flag_show_volume_dialog), - isVolumeUiChecked, - ), - ) - - val showVolumeUiDialog = DialogModel.MultiChoice(items = dialogItems) - - val chosenFlags = - showDialog("show_volume_ui", showVolumeUiDialog) ?: return null - - val showVolumeUi = chosenFlags.contains(showVolumeUiId) - - val items = VolumeStream.entries - .map { it to getString(VolumeStreamStrings.getLabel(it)) } - - val stream = showDialog("pick_volume_stream", DialogModel.SingleChoice(items)) - ?: return null - - val action = when (actionId) { - ActionId.VOLUME_INCREASE_STREAM -> - ActionData.Volume.Stream.Increase(showVolumeUi = showVolumeUi, stream) - - ActionId.VOLUME_DECREASE_STREAM -> - ActionData.Volume.Stream.Decrease(showVolumeUi = showVolumeUi, stream) + // Get the old stream if this is being edited + val oldStream = when (oldData) { + is ActionData.Volume.Up -> oldData.volumeStream + is ActionData.Volume.Down -> oldData.volumeStream + else -> null + } - else -> throw Exception("don't know how to create action for $actionId") + val oldShowVolumeUi = when (oldData) { + is ActionData.Volume.Up -> oldData.showVolumeUi + is ActionData.Volume.Down -> oldData.showVolumeUi + else -> false } - return action + volumeActionState = VolumeActionBottomSheetState( + actionId = newActionId, + volumeStream = oldStream ?: VolumeStream.MUSIC, // Default to MUSIC for old stream actions + showVolumeUi = oldShowVolumeUi, + ) + return null } ActionId.CHANGE_RINGER_MODE -> { diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt index 72be9153c5..f9acf71488 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/PerformActionsUseCase.kt @@ -326,31 +326,23 @@ class PerformActionsUseCaseImpl @AssistedInject constructor( } is ActionData.Volume.Down -> { - result = audioAdapter.lowerVolume(showVolumeUi = action.showVolumeUi) - } - - is ActionData.Volume.Up -> { - result = audioAdapter.raiseVolume(showVolumeUi = action.showVolumeUi) - } - - is ActionData.Volume.Mute -> { - result = audioAdapter.muteVolume(showVolumeUi = action.showVolumeUi) - } - - is ActionData.Volume.Stream.Decrease -> { result = audioAdapter.lowerVolume( stream = action.volumeStream, showVolumeUi = action.showVolumeUi, ) } - is ActionData.Volume.Stream.Increase -> { + is ActionData.Volume.Up -> { result = audioAdapter.raiseVolume( stream = action.volumeStream, showVolumeUi = action.showVolumeUi, ) } + is ActionData.Volume.Mute -> { + result = audioAdapter.muteVolume(showVolumeUi = action.showVolumeUi) + } + is ActionData.Volume.ToggleMute -> { result = audioAdapter.toggleMuteVolume(showVolumeUi = action.showVolumeUi) } diff --git a/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt new file mode 100644 index 0000000000..c55738d03a --- /dev/null +++ b/base/src/main/java/io/github/sds100/keymapper/base/actions/VolumeActionBottomSheet.kt @@ -0,0 +1,266 @@ +package io.github.sds100.keymapper.base.actions + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.VolumeUp +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.github.sds100.keymapper.base.R +import io.github.sds100.keymapper.base.compose.KeyMapperTheme +import io.github.sds100.keymapper.base.utils.VolumeStreamStrings +import io.github.sds100.keymapper.base.utils.ui.compose.CheckBoxText +import io.github.sds100.keymapper.base.utils.ui.compose.OptionsHeaderRow +import io.github.sds100.keymapper.base.utils.ui.compose.RadioButtonText +import io.github.sds100.keymapper.system.volume.VolumeStream +import kotlinx.coroutines.launch + +data class VolumeActionBottomSheetState( + val actionId: ActionId, + val volumeStream: VolumeStream?, + val showVolumeUi: Boolean, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun VolumeActionBottomSheet(delegate: CreateActionDelegate) { + val scope = rememberCoroutineScope() + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + if (delegate.volumeActionState != null) { + val state = delegate.volumeActionState!! + val title = when (state.actionId) { + ActionId.VOLUME_UP -> stringResource(R.string.action_volume_up) + ActionId.VOLUME_DOWN -> stringResource(R.string.action_volume_down) + else -> "" + } + + VolumeActionBottomSheet( + sheetState = sheetState, + onDismissRequest = { + delegate.volumeActionState = null + }, + state = state, + title = title, + onSelectStream = { + delegate.volumeActionState = delegate.volumeActionState?.copy(volumeStream = it) + }, + onToggleShowVolumeUi = { + delegate.volumeActionState = delegate.volumeActionState?.copy(showVolumeUi = it) + }, + onDoneClick = { + scope.launch { + sheetState.hide() + delegate.onDoneConfigVolumeClick() + } + }, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun VolumeActionBottomSheet( + sheetState: SheetState, + onDismissRequest: () -> Unit, + state: VolumeActionBottomSheetState, + title: String, + onSelectStream: (VolumeStream?) -> Unit = {}, + onToggleShowVolumeUi: (Boolean) -> Unit = {}, + onDoneClick: () -> Unit = {}, +) { + val scrollState = rememberScrollState() + val scope = rememberCoroutineScope() + + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = sheetState, + dragHandle = null, + ) { + Column( + modifier = Modifier.verticalScroll(scrollState), + ) { + Spacer(modifier = Modifier.height(16.dp)) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp), + textAlign = TextAlign.Center, + text = title, + style = MaterialTheme.typography.headlineMedium, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + OptionsHeaderRow( + modifier = Modifier.padding(horizontal = 16.dp), + icon = Icons.AutoMirrored.Outlined.VolumeUp, + text = stringResource(R.string.action_config_volume_stream), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Default stream option (null means use system default) + RadioButtonText( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(R.string.action_config_volume_stream_default), + isSelected = state.volumeStream == null, + onSelected = { onSelectStream(null) }, + ) + + // Individual stream options + VolumeStream.entries.forEach { stream -> + RadioButtonText( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(VolumeStreamStrings.getLabel(stream)), + isSelected = state.volumeStream == stream, + onSelected = { onSelectStream(stream) }, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + OptionsHeaderRow( + modifier = Modifier.padding(horizontal = 16.dp), + icon = Icons.Outlined.Visibility, + text = stringResource(R.string.action_config_volume_options), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + CheckBoxText( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(R.string.flag_show_volume_dialog), + isChecked = state.showVolumeUi, + onCheckedChange = onToggleShowVolumeUi, + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = { + scope.launch { + sheetState.hide() + onDismissRequest() + } + }, + ) { + Text(stringResource(R.string.neg_cancel)) + } + + Spacer(modifier = Modifier.width(16.dp)) + + Button( + modifier = Modifier.weight(1f), + onClick = onDoneClick, + ) { + Text(stringResource(R.string.pos_done)) + } + } + + Spacer(Modifier.height(16.dp)) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewVolumeActionBottomSheet() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + density = LocalDensity.current, + initialValue = SheetValue.Expanded, + ) + + var state by remember { + mutableStateOf( + VolumeActionBottomSheetState( + actionId = ActionId.VOLUME_UP, + volumeStream = VolumeStream.MUSIC, + showVolumeUi = true, + ), + ) + } + + VolumeActionBottomSheet( + sheetState = sheetState, + onDismissRequest = {}, + state = state, + title = stringResource(R.string.action_volume_up), + onSelectStream = { state = state.copy(volumeStream = it) }, + onToggleShowVolumeUi = { state = state.copy(showVolumeUi = it) }, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +private fun PreviewVolumeActionBottomSheetDefaultStream() { + KeyMapperTheme { + val sheetState = SheetState( + skipPartiallyExpanded = true, + density = LocalDensity.current, + initialValue = SheetValue.Expanded, + ) + + var state by remember { + mutableStateOf( + VolumeActionBottomSheetState( + actionId = ActionId.VOLUME_DOWN, + volumeStream = null, + showVolumeUi = false, + ), + ) + } + + VolumeActionBottomSheet( + sheetState = sheetState, + onDismissRequest = {}, + state = state, + title = stringResource(R.string.action_volume_down), + onSelectStream = { state = state.copy(volumeStream = it) }, + onToggleShowVolumeUi = { state = state.copy(showVolumeUi = it) }, + ) + } +} diff --git a/base/src/main/java/io/github/sds100/keymapper/base/sorting/comparators/KeyMapActionsComparator.kt b/base/src/main/java/io/github/sds100/keymapper/base/sorting/comparators/KeyMapActionsComparator.kt index 53ffdbb46f..e47422fda7 100644 --- a/base/src/main/java/io/github/sds100/keymapper/base/sorting/comparators/KeyMapActionsComparator.kt +++ b/base/src/main/java/io/github/sds100/keymapper/base/sorting/comparators/KeyMapActionsComparator.kt @@ -70,7 +70,8 @@ class KeyMapActionsComparator( is ActionData.InputKeyEvent -> Success(action.keyCode.toString()) is ActionData.Sound.SoundFile -> Success(action.soundDescription) is ActionData.Sound.Ringtone -> Success(action.uri) - is ActionData.Volume.Stream -> Success(action.volumeStream.toString()) + is ActionData.Volume.Up -> Success(action.volumeStream?.toString() ?: "") + is ActionData.Volume.Down -> Success(action.volumeStream?.toString() ?: "") is ActionData.Volume.SetRingerMode -> Success(action.ringerMode.toString()) is ActionData.Flashlight -> Success(action.lens.toString()) is ActionData.SwitchKeyboard -> Success(action.savedImeName) diff --git a/base/src/main/res/values/strings.xml b/base/src/main/res/values/strings.xml index c1c7b17059..fe902e12df 100644 --- a/base/src/main/res/values/strings.xml +++ b/base/src/main/res/values/strings.xml @@ -1541,6 +1541,10 @@ This device does not let you change the brightness. Brightness change + Volume stream + Default (system controlled) + Options + Unsupported