From f8de30ffaee3023c2bbcf309047178c79ac1c661 Mon Sep 17 00:00:00 2001 From: Kyle Scheuing Date: Mon, 16 Dec 2024 01:22:03 -0500 Subject: [PATCH] refactor: replace sound mode types with availableSoundModes It's simpler and more flexible to just supply a list of allowed sound modes rather than naming all possible configurations. --- android/.idea/misc.xml | 1 - .../DeviceSettingsQuickPresetsTest.kt | 20 ++- .../openscq30/DeviceSettingsSoundModeTest.kt | 87 ++++++++-- .../extensions/SoundcoreDeviceState.kt | 2 +- .../extensions/resources/TransparencyMode.kt | 9 + .../openscq30/lib/wrapper/DeviceFeatures.kt | 36 ++-- .../composables/DeviceSettings.kt | 16 +- .../composables/QuickPresetConfiguration.kt | 42 ++--- .../AmbientSoundModeCycleSelection.kt | 41 ++--- .../ui/soundmode/AmbientSoundModeSelection.kt | 14 +- .../soundmode/NoiseCancelingModeSelection.kt | 16 +- .../ui/soundmode/SoundModeSettings.kt | 48 +++--- .../ui/soundmode/TransparencyModeSelection.kt | 11 +- .../SoundModeTypeTwoSettings.kt | 13 +- .../openscq30/ui/utils/LabeledRadioButton.kt | 2 +- .../widgets/general_settings/sound_modes.rs | 39 +++-- .../quick_presets/edit_quick_preset.rs | 59 +++---- lib/src/demo/device/demo_device.rs | 28 ++- lib/src/device_profile.rs | 18 +- lib/src/devices/a3027/device_profile.rs | 24 ++- lib/src/devices/a3028/device_profile.rs | 24 ++- lib/src/devices/a3031/device_profile.rs | 26 ++- lib/src/devices/a3033/device_profile.rs | 2 +- lib/src/devices/a3926/device_profile.rs | 26 ++- lib/src/devices/a3930/device_profile.rs | 15 +- lib/src/devices/a3931/device_profile.rs | 21 ++- lib/src/devices/a3933/device_profile.rs | 29 +++- lib/src/devices/a3936/device_profile.rs | 2 +- lib/src/devices/a3945/device_profile.rs | 2 +- lib/src/devices/a3951/device_profile.rs | 30 +++- lib/src/devices/standard/state.rs | 4 +- .../device/soundcore_device.rs | 5 +- lib_protobuf/protobuf/device_features.proto | 15 +- lib_protobuf/src/conversion.rs | 38 +++-- .../deviceSettings/DeviceSettings.tsx | 10 +- .../deviceSettings/SoundModeSelection.tsx | 31 ++-- .../soundMode/AmbientSoundModeSelection.tsx | 36 ++-- .../soundMode/NoiseCancelingModeSelection.tsx | 38 ++--- .../soundMode/TransparencyModeSelection.tsx | 26 +-- web/src/libTypes/DeviceState.ts | 24 ++- web/tests/components/App.spec.tsx | 8 +- .../NoiseCancelingModeSelection.spec.tsx | 2 +- .../deviceSettings/DeviceSettings.spec.tsx | 8 +- .../components/libTypes/DeviceState.spec.ts | 160 ------------------ web/wasm/src/lib.rs | 13 -- 45 files changed, 539 insertions(+), 582 deletions(-) create mode 100644 android/app/src/main/java/com/oppzippy/openscq30/lib/extensions/resources/TransparencyMode.kt delete mode 100644 web/tests/components/libTypes/DeviceState.spec.ts diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index 74dd639e..b2c751a3 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsQuickPresetsTest.kt b/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsQuickPresetsTest.kt index a527bafd..4f759daf 100644 --- a/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsQuickPresetsTest.kt +++ b/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsQuickPresetsTest.kt @@ -15,14 +15,12 @@ import com.oppzippy.openscq30.features.quickpresets.storage.FallbackQuickPreset import com.oppzippy.openscq30.features.quickpresets.storage.QuickPreset import com.oppzippy.openscq30.features.quickpresets.storage.QuickPresetRepository import com.oppzippy.openscq30.lib.wrapper.AmbientSoundMode +import com.oppzippy.openscq30.lib.wrapper.AvailableSoundModes import com.oppzippy.openscq30.lib.wrapper.DeviceFeatures import com.oppzippy.openscq30.lib.wrapper.FirmwareVersion import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingMode -import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingModeType import com.oppzippy.openscq30.lib.wrapper.PresetEqualizerProfile -import com.oppzippy.openscq30.lib.wrapper.SoundModeProfile import com.oppzippy.openscq30.lib.wrapper.TransparencyMode -import com.oppzippy.openscq30.lib.wrapper.TransparencyModeType import com.oppzippy.openscq30.ui.quickpresets.QuickPresetScreen import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest @@ -57,7 +55,21 @@ class DeviceSettingsQuickPresetsTest { lateinit var customProfileDao: CustomProfileDao private var deviceFeatures = DeviceFeatures( - soundMode = SoundModeProfile(NoiseCancelingModeType.Custom, TransparencyModeType.Custom), + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), + transparencyModes = listOf(TransparencyMode.FullyTransparent, TransparencyMode.VocalMode), + noiseCancelingModes = listOf( + NoiseCancelingMode.Indoor, + NoiseCancelingMode.Outdoor, + NoiseCancelingMode.Transport, + NoiseCancelingMode.Custom, + ), + customNoiseCanceling = true, + ), hasHearId = true, numEqualizerChannels = 2, numEqualizerBands = 8, diff --git a/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsSoundModeTest.kt b/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsSoundModeTest.kt index 5e22b6e4..d44ac835 100644 --- a/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsSoundModeTest.kt +++ b/android/app/src/androidTest/java/com/oppzippy/openscq30/DeviceSettingsSoundModeTest.kt @@ -12,10 +12,10 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.performClick import com.oppzippy.openscq30.lib.wrapper.AmbientSoundMode import com.oppzippy.openscq30.lib.wrapper.AmbientSoundModeCycle +import com.oppzippy.openscq30.lib.wrapper.AvailableSoundModes import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingMode import com.oppzippy.openscq30.lib.wrapper.SoundModes import com.oppzippy.openscq30.lib.wrapper.TransparencyMode -import com.oppzippy.openscq30.ui.soundmode.NoiseCancelingType import com.oppzippy.openscq30.ui.soundmode.SoundModeSettings import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest @@ -119,8 +119,15 @@ class DeviceSettingsSoundModeTest { 0u, ), onAmbientSoundModeChange = onAmbientSoundModeChange, - hasTransparencyModes = false, - noiseCancelingType = NoiseCancelingType.None, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + ), + transparencyModes = emptyList(), + noiseCancelingModes = emptyList(), + customNoiseCanceling = false, + ), ambientSoundModeCycle = null, ) } @@ -144,8 +151,15 @@ class DeviceSettingsSoundModeTest { 0u, ), onTransparencyModeChange = onTransparencyModeChange, - hasTransparencyModes = true, - noiseCancelingType = NoiseCancelingType.None, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + ), + transparencyModes = listOf(TransparencyMode.FullyTransparent, TransparencyMode.VocalMode), + noiseCancelingModes = emptyList(), + customNoiseCanceling = false, + ), ambientSoundModeCycle = null, ) } @@ -169,8 +183,21 @@ class DeviceSettingsSoundModeTest { 0u, ), onNoiseCancelingModeChange = onNoiseCancelingModeChange, - hasTransparencyModes = true, - noiseCancelingType = NoiseCancelingType.Custom, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), + transparencyModes = listOf(TransparencyMode.FullyTransparent, TransparencyMode.VocalMode), + noiseCancelingModes = listOf( + NoiseCancelingMode.Indoor, + NoiseCancelingMode.Outdoor, + NoiseCancelingMode.Transport, + NoiseCancelingMode.Custom, + ), + customNoiseCanceling = true, + ), ambientSoundModeCycle = null, ) } @@ -194,8 +221,21 @@ class DeviceSettingsSoundModeTest { 0u, ), onCustomNoiseCancelingChange = onCustomNoiseCancelingChange, - hasTransparencyModes = false, - noiseCancelingType = NoiseCancelingType.Custom, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), + transparencyModes = emptyList(), + noiseCancelingModes = listOf( + NoiseCancelingMode.Indoor, + NoiseCancelingMode.Outdoor, + NoiseCancelingMode.Transport, + NoiseCancelingMode.Custom, + ), + customNoiseCanceling = true, + ), ambientSoundModeCycle = null, ) } @@ -224,8 +264,16 @@ class DeviceSettingsSoundModeTest { TransparencyMode.VocalMode, 0u, ), - hasTransparencyModes = false, - noiseCancelingType = NoiseCancelingType.Normal, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), + transparencyModes = emptyList(), + noiseCancelingModes = emptyList(), + customNoiseCanceling = false, + ), ambientSoundModeCycle = cycle, onAmbientSoundModeCycleChange = { cycleFlow.value = it }, ) @@ -251,8 +299,21 @@ class DeviceSettingsSoundModeTest { TransparencyMode.VocalMode, 0u, ), - hasTransparencyModes = true, - noiseCancelingType = NoiseCancelingType.Custom, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), + transparencyModes = listOf(TransparencyMode.FullyTransparent, TransparencyMode.VocalMode), + noiseCancelingModes = listOf( + NoiseCancelingMode.Indoor, + NoiseCancelingMode.Outdoor, + NoiseCancelingMode.Transport, + NoiseCancelingMode.Custom, + ), + customNoiseCanceling = true, + ), ambientSoundModeCycle = null, ) } diff --git a/android/app/src/androidTest/java/com/oppzippy/openscq30/extensions/SoundcoreDeviceState.kt b/android/app/src/androidTest/java/com/oppzippy/openscq30/extensions/SoundcoreDeviceState.kt index ea5751df..40810a8b 100644 --- a/android/app/src/androidTest/java/com/oppzippy/openscq30/extensions/SoundcoreDeviceState.kt +++ b/android/app/src/androidTest/java/com/oppzippy/openscq30/extensions/SoundcoreDeviceState.kt @@ -9,7 +9,7 @@ import com.oppzippy.openscq30.lib.wrapper.SingleBattery fun DeviceState.Companion.empty(): DeviceState = DeviceState( deviceFeatures = DeviceFeatures( - soundMode = null, + availableSoundModes = null, hasHearId = false, numEqualizerChannels = 0, numEqualizerBands = 0, diff --git a/android/app/src/main/java/com/oppzippy/openscq30/lib/extensions/resources/TransparencyMode.kt b/android/app/src/main/java/com/oppzippy/openscq30/lib/extensions/resources/TransparencyMode.kt new file mode 100644 index 00000000..54f7d47a --- /dev/null +++ b/android/app/src/main/java/com/oppzippy/openscq30/lib/extensions/resources/TransparencyMode.kt @@ -0,0 +1,9 @@ +package com.oppzippy.openscq30.lib.extensions.resources + +import com.oppzippy.openscq30.R +import com.oppzippy.openscq30.lib.wrapper.TransparencyMode + +fun TransparencyMode.toStringResource(): Int = when (this) { + TransparencyMode.VocalMode -> R.string.vocal_mode + TransparencyMode.FullyTransparent -> R.string.fully_transparent +} diff --git a/android/app/src/main/java/com/oppzippy/openscq30/lib/wrapper/DeviceFeatures.kt b/android/app/src/main/java/com/oppzippy/openscq30/lib/wrapper/DeviceFeatures.kt index 1e764d0e..4e1c5e79 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/lib/wrapper/DeviceFeatures.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/lib/wrapper/DeviceFeatures.kt @@ -1,16 +1,16 @@ package com.oppzippy.openscq30.lib.wrapper +import com.oppzippy.openscq30.lib.protobuf.AvailableSoundModes as ProtobufAvailableSoundModes import com.oppzippy.openscq30.lib.protobuf.DeviceFeatures as ProtobufDeviceFeatures import com.oppzippy.openscq30.lib.protobuf.NoiseCancelingModeType as ProtobufNoiseCancelingModeType -import com.oppzippy.openscq30.lib.protobuf.SoundModeProfile as ProtobufSoundModeProfile import com.oppzippy.openscq30.lib.protobuf.TransparencyModeType as ProtobufTransparencyModeType +import com.oppzippy.openscq30.lib.protobuf.availableSoundModes +import com.oppzippy.openscq30.lib.protobuf.availableSoundModesOrNull import com.oppzippy.openscq30.lib.protobuf.deviceFeatures import com.oppzippy.openscq30.lib.protobuf.dynamicRangeCompressionMinFirmwareVersionOrNull -import com.oppzippy.openscq30.lib.protobuf.soundModeOrNull -import com.oppzippy.openscq30.lib.protobuf.soundModeProfile data class DeviceFeatures( - val soundMode: SoundModeProfile?, + val availableSoundModes: AvailableSoundModes?, val hasHearId: Boolean, val numEqualizerChannels: Int, val numEqualizerBands: Int, @@ -22,7 +22,7 @@ data class DeviceFeatures( val dynamicRangeCompressionMinFirmwareVersion: FirmwareVersion?, ) { fun toProtobuf(): ProtobufDeviceFeatures = deviceFeatures { - this@DeviceFeatures.soundMode?.let { soundMode = it.toProtobuf() } + this@DeviceFeatures.availableSoundModes?.let { availableSoundModes = it.toProtobuf() } hasHearId = this@DeviceFeatures.hasHearId numEqualizerChannels = this@DeviceFeatures.numEqualizerChannels numEqualizerBands = this@DeviceFeatures.numEqualizerBands @@ -38,7 +38,7 @@ data class DeviceFeatures( } fun ProtobufDeviceFeatures.toKotlin(): DeviceFeatures = DeviceFeatures( - soundMode = soundModeOrNull?.toKotlin(), + availableSoundModes = availableSoundModesOrNull?.toKotlin(), hasHearId = hasHearId, numEqualizerChannels = numEqualizerChannels, numEqualizerBands = numEqualizerBands, @@ -50,19 +50,25 @@ fun ProtobufDeviceFeatures.toKotlin(): DeviceFeatures = DeviceFeatures( dynamicRangeCompressionMinFirmwareVersion = dynamicRangeCompressionMinFirmwareVersionOrNull?.toKotlin(), ) -data class SoundModeProfile( - val noiseCancelingModeType: NoiseCancelingModeType, - val transparencyModeType: TransparencyModeType, +data class AvailableSoundModes( + val ambientSoundModes: List, + val transparencyModes: List, + val noiseCancelingModes: List, + val customNoiseCanceling: Boolean, ) { - fun toProtobuf(): ProtobufSoundModeProfile = soundModeProfile { - noiseCancelingModeType = this@SoundModeProfile.noiseCancelingModeType.toProtobuf() - transparencyModeType = this@SoundModeProfile.transparencyModeType.toProtobuf() + fun toProtobuf(): ProtobufAvailableSoundModes = availableSoundModes { + ambientSoundModes.addAll(this@AvailableSoundModes.ambientSoundModes.map { it.toProtobuf() }) + transparencyModes.addAll(this@AvailableSoundModes.transparencyModes.map { it.toProtobuf() }) + noiseCancelingModes.addAll(this@AvailableSoundModes.noiseCancelingModes.map { it.toProtobuf() }) + customNoiseCanceling = this@AvailableSoundModes.customNoiseCanceling } } -fun ProtobufSoundModeProfile.toKotlin(): SoundModeProfile = SoundModeProfile( - noiseCancelingModeType = noiseCancelingModeType.toKotlin(), - transparencyModeType = transparencyModeType.toKotlin(), +fun ProtobufAvailableSoundModes.toKotlin(): AvailableSoundModes = AvailableSoundModes( + ambientSoundModes = ambientSoundModesList.map { it.toKotlin() }, + transparencyModes = transparencyModesList.map { it.toKotlin() }, + noiseCancelingModes = noiseCancelingModesList.map { it.toKotlin() }, + customNoiseCanceling = customNoiseCanceling, ) enum class NoiseCancelingModeType { diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/devicesettings/composables/DeviceSettings.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/devicesettings/composables/DeviceSettings.kt index c8a09894..4981eddc 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/devicesettings/composables/DeviceSettings.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/devicesettings/composables/DeviceSettings.kt @@ -29,10 +29,8 @@ import com.oppzippy.openscq30.lib.wrapper.EqualizerConfiguration import com.oppzippy.openscq30.lib.wrapper.ManualNoiseCanceling import com.oppzippy.openscq30.lib.wrapper.MultiButtonConfiguration import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingMode -import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingModeType import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingModeTypeTwo import com.oppzippy.openscq30.lib.wrapper.TransparencyMode -import com.oppzippy.openscq30.lib.wrapper.TransparencyModeType import com.oppzippy.openscq30.ui.buttonactions.ButtonActionSelection import com.oppzippy.openscq30.ui.deviceinfo.DeviceInfoScreen import com.oppzippy.openscq30.ui.devicesettings.Screen @@ -42,7 +40,6 @@ import com.oppzippy.openscq30.ui.equalizer.EqualizerSettings import com.oppzippy.openscq30.ui.importexport.ImportExportScreen import com.oppzippy.openscq30.ui.importexport.ImportExportViewModel import com.oppzippy.openscq30.ui.quickpresets.QuickPresetScreen -import com.oppzippy.openscq30.ui.soundmode.NoiseCancelingType import com.oppzippy.openscq30.ui.soundmode.SoundModeSettings import com.oppzippy.openscq30.ui.soundmodestypetwo.SoundModeTypeTwoSettings @@ -69,7 +66,7 @@ fun DeviceSettings( ) { val navController = rememberNavController() val listedScreens = ArrayList() - if (uiState.deviceState.deviceFeatures.soundMode != null) { + if (uiState.deviceState.deviceFeatures.availableSoundModes != null) { listedScreens.add(Screen.SoundModes.screenInfo) } if (uiState.deviceState.soundModesTypeTwo != null) { @@ -154,17 +151,12 @@ fun DeviceSettings( } uiState.deviceState.soundModes?.let { soundModes -> composable { - val soundModeProfile = - uiState.deviceState.deviceFeatures.soundMode ?: return@composable + val availableSoundModes = + uiState.deviceState.deviceFeatures.availableSoundModes ?: return@composable SoundModeSettings( soundModes = soundModes, ambientSoundModeCycle = uiState.deviceState.ambientSoundModeCycle, - hasTransparencyModes = soundModeProfile.transparencyModeType == TransparencyModeType.Custom, - noiseCancelingType = when (soundModeProfile.noiseCancelingModeType) { - NoiseCancelingModeType.None -> NoiseCancelingType.None - NoiseCancelingModeType.Basic -> NoiseCancelingType.Normal - NoiseCancelingModeType.Custom -> NoiseCancelingType.Custom - }, + availableSoundModes = availableSoundModes, onAmbientSoundModeChange = onAmbientSoundModeChange, onTransparencyModeChange = onTransparencyModeChange, onNoiseCancelingModeChange = onNoiseCancelingModeChange, diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/quickpresets/composables/QuickPresetConfiguration.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/quickpresets/composables/QuickPresetConfiguration.kt index bd326182..843a847c 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/quickpresets/composables/QuickPresetConfiguration.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/quickpresets/composables/QuickPresetConfiguration.kt @@ -22,10 +22,8 @@ import com.oppzippy.openscq30.lib.extensions.resources.toStringResource import com.oppzippy.openscq30.lib.wrapper.AmbientSoundMode import com.oppzippy.openscq30.lib.wrapper.DeviceFeatures import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingMode -import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingModeType import com.oppzippy.openscq30.lib.wrapper.PresetEqualizerProfile import com.oppzippy.openscq30.lib.wrapper.TransparencyMode -import com.oppzippy.openscq30.lib.wrapper.TransparencyModeType import com.oppzippy.openscq30.ui.equalizer.composables.CustomProfileSelection import com.oppzippy.openscq30.ui.quickpresets.models.QuickPresetEqualizerConfiguration import com.oppzippy.openscq30.ui.soundmode.AmbientSoundModeSelection @@ -67,24 +65,26 @@ fun QuickPresetConfiguration( .fillMaxWidth() .testTag("quickPresetNameInput"), ) - deviceFeatures.soundMode?.let { soundModeProfile -> - CheckboxWithLabel( - text = stringResource(R.string.ambient_sound_mode), - isChecked = ambientSoundMode != null, - onCheckedChange = { - onAmbientSoundModeChange(if (it) AmbientSoundMode.Normal else null) - }, - ) - if (ambientSoundMode != null) { - AmbientSoundModeSelection( - ambientSoundMode = ambientSoundMode, - onAmbientSoundModeChange = onAmbientSoundModeChange, - hasNoiseCanceling = soundModeProfile.noiseCancelingModeType != NoiseCancelingModeType.None, + deviceFeatures.availableSoundModes?.let { availableSoundModes -> + if (availableSoundModes.ambientSoundModes.isNotEmpty()) { + CheckboxWithLabel( + text = stringResource(R.string.ambient_sound_mode), + isChecked = ambientSoundMode != null, + onCheckedChange = { + onAmbientSoundModeChange(if (it) AmbientSoundMode.Normal else null) + }, ) + if (ambientSoundMode != null) { + AmbientSoundModeSelection( + ambientSoundMode = ambientSoundMode, + onAmbientSoundModeChange = onAmbientSoundModeChange, + availableSoundModes = availableSoundModes.ambientSoundModes, + ) + } + HorizontalDivider() } - HorizontalDivider() - if (soundModeProfile.transparencyModeType == TransparencyModeType.Custom) { + if (availableSoundModes.transparencyModes.isNotEmpty()) { CheckboxWithLabel( text = stringResource(R.string.transparency_mode), isChecked = transparencyMode != null, @@ -96,12 +96,13 @@ fun QuickPresetConfiguration( TransparencyModeSelection( transparencyMode = transparencyMode, onTransparencyModeChange = onTransparencyModeChange, + availableSoundModes = availableSoundModes.transparencyModes, ) } HorizontalDivider() } - if (soundModeProfile.noiseCancelingModeType != NoiseCancelingModeType.None) { + if (availableSoundModes.noiseCancelingModes.isNotEmpty()) { CheckboxWithLabel( text = stringResource(R.string.noise_canceling_mode), isChecked = noiseCancelingMode != null, @@ -113,14 +114,13 @@ fun QuickPresetConfiguration( NoiseCancelingModeSelection( noiseCancelingMode = noiseCancelingMode, onNoiseCancelingModeChange = onNoiseCancelingModeChange, - hasCustomNoiseCanceling = - soundModeProfile.noiseCancelingModeType == NoiseCancelingModeType.Custom, + availableSoundModes = availableSoundModes.noiseCancelingModes, ) } HorizontalDivider() } - if (soundModeProfile.noiseCancelingModeType == NoiseCancelingModeType.Custom) { + if (availableSoundModes.customNoiseCanceling) { CheckboxWithLabel( text = stringResource(R.string.custom_noise_canceling), isChecked = customNoiseCanceling != null, diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeCycleSelection.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeCycleSelection.kt index b8437bf0..7eb16ca4 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeCycleSelection.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeCycleSelection.kt @@ -6,7 +6,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.oppzippy.openscq30.R +import com.oppzippy.openscq30.lib.extensions.resources.toStringResource +import com.oppzippy.openscq30.lib.wrapper.AmbientSoundMode import com.oppzippy.openscq30.lib.wrapper.AmbientSoundModeCycle import com.oppzippy.openscq30.ui.theme.OpenSCQ30Theme import com.oppzippy.openscq30.ui.utils.CheckboxWithLabel @@ -15,34 +16,24 @@ import com.oppzippy.openscq30.ui.utils.CheckboxWithLabel fun AmbientSoundModeCycleSelection( cycle: AmbientSoundModeCycle, onAmbientSoundModeCycleChange: (cycle: AmbientSoundModeCycle) -> Unit, - hasNoiseCanceling: Boolean, + availableSoundModes: List, ) { Column(Modifier.testTag("ambientSoundModeCycleSelection")) { - CheckboxWithLabel( - text = stringResource(R.string.normal), - isChecked = cycle.normalMode, - onCheckedChange = { isChecked -> - onAmbientSoundModeCycleChange( - cycle.copy(normalMode = isChecked), - ) - }, - ) - CheckboxWithLabel( - text = stringResource(R.string.transparency), - isChecked = cycle.transparencyMode, - onCheckedChange = { isChecked -> - onAmbientSoundModeCycleChange( - cycle.copy(transparencyMode = isChecked), - ) - }, - ) - if (hasNoiseCanceling) { + availableSoundModes.forEach { mode -> CheckboxWithLabel( - text = stringResource(R.string.noise_canceling), - isChecked = cycle.noiseCancelingMode, + text = stringResource(mode.toStringResource()), + isChecked = when (mode) { + AmbientSoundMode.Normal -> cycle.normalMode + AmbientSoundMode.Transparency -> cycle.transparencyMode + AmbientSoundMode.NoiseCanceling -> cycle.noiseCancelingMode + }, onCheckedChange = { isChecked -> onAmbientSoundModeCycleChange( - cycle.copy(noiseCancelingMode = isChecked), + when (mode) { + AmbientSoundMode.Normal -> cycle.copy(normalMode = isChecked) + AmbientSoundMode.Transparency -> cycle.copy(transparencyMode = isChecked) + AmbientSoundMode.NoiseCanceling -> cycle.copy(noiseCancelingMode = isChecked) + }, ) }, ) @@ -61,7 +52,7 @@ private fun PreviewAmbientSoundModeSelection() { noiseCancelingMode = true, ), onAmbientSoundModeCycleChange = {}, - hasNoiseCanceling = true, + availableSoundModes = AmbientSoundMode.entries, ) } } diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeSelection.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeSelection.kt index 2f00f0bb..1bfff70e 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeSelection.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/AmbientSoundModeSelection.kt @@ -5,7 +5,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.oppzippy.openscq30.R +import com.oppzippy.openscq30.lib.extensions.resources.toStringResource import com.oppzippy.openscq30.lib.wrapper.AmbientSoundMode import com.oppzippy.openscq30.ui.theme.OpenSCQ30Theme import com.oppzippy.openscq30.ui.utils.LabeledRadioButtonGroup @@ -14,15 +14,9 @@ import com.oppzippy.openscq30.ui.utils.LabeledRadioButtonGroup fun AmbientSoundModeSelection( ambientSoundMode: AmbientSoundMode, onAmbientSoundModeChange: (ambientSoundMode: AmbientSoundMode) -> Unit, - hasNoiseCanceling: Boolean, + availableSoundModes: List, ) { - val values = linkedMapOf( - Pair(AmbientSoundMode.Normal, stringResource(R.string.normal)), - Pair(AmbientSoundMode.Transparency, stringResource(R.string.transparency)), - ) - if (hasNoiseCanceling) { - values[AmbientSoundMode.NoiseCanceling] = stringResource(R.string.noise_canceling) - } + val values = availableSoundModes.associateWith { stringResource(it.toStringResource()) } LabeledRadioButtonGroup( modifier = Modifier.testTag("ambientSoundModeSelection"), @@ -39,7 +33,7 @@ private fun PreviewAmbientSoundModeSelection() { AmbientSoundModeSelection( ambientSoundMode = AmbientSoundMode.Normal, onAmbientSoundModeChange = {}, - hasNoiseCanceling = true, + availableSoundModes = AmbientSoundMode.entries, ) } } diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/NoiseCancelingModeSelection.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/NoiseCancelingModeSelection.kt index bc16dede..b892cef0 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/NoiseCancelingModeSelection.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/NoiseCancelingModeSelection.kt @@ -3,7 +3,7 @@ package com.oppzippy.openscq30.ui.soundmode import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.oppzippy.openscq30.R +import com.oppzippy.openscq30.lib.extensions.resources.toStringResource import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingMode import com.oppzippy.openscq30.ui.theme.OpenSCQ30Theme import com.oppzippy.openscq30.ui.utils.LabeledRadioButtonGroup @@ -12,17 +12,9 @@ import com.oppzippy.openscq30.ui.utils.LabeledRadioButtonGroup fun NoiseCancelingModeSelection( noiseCancelingMode: NoiseCancelingMode, onNoiseCancelingModeChange: (noiseCancelingMode: NoiseCancelingMode) -> Unit, - hasCustomNoiseCanceling: Boolean, + availableSoundModes: List, ) { - val values = linkedMapOf( - Pair(NoiseCancelingMode.Transport, stringResource(R.string.transport)), - Pair(NoiseCancelingMode.Indoor, stringResource(R.string.indoor)), - Pair(NoiseCancelingMode.Outdoor, stringResource(R.string.outdoor)), - ) - if (hasCustomNoiseCanceling) { - values[NoiseCancelingMode.Custom] = stringResource(R.string.custom) - } - + val values = availableSoundModes.associateWith { stringResource(it.toStringResource()) } LabeledRadioButtonGroup( selectedValue = noiseCancelingMode, values = values, @@ -37,7 +29,7 @@ private fun PreviewNoiseCancelingModeSelection() { NoiseCancelingModeSelection( noiseCancelingMode = NoiseCancelingMode.Transport, onNoiseCancelingModeChange = {}, - hasCustomNoiseCanceling = true, + availableSoundModes = NoiseCancelingMode.entries, ) } } diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/SoundModeSettings.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/SoundModeSettings.kt index 31033ca5..ded379c7 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/SoundModeSettings.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/SoundModeSettings.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp import com.oppzippy.openscq30.R import com.oppzippy.openscq30.lib.wrapper.AmbientSoundMode import com.oppzippy.openscq30.lib.wrapper.AmbientSoundModeCycle +import com.oppzippy.openscq30.lib.wrapper.AvailableSoundModes import com.oppzippy.openscq30.lib.wrapper.NoiseCancelingMode import com.oppzippy.openscq30.lib.wrapper.SoundModes import com.oppzippy.openscq30.lib.wrapper.TransparencyMode @@ -23,8 +24,7 @@ fun SoundModeSettings( modifier: Modifier = Modifier, soundModes: SoundModes, ambientSoundModeCycle: AmbientSoundModeCycle?, - hasTransparencyModes: Boolean, - noiseCancelingType: NoiseCancelingType, + availableSoundModes: AvailableSoundModes, onAmbientSoundModeChange: (ambientSoundMode: AmbientSoundMode) -> Unit = {}, onTransparencyModeChange: (transparencyMode: TransparencyMode) -> Unit = {}, onNoiseCancelingModeChange: (noiseCancelingMode: NoiseCancelingMode) -> Unit = {}, @@ -32,47 +32,49 @@ fun SoundModeSettings( onAmbientSoundModeCycleChange: (cycle: AmbientSoundModeCycle) -> Unit = {}, ) { Column(modifier = modifier) { - GroupHeader(stringResource(R.string.ambient_sound_mode)) - AmbientSoundModeSelection( - ambientSoundMode = soundModes.ambientSoundMode, - onAmbientSoundModeChange = onAmbientSoundModeChange, - hasNoiseCanceling = noiseCancelingType != NoiseCancelingType.None, - ) - if (ambientSoundModeCycle != null) { + if (availableSoundModes.ambientSoundModes.isNotEmpty()) { + GroupHeader(stringResource(R.string.ambient_sound_mode)) + AmbientSoundModeSelection( + ambientSoundMode = soundModes.ambientSoundMode, + onAmbientSoundModeChange = onAmbientSoundModeChange, + availableSoundModes = availableSoundModes.ambientSoundModes, + ) Spacer(modifier = Modifier.padding(vertical = 8.dp)) + } + if (ambientSoundModeCycle != null) { GroupHeader(stringResource(R.string.ambient_sound_mode_cycle)) AmbientSoundModeCycleSelection( cycle = ambientSoundModeCycle, onAmbientSoundModeCycleChange = onAmbientSoundModeCycleChange, - hasNoiseCanceling = noiseCancelingType != NoiseCancelingType.None, + availableSoundModes = availableSoundModes.ambientSoundModes, ) - } - if (hasTransparencyModes) { Spacer(modifier = Modifier.padding(vertical = 8.dp)) + } + if (availableSoundModes.transparencyModes.isNotEmpty()) { GroupHeader(stringResource(R.string.transparency_mode)) TransparencyModeSelection( transparencyMode = soundModes.transparencyMode, onTransparencyModeChange = onTransparencyModeChange, + availableSoundModes = availableSoundModes.transparencyModes, ) - } - if (noiseCancelingType != NoiseCancelingType.None) { Spacer(modifier = Modifier.padding(vertical = 8.dp)) + } + if (availableSoundModes.noiseCancelingModes.isNotEmpty()) { GroupHeader(stringResource(R.string.noise_canceling_mode)) NoiseCancelingModeSelection( noiseCancelingMode = soundModes.noiseCancelingMode, onNoiseCancelingModeChange = onNoiseCancelingModeChange, - hasCustomNoiseCanceling = noiseCancelingType == NoiseCancelingType.Custom, + availableSoundModes = availableSoundModes.noiseCancelingModes, ) - } - if (noiseCancelingType == NoiseCancelingType.Custom && - soundModes.noiseCancelingMode == NoiseCancelingMode.Custom - ) { Spacer(modifier = Modifier.padding(vertical = 8.dp)) + } + if (availableSoundModes.customNoiseCanceling) { GroupHeader(stringResource(R.string.custom_noise_canceling)) CustomNoiseCancelingSelection( customNoiseCanceling = soundModes.customNoiseCanceling, onCustomNoiseCancelingChange = onCustomNoiseCancelingChange, ) + Spacer(modifier = Modifier.padding(vertical = 8.dp)) } } } @@ -102,8 +104,12 @@ private fun PreviewSoundModeSettings() { transparencyMode = false, noiseCancelingMode = true, ), - hasTransparencyModes = true, - noiseCancelingType = NoiseCancelingType.Custom, + availableSoundModes = AvailableSoundModes( + ambientSoundModes = AmbientSoundMode.entries, + transparencyModes = TransparencyMode.entries, + noiseCancelingModes = NoiseCancelingMode.entries, + customNoiseCanceling = true, + ), ) } } diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/TransparencyModeSelection.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/TransparencyModeSelection.kt index f36c3c3d..0aebb742 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/TransparencyModeSelection.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmode/TransparencyModeSelection.kt @@ -3,7 +3,7 @@ package com.oppzippy.openscq30.ui.soundmode import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.oppzippy.openscq30.R +import com.oppzippy.openscq30.lib.extensions.resources.toStringResource import com.oppzippy.openscq30.lib.wrapper.TransparencyMode import com.oppzippy.openscq30.ui.theme.OpenSCQ30Theme import com.oppzippy.openscq30.ui.utils.LabeledRadioButtonGroup @@ -12,13 +12,13 @@ import com.oppzippy.openscq30.ui.utils.LabeledRadioButtonGroup fun TransparencyModeSelection( transparencyMode: TransparencyMode, onTransparencyModeChange: (transparencyMode: TransparencyMode) -> Unit, + availableSoundModes: List, ) { + val values = availableSoundModes.associateWith { stringResource(it.toStringResource()) } + LabeledRadioButtonGroup( selectedValue = transparencyMode, - values = linkedMapOf( - Pair(TransparencyMode.FullyTransparent, stringResource(R.string.fully_transparent)), - Pair(TransparencyMode.VocalMode, stringResource(R.string.vocal_mode)), - ), + values = values, onValueChange = onTransparencyModeChange, ) } @@ -30,6 +30,7 @@ private fun PreviewAmbientSoundModeSelection() { TransparencyModeSelection( transparencyMode = TransparencyMode.VocalMode, onTransparencyModeChange = {}, + availableSoundModes = TransparencyMode.entries, ) } } diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmodestypetwo/SoundModeTypeTwoSettings.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmodestypetwo/SoundModeTypeTwoSettings.kt index 43f384ac..0b165d97 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmodestypetwo/SoundModeTypeTwoSettings.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/soundmodestypetwo/SoundModeTypeTwoSettings.kt @@ -39,7 +39,11 @@ fun SoundModeTypeTwoSettings( AmbientSoundModeSelection( ambientSoundMode = soundModes.ambientSoundMode, onAmbientSoundModeChange = onAmbientSoundModeChange, - hasNoiseCanceling = true, + availableSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), ) if (ambientSoundModeCycle != null) { Spacer(modifier = Modifier.padding(vertical = 8.dp)) @@ -47,7 +51,11 @@ fun SoundModeTypeTwoSettings( AmbientSoundModeCycleSelection( cycle = ambientSoundModeCycle, onAmbientSoundModeCycleChange = onAmbientSoundModeCycleChange, - hasNoiseCanceling = true, + availableSoundModes = listOf( + AmbientSoundMode.Normal, + AmbientSoundMode.Transparency, + AmbientSoundMode.NoiseCanceling, + ), ) } Spacer(modifier = Modifier.padding(vertical = 8.dp)) @@ -55,6 +63,7 @@ fun SoundModeTypeTwoSettings( TransparencyModeSelection( transparencyMode = soundModes.transparencyMode, onTransparencyModeChange = onTransparencyModeChange, + availableSoundModes = listOf(TransparencyMode.FullyTransparent, TransparencyMode.VocalMode), ) Spacer(modifier = Modifier.padding(vertical = 8.dp)) GroupHeader(stringResource(R.string.noise_canceling_mode)) diff --git a/android/app/src/main/java/com/oppzippy/openscq30/ui/utils/LabeledRadioButton.kt b/android/app/src/main/java/com/oppzippy/openscq30/ui/utils/LabeledRadioButton.kt index 160e3631..0d39d45e 100644 --- a/android/app/src/main/java/com/oppzippy/openscq30/ui/utils/LabeledRadioButton.kt +++ b/android/app/src/main/java/com/oppzippy/openscq30/ui/utils/LabeledRadioButton.kt @@ -19,7 +19,7 @@ import com.oppzippy.openscq30.ui.theme.OpenSCQ30Theme fun LabeledRadioButtonGroup( modifier: Modifier = Modifier, selectedValue: T, - values: LinkedHashMap, + values: Map, onValueChange: (value: T) -> Unit, enabled: Boolean = true, ) { diff --git a/gui/src/ui/widgets/general_settings/sound_modes.rs b/gui/src/ui/widgets/general_settings/sound_modes.rs index 11a9cea8..394bec79 100644 --- a/gui/src/ui/widgets/general_settings/sound_modes.rs +++ b/gui/src/ui/widgets/general_settings/sound_modes.rs @@ -42,9 +42,9 @@ mod imp { }, CompositeTemplate, TemplateChild, }; - use openscq30_lib::{ - device_profile::{NoiseCancelingModeType, TransparencyModeType}, - devices::standard::state::DeviceState, + use openscq30_lib::devices::standard::{ + state::DeviceState, + structures::{AmbientSoundMode, NoiseCancelingMode}, }; use tokio::sync::mpsc::UnboundedSender; @@ -99,34 +99,33 @@ mod imp { let Some(sound_modes) = state.sound_modes else { return; }; - let Some(sound_mode_profile) = state.device_features.sound_mode else { + let Some(available_sound_modes) = state.device_features.available_sound_modes else { return; }; + let has_noise_canceling = available_sound_modes + .ambient_sound_modes + .contains(&AmbientSoundMode::NoiseCanceling); + // Set button visibility self.ambient_sound_mode_selection - .set_has_noise_canceling_mode( - sound_mode_profile.noise_canceling_mode_type != NoiseCancelingModeType::None, - ); + .set_has_noise_canceling_mode(has_noise_canceling); self.ambient_sound_mode_cycle_selection .set_visible(state.device_features.has_ambient_sound_mode_cycle); self.ambient_sound_mode_cycle_selection - .set_has_noise_canceling_mode( - sound_mode_profile.noise_canceling_mode_type != NoiseCancelingModeType::None, - ); - self.transparency_mode_selection.set_visible( - sound_mode_profile.transparency_mode_type == TransparencyModeType::Custom, - ); - self.noise_canceling_mode_selection.set_visible( - sound_mode_profile.noise_canceling_mode_type != NoiseCancelingModeType::None, - ); + .set_has_noise_canceling_mode(has_noise_canceling); + self.transparency_mode_selection + .set_visible(!available_sound_modes.transparency_modes.is_empty()); + self.noise_canceling_mode_selection + .set_visible(!available_sound_modes.noise_canceling_modes.is_empty()); self.noise_canceling_mode_selection .set_has_custom_noise_canceling( - sound_mode_profile.noise_canceling_mode_type == NoiseCancelingModeType::Custom, + available_sound_modes + .noise_canceling_modes + .contains(&NoiseCancelingMode::Custom), ); - self.custom_noise_canceling_selection.set_visible( - sound_mode_profile.noise_canceling_mode_type == NoiseCancelingModeType::Custom, - ); + self.custom_noise_canceling_selection + .set_visible(available_sound_modes.custom_noise_canceling); // Set selected values self.ambient_sound_mode_selection diff --git a/gui/src/ui/widgets/quick_presets/edit_quick_preset.rs b/gui/src/ui/widgets/quick_presets/edit_quick_preset.rs index 9db2284e..5f6e355e 100644 --- a/gui/src/ui/widgets/quick_presets/edit_quick_preset.rs +++ b/gui/src/ui/widgets/quick_presets/edit_quick_preset.rs @@ -47,10 +47,9 @@ mod imp { template_callbacks, ClosureExpression, CompositeTemplate, }; use openscq30_lib::{ - device_profile::{DeviceFeatures, NoiseCancelingModeType, TransparencyModeType}, + device_profile::DeviceFeatures, devices::standard::structures::{ - AmbientSoundMode, CustomNoiseCanceling, NoiseCancelingMode, PresetEqualizerProfile, - TransparencyMode, + CustomNoiseCanceling, PresetEqualizerProfile, TransparencyMode, }, }; use strum::IntoEnumIterator; @@ -211,18 +210,14 @@ mod imp { pub fn set_device_features(&self, features: &DeviceFeatures) { self.freeze_handle_option_changed.set(true); - if let Some(sound_mode_profile) = features.sound_mode { + if let Some(available_sound_modes) = features.available_sound_modes { self.ambient_sound_mode_group.set_visible(true); - let ambient_sound_modes = - [AmbientSoundMode::Normal, AmbientSoundMode::Transparency] - .into_iter() - .chain( - (sound_mode_profile.noise_canceling_mode_type - != NoiseCancelingModeType::None) - .then_some(AmbientSoundMode::NoiseCanceling), - ) - .map(GlibAmbientSoundModeValue) - .map(GlibAmbientSoundMode::new); + let ambient_sound_modes = available_sound_modes + .ambient_sound_modes + .iter() + .cloned() + .map(GlibAmbientSoundModeValue) + .map(GlibAmbientSoundMode::new); Self::update_list_values( &self.ambient_sound_mode, self.ambient_sound_modes_store @@ -232,26 +227,17 @@ mod imp { ambient_sound_modes, ); - self.transparency_mode_group.set_visible( - sound_mode_profile.transparency_mode_type == TransparencyModeType::Custom, - ); - - self.noise_canceling_mode_group.set_visible( - sound_mode_profile.noise_canceling_mode_type != NoiseCancelingModeType::None, - ); - let noise_canceling_modes = [ - NoiseCancelingMode::Transport, - NoiseCancelingMode::Indoor, - NoiseCancelingMode::Outdoor, - ] - .into_iter() - .chain( - (sound_mode_profile.noise_canceling_mode_type - == NoiseCancelingModeType::Custom) - .then_some(NoiseCancelingMode::Custom), - ) - .map(GlibNoiseCancelingModeValue) - .map(GlibNoiseCancelingMode::new); + self.transparency_mode_group + .set_visible(!available_sound_modes.transparency_modes.is_empty()); + + self.noise_canceling_mode_group + .set_visible(!available_sound_modes.noise_canceling_modes.is_empty()); + let noise_canceling_modes = available_sound_modes + .noise_canceling_modes + .iter() + .cloned() + .map(GlibNoiseCancelingModeValue) + .map(GlibNoiseCancelingMode::new); Self::update_list_values( &self.noise_canceling_mode, self.noise_canceling_modes_store @@ -261,9 +247,8 @@ mod imp { noise_canceling_modes, ); - self.custom_noise_canceling_group.set_visible( - sound_mode_profile.noise_canceling_mode_type == NoiseCancelingModeType::Custom, - ); + self.custom_noise_canceling_group + .set_visible(available_sound_modes.custom_noise_canceling); } else { self.ambient_sound_mode_group.set_visible(false); self.transparency_mode_group.set_visible(false); diff --git a/lib/src/demo/device/demo_device.rs b/lib/src/demo/device/demo_device.rs index e8d6d928..7ff315d6 100644 --- a/lib/src/demo/device/demo_device.rs +++ b/lib/src/demo/device/demo_device.rs @@ -6,9 +6,7 @@ use uuid::Uuid; use crate::{ api::{connection::ConnectionStatus, device::Device}, - device_profile::{ - DeviceFeatures, NoiseCancelingModeType, SoundModeProfile, TransparencyModeType, - }, + device_profile::{AvailableSoundModes, DeviceFeatures}, devices::standard::{state::DeviceState, structures::*}, futures::Futures, }; @@ -27,16 +25,30 @@ where { pub async fn new(name: impl Into, mac_address: MacAddr6) -> Self { FuturesType::sleep(Duration::from_millis(500)).await; // it takes some time to connect - // + let (state_sender, _) = watch::channel(DeviceState { device_features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Custom, - transparency_mode_type: TransparencyModeType::Custom, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[ + TransparencyMode::FullyTransparent, + TransparencyMode::VocalMode, + ], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + NoiseCancelingMode::Custom, + ], + custom_noise_canceling: true, }), has_hear_id: true, num_equalizer_channels: 2, - num_equalizer_bands: 10, + num_equalizer_bands: 8, has_dynamic_range_compression: true, dynamic_range_compression_min_firmware_version: None, has_button_configuration: true, diff --git a/lib/src/device_profile.rs b/lib/src/device_profile.rs index 2babff8f..ccd70e43 100644 --- a/lib/src/device_profile.rs +++ b/lib/src/device_profile.rs @@ -16,7 +16,9 @@ use crate::{ a3936::device_profile::A3936_DEVICE_PROFILE, a3945::device_profile::A3945_DEVICE_PROFILE, a3951::device_profile::A3951_DEVICE_PROFILE, - standard::structures::{FirmwareVersion, SerialNumber}, + standard::structures::{ + AmbientSoundMode, FirmwareVersion, NoiseCancelingMode, SerialNumber, TransparencyMode, + }, }, soundcore_device::{ device::device_implementation::DeviceImplementation, device_model::DeviceModel, @@ -31,10 +33,10 @@ pub(crate) struct DeviceProfile { } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct DeviceFeatures { - pub sound_mode: Option, + pub available_sound_modes: Option, pub has_hear_id: bool, pub num_equalizer_channels: usize, pub num_equalizer_bands: usize, @@ -48,11 +50,13 @@ pub struct DeviceFeatures { } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct SoundModeProfile { - pub noise_canceling_mode_type: NoiseCancelingModeType, - pub transparency_mode_type: TransparencyModeType, +pub struct AvailableSoundModes { + pub ambient_sound_modes: &'static [AmbientSoundMode], + pub transparency_modes: &'static [TransparencyMode], + pub noise_canceling_modes: &'static [NoiseCancelingMode], + pub custom_noise_canceling: bool, } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Default)] diff --git a/lib/src/devices/a3027/device_profile.rs b/lib/src/devices/a3027/device_profile.rs index 27ac70c7..cc7165f2 100644 --- a/lib/src/devices/a3027/device_profile.rs +++ b/lib/src/devices/a3027/device_profile.rs @@ -1,9 +1,9 @@ use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, + devices::standard::{ + implementation::StandardImplementation, + structures::{AmbientSoundMode, NoiseCancelingMode}, }, - devices::standard::implementation::StandardImplementation, soundcore_device::device_model::DeviceModel, }; @@ -11,9 +11,19 @@ use super::packets::A3027StateUpdatePacket; pub(crate) const A3027_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Basic, - transparency_mode_type: TransparencyModeType::Basic, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + ], + custom_noise_canceling: false, }), has_hear_id: false, num_equalizer_channels: 1, diff --git a/lib/src/devices/a3028/device_profile.rs b/lib/src/devices/a3028/device_profile.rs index 481d9cde..3682c8a6 100644 --- a/lib/src/devices/a3028/device_profile.rs +++ b/lib/src/devices/a3028/device_profile.rs @@ -1,9 +1,9 @@ use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, + devices::standard::{ + implementation::StandardImplementation, + structures::{AmbientSoundMode, NoiseCancelingMode}, }, - devices::standard::implementation::StandardImplementation, soundcore_device::device_model::DeviceModel, }; @@ -11,9 +11,19 @@ use super::packets::A3028StateUpdatePacket; pub(crate) const A3028_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Basic, - transparency_mode_type: TransparencyModeType::Basic, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + ], + custom_noise_canceling: false, }), has_hear_id: false, num_equalizer_channels: 1, diff --git a/lib/src/devices/a3031/device_profile.rs b/lib/src/devices/a3031/device_profile.rs index 1a8fcf7a..cc0e2559 100644 --- a/lib/src/devices/a3031/device_profile.rs +++ b/lib/src/devices/a3031/device_profile.rs @@ -3,18 +3,16 @@ use std::{collections::HashMap, sync::Arc}; use nom::error::VerboseError; use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, - }, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, devices::standard::{ self, implementation::ButtonConfigurationImplementation, packets::inbound::{state_update_packet::StateUpdatePacket, InboundPacket}, state::DeviceState, structures::{ - AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, - MultiButtonConfiguration, SoundModes, SoundModesTypeTwo, STATE_UPDATE, + AmbientSoundMode, AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, + MultiButtonConfiguration, NoiseCancelingMode, SoundModes, SoundModesTypeTwo, + STATE_UPDATE, }, }, soundcore_device::{ @@ -27,9 +25,19 @@ use super::packets::A3031StateUpdatePacket; pub(crate) const A3031_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Basic, - transparency_mode_type: TransparencyModeType::Basic, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + ], + custom_noise_canceling: false, }), has_hear_id: false, num_equalizer_channels: 1, diff --git a/lib/src/devices/a3033/device_profile.rs b/lib/src/devices/a3033/device_profile.rs index a3b0b5ba..0cc8b6bc 100644 --- a/lib/src/devices/a3033/device_profile.rs +++ b/lib/src/devices/a3033/device_profile.rs @@ -8,7 +8,7 @@ use super::packets::A3033StateUpdatePacket; pub(crate) const A3033_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: None, + available_sound_modes: None, has_hear_id: false, num_equalizer_channels: 1, num_equalizer_bands: 8, diff --git a/lib/src/devices/a3926/device_profile.rs b/lib/src/devices/a3926/device_profile.rs index 605ec046..0ad0dad7 100644 --- a/lib/src/devices/a3926/device_profile.rs +++ b/lib/src/devices/a3926/device_profile.rs @@ -3,18 +3,16 @@ use std::{collections::HashMap, sync::Arc}; use nom::error::VerboseError; use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, - }, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, devices::standard::{ self, implementation::ButtonConfigurationImplementation, packets::inbound::{state_update_packet::StateUpdatePacket, InboundPacket}, state::DeviceState, structures::{ - AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, - MultiButtonConfiguration, SoundModes, SoundModesTypeTwo, STATE_UPDATE, + AmbientSoundMode, AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, + MultiButtonConfiguration, NoiseCancelingMode, SoundModes, SoundModesTypeTwo, + STATE_UPDATE, }, }, soundcore_device::{ @@ -28,9 +26,19 @@ use super::packets::A3926StateUpdatePacket; // TODO does it support custom noise canceling or transparency modes? pub(crate) const A3926_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Basic, - transparency_mode_type: TransparencyModeType::Basic, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + ], + custom_noise_canceling: false, }), has_hear_id: true, num_equalizer_channels: 2, diff --git a/lib/src/devices/a3930/device_profile.rs b/lib/src/devices/a3930/device_profile.rs index e4d40a86..ca640e5f 100644 --- a/lib/src/devices/a3930/device_profile.rs +++ b/lib/src/devices/a3930/device_profile.rs @@ -1,9 +1,6 @@ use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, - }, - devices::standard::implementation::StandardImplementation, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, + devices::standard::{implementation::StandardImplementation, structures::AmbientSoundMode}, soundcore_device::device_model::DeviceModel, }; @@ -11,9 +8,11 @@ use super::packets::A3930StateUpdatePacket; pub(crate) const A3930_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::None, - transparency_mode_type: TransparencyModeType::Basic, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[AmbientSoundMode::Normal, AmbientSoundMode::Transparency], + transparency_modes: &[], + noise_canceling_modes: &[], + custom_noise_canceling: false, }), has_hear_id: true, num_equalizer_channels: 2, diff --git a/lib/src/devices/a3931/device_profile.rs b/lib/src/devices/a3931/device_profile.rs index 1811189e..1a2d2e69 100644 --- a/lib/src/devices/a3931/device_profile.rs +++ b/lib/src/devices/a3931/device_profile.rs @@ -3,18 +3,16 @@ use std::{collections::HashMap, sync::Arc}; use nom::error::VerboseError; use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, - }, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, devices::standard::{ self, implementation::ButtonConfigurationImplementation, packets::inbound::{state_update_packet::StateUpdatePacket, InboundPacket}, state::DeviceState, structures::{ - AmbientSoundModeCycle, Command, EqualizerConfiguration, FirmwareVersion, HearId, - MultiButtonConfiguration, SoundModes, SoundModesTypeTwo, STATE_UPDATE, + AmbientSoundMode, AmbientSoundModeCycle, Command, EqualizerConfiguration, + FirmwareVersion, HearId, MultiButtonConfiguration, SoundModes, SoundModesTypeTwo, + TransparencyMode, STATE_UPDATE, }, }, soundcore_device::{ @@ -27,9 +25,14 @@ use super::packets::A3931StateUpdatePacket; pub(crate) const A3931_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::None, - transparency_mode_type: TransparencyModeType::Custom, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[AmbientSoundMode::Normal, AmbientSoundMode::Transparency], + transparency_modes: &[ + TransparencyMode::FullyTransparent, + TransparencyMode::VocalMode, + ], + noise_canceling_modes: &[], + custom_noise_canceling: false, }), has_hear_id: false, num_equalizer_channels: 2, diff --git a/lib/src/devices/a3933/device_profile.rs b/lib/src/devices/a3933/device_profile.rs index 902be128..0b2d8b8e 100644 --- a/lib/src/devices/a3933/device_profile.rs +++ b/lib/src/devices/a3933/device_profile.rs @@ -3,10 +3,7 @@ use std::{collections::HashMap, sync::Arc}; use nom::error::VerboseError; use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, - }, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, devices::standard::{ self, implementation::ButtonConfigurationImplementation, @@ -14,8 +11,9 @@ use crate::{ quirks::{TwoExtraEqBandSetEqualizerPacket, TwoExtraEqBands}, state::DeviceState, structures::{ - AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, - MultiButtonConfiguration, SoundModes, SoundModesTypeTwo, STATE_UPDATE, + AmbientSoundMode, AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, + MultiButtonConfiguration, NoiseCancelingMode, SoundModes, SoundModesTypeTwo, + TransparencyMode, STATE_UPDATE, }, }, soundcore_device::{ @@ -28,9 +26,22 @@ use super::packets::inbound::A3933StateUpdatePacket; pub(crate) const A3933_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Basic, - transparency_mode_type: TransparencyModeType::Custom, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[ + TransparencyMode::FullyTransparent, + TransparencyMode::VocalMode, + ], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + ], + custom_noise_canceling: false, }), has_hear_id: true, num_equalizer_channels: 2, diff --git a/lib/src/devices/a3936/device_profile.rs b/lib/src/devices/a3936/device_profile.rs index 09554004..5e5140af 100644 --- a/lib/src/devices/a3936/device_profile.rs +++ b/lib/src/devices/a3936/device_profile.rs @@ -29,7 +29,7 @@ use super::{ pub(crate) const A3936_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: None, + available_sound_modes: None, has_hear_id: true, num_equalizer_channels: 2, num_equalizer_bands: 8, diff --git a/lib/src/devices/a3945/device_profile.rs b/lib/src/devices/a3945/device_profile.rs index 0056e5fc..e1ac1007 100644 --- a/lib/src/devices/a3945/device_profile.rs +++ b/lib/src/devices/a3945/device_profile.rs @@ -22,7 +22,7 @@ use super::packets::A3945StateUpdatePacket; pub(crate) const A3945_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: None, + available_sound_modes: None, has_hear_id: false, num_equalizer_channels: 2, num_equalizer_bands: 8, diff --git a/lib/src/devices/a3951/device_profile.rs b/lib/src/devices/a3951/device_profile.rs index d7a4b751..7ff033b1 100644 --- a/lib/src/devices/a3951/device_profile.rs +++ b/lib/src/devices/a3951/device_profile.rs @@ -3,18 +3,16 @@ use std::{collections::HashMap, sync::Arc}; use nom::error::VerboseError; use crate::{ - device_profile::{ - DeviceFeatures, DeviceProfile, NoiseCancelingModeType, SoundModeProfile, - TransparencyModeType, - }, + device_profile::{AvailableSoundModes, DeviceFeatures, DeviceProfile}, devices::standard::{ self, implementation::ButtonConfigurationImplementation, packets::inbound::{state_update_packet::StateUpdatePacket, InboundPacket}, state::DeviceState, structures::{ - AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, - MultiButtonConfiguration, SoundModes, SoundModesTypeTwo, STATE_UPDATE, + AmbientSoundMode, AmbientSoundModeCycle, Command, EqualizerConfiguration, HearId, + MultiButtonConfiguration, NoiseCancelingMode, SoundModes, SoundModesTypeTwo, + TransparencyMode, STATE_UPDATE, }, }, soundcore_device::{ @@ -27,9 +25,23 @@ use super::packets::A3951StateUpdatePacket; pub(crate) const A3951_DEVICE_PROFILE: DeviceProfile = DeviceProfile { features: DeviceFeatures { - sound_mode: Some(SoundModeProfile { - noise_canceling_mode_type: NoiseCancelingModeType::Custom, - transparency_mode_type: TransparencyModeType::Custom, + available_sound_modes: Some(AvailableSoundModes { + ambient_sound_modes: &[ + AmbientSoundMode::Normal, + AmbientSoundMode::Transparency, + AmbientSoundMode::NoiseCanceling, + ], + transparency_modes: &[ + TransparencyMode::FullyTransparent, + TransparencyMode::VocalMode, + ], + noise_canceling_modes: &[ + NoiseCancelingMode::Transport, + NoiseCancelingMode::Indoor, + NoiseCancelingMode::Outdoor, + NoiseCancelingMode::Custom, + ], + custom_noise_canceling: true, }), has_hear_id: true, num_equalizer_channels: 2, diff --git a/lib/src/devices/standard/state.rs b/lib/src/devices/standard/state.rs index 151fc8b2..5fb329b0 100644 --- a/lib/src/devices/standard/state.rs +++ b/lib/src/devices/standard/state.rs @@ -1,5 +1,5 @@ #[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; +use serde::Serialize; use crate::{ device_profile::DeviceFeatures, @@ -17,7 +17,7 @@ use super::structures::{ }; #[derive(Debug, PartialEq, Clone, Default)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct DeviceState { pub device_features: DeviceFeatures, diff --git a/lib/src/soundcore_device/device/soundcore_device.rs b/lib/src/soundcore_device/device/soundcore_device.rs index 422de969..b7d2f75b 100644 --- a/lib/src/soundcore_device/device/soundcore_device.rs +++ b/lib/src/soundcore_device/device/soundcore_device.rs @@ -174,7 +174,10 @@ where async fn set_sound_modes(&self, sound_modes: SoundModes) -> crate::Result<()> { let state_sender = self.state_sender.lock().await; let state = state_sender.borrow().to_owned(); - if state.device_features.sound_mode.is_none() { + // don't actually verify that the sound modes we're setting are supported by the device, + // since that would make it impossible to get the settings out of a state where multiple + // different sound mode options are invalid + if state.device_features.available_sound_modes.is_none() { return Err(crate::Error::FeatureNotSupported { feature_name: "sound modes", }); diff --git a/lib_protobuf/protobuf/device_features.proto b/lib_protobuf/protobuf/device_features.proto index e2fb4221..1d949a30 100644 --- a/lib_protobuf/protobuf/device_features.proto +++ b/lib_protobuf/protobuf/device_features.proto @@ -2,6 +2,7 @@ syntax = "proto2"; package openscq30; import "firmware_version.proto"; +import "sound_modes.proto"; option java_package = "com.oppzippy.openscq30.lib.protobuf"; option java_multiple_files = true; @@ -17,13 +18,8 @@ enum TransparencyModeType { TRANSPARENCY_MODE_CUSTOM = 1; } -message SoundModeProfile { - required NoiseCancelingModeType noise_canceling_mode_type = 1; - required TransparencyModeType transparency_mode_type = 2; -} - message DeviceFeatures { - optional SoundModeProfile sound_mode = 1; + optional AvailableSoundModes available_sound_modes = 1; required bool has_hear_id = 2; required uint32 num_equalizer_channels = 3; required uint32 num_equalizer_bands = 4; @@ -34,3 +30,10 @@ message DeviceFeatures { required bool has_auto_power_off = 9; optional FirmwareVersion dynamic_range_compression_min_firmware_version = 10; } + +message AvailableSoundModes { + repeated AmbientSoundMode ambient_sound_modes = 1 [packed = true]; + repeated TransparencyMode transparency_modes = 2 [packed = true]; + repeated NoiseCancelingMode noise_canceling_modes = 3 [packed = true]; + required bool custom_noise_canceling = 4; +} diff --git a/lib_protobuf/src/conversion.rs b/lib_protobuf/src/conversion.rs index e8704568..236013f5 100644 --- a/lib_protobuf/src/conversion.rs +++ b/lib_protobuf/src/conversion.rs @@ -1,7 +1,8 @@ use openscq30_lib::{ device_profile::{ - DeviceFeatures as LibDeviceFeatures, NoiseCancelingModeType as LibNoiseCancelingModeType, - SoundModeProfile as LibSoundModeProfile, TransparencyModeType as LibTransparencyModeType, + AvailableSoundModes as LibAvailableSoundModes, DeviceFeatures as LibDeviceFeatures, + NoiseCancelingModeType as LibNoiseCancelingModeType, + TransparencyModeType as LibTransparencyModeType, }, devices::standard::{ state::DeviceState as LibDeviceState, @@ -53,7 +54,7 @@ impl From for DeviceState { impl From for DeviceFeatures { fn from(value: LibDeviceFeatures) -> Self { Self { - sound_mode: value.sound_mode.map(Into::into), + available_sound_modes: value.available_sound_modes.map(Into::into), has_hear_id: value.has_hear_id, num_equalizer_channels: value.num_equalizer_channels as u32, num_equalizer_bands: value.num_equalizer_bands as u32, @@ -69,14 +70,31 @@ impl From for DeviceFeatures { } } -impl From for SoundModeProfile { - fn from(value: LibSoundModeProfile) -> Self { +impl From for AvailableSoundModes { + fn from(value: LibAvailableSoundModes) -> Self { Self { - noise_canceling_mode_type: NoiseCancelingModeType::from( - value.noise_canceling_mode_type, - ) - .into(), - transparency_mode_type: TransparencyModeType::from(value.transparency_mode_type).into(), + ambient_sound_modes: value + .ambient_sound_modes + .iter() + .cloned() + .map(AmbientSoundMode::from) + .map(Into::into) + .collect(), + transparency_modes: value + .transparency_modes + .iter() + .cloned() + .map(TransparencyMode::from) + .map(Into::into) + .collect(), + noise_canceling_modes: value + .noise_canceling_modes + .iter() + .cloned() + .map(NoiseCancelingMode::from) + .map(Into::into) + .collect(), + custom_noise_canceling: value.custom_noise_canceling, } } } diff --git a/web/src/components/deviceSettings/DeviceSettings.tsx b/web/src/components/deviceSettings/DeviceSettings.tsx index f263d828..dd180349 100644 --- a/web/src/components/deviceSettings/DeviceSettings.tsx +++ b/web/src/components/deviceSettings/DeviceSettings.tsx @@ -87,20 +87,14 @@ function SoundModeSelectionSection({ ); if ( - displayState.deviceFeatures.soundMode != null && + displayState.deviceFeatures.availableSoundModes != null && displayState.soundModes ) { return ( ); } diff --git a/web/src/components/deviceSettings/SoundModeSelection.tsx b/web/src/components/deviceSettings/SoundModeSelection.tsx index 78b53fc8..8e6c6e88 100644 --- a/web/src/components/deviceSettings/SoundModeSelection.tsx +++ b/web/src/components/deviceSettings/SoundModeSelection.tsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import React, { useCallback } from "react"; import { AmbientSoundModeSelection } from "../soundMode/AmbientSoundModeSelection"; import { NoiseCancelingModeSelection } from "../soundMode/NoiseCancelingModeSelection"; -import { SoundModes } from "../../libTypes/DeviceState"; +import { AvailableSoundModes, SoundModes } from "../../libTypes/DeviceState"; import { TransparencyModeSelection } from "../soundMode/TransparencyModeSelection"; import { CustomNoiseCancelingSelection } from "../soundMode/CustomNoiseCancelingSelection"; import { useTranslation } from "react-i18next"; @@ -10,14 +10,11 @@ import { useTranslation } from "react-i18next"; interface Props { soundModes: SoundModes; setSoundModes: (soundModes: SoundModes) => void; - options: { - hasTransparencyModes: boolean; - noiseCanceling: "none" | "basic" | "custom"; - }; + availableModes: AvailableSoundModes; } export const SoundModeSelection = React.memo(function ({ - options, + availableModes, soundModes, setSoundModes, }: Props) { @@ -66,29 +63,29 @@ export const SoundModeSelection = React.memo(function ({ - {options.hasTransparencyModes && ( + {availableModes.transparencyModes.length != 0 && ( )} - {options.noiseCanceling != "none" && ( + {availableModes.noiseCancelingModes.length != 0 && ( + )} + {availableModes.customNoiseCanceling && ( + )} - {options.noiseCanceling == "custom" && - soundModes.noiseCancelingMode == "custom" && ( - - )} ); }); diff --git a/web/src/components/soundMode/AmbientSoundModeSelection.tsx b/web/src/components/soundMode/AmbientSoundModeSelection.tsx index fe4fca62..e59d3cc1 100644 --- a/web/src/components/soundMode/AmbientSoundModeSelection.tsx +++ b/web/src/components/soundMode/AmbientSoundModeSelection.tsx @@ -2,44 +2,34 @@ import { Box, Typography } from "@mui/material"; import React from "react"; import { useTranslation } from "react-i18next"; import { useFilterNulls } from "../../hooks/useFilterNulls"; -import { SoundModes } from "../../libTypes/DeviceState"; -import { ToggleButtonRow, ToggleButtonValues } from "./ToggleButtonRow"; +import { AvailableSoundModes, SoundModes } from "../../libTypes/DeviceState"; +import { ToggleButtonRow } from "./ToggleButtonRow"; interface Props { value: SoundModes["ambientSoundMode"]; - hasNoiseCancelingMode: boolean; + availableModes: AvailableSoundModes["ambientSoundModes"]; onValueChanged: (newValue: SoundModes["ambientSoundMode"]) => void; } export const AmbientSoundModeSelection = React.memo(function ({ value, - hasNoiseCancelingMode, onValueChanged, + availableModes, }: Props) { const { t } = useTranslation(); // Don't allow deselecting the button const onValueChangedNotNull = useFilterNulls(onValueChanged); - let values: ToggleButtonValues = [ - { - value: "transparency", - label: t("ambientSoundMode.transparency"), - }, - { - value: "normal", - label: t("ambientSoundMode.normal"), - }, - ]; + const translations = { + transparency: t("ambientSoundMode.transparency"), + normal: t("ambientSoundMode.normal"), + noiseCanceling: t("ambientSoundMode.noiseCanceling"), + }; - if (hasNoiseCancelingMode) { - values = [ - { - value: "noiseCanceling", - label: t("ambientSoundMode.noiseCanceling"), - }, - ...values, - ]; - } + const values = availableModes.map((mode) => ({ + value: mode, + label: translations[mode], + })); return ( diff --git a/web/src/components/soundMode/NoiseCancelingModeSelection.tsx b/web/src/components/soundMode/NoiseCancelingModeSelection.tsx index 5951ae54..243a0d84 100644 --- a/web/src/components/soundMode/NoiseCancelingModeSelection.tsx +++ b/web/src/components/soundMode/NoiseCancelingModeSelection.tsx @@ -1,45 +1,35 @@ import { Box, Typography } from "@mui/material"; -import { ToggleButtonRow, ToggleButtonValues } from "./ToggleButtonRow"; +import { ToggleButtonRow } from "./ToggleButtonRow"; import { useTranslation } from "react-i18next"; import React from "react"; -import { SoundModes } from "../../libTypes/DeviceState"; +import { AvailableSoundModes, SoundModes } from "../../libTypes/DeviceState"; import { useFilterNulls } from "../../hooks/useFilterNulls"; interface Props { value: SoundModes["noiseCancelingMode"]; onValueChanged: (newValue: SoundModes["noiseCancelingMode"]) => void; - hasCustomMode: boolean; + availableModes: AvailableSoundModes["noiseCancelingModes"]; } export const NoiseCancelingModeSelection = React.memo(function ({ - hasCustomMode, value, onValueChanged, + availableModes, }: Props) { const { t } = useTranslation(); // Don't allow deselecting the button const onValueChangedNotNull = useFilterNulls(onValueChanged); - const values: ToggleButtonValues = [ - { - value: "transport", - label: t("noiseCancelingMode.transport"), - }, - { - value: "outdoor", - label: t("noiseCancelingMode.outdoor"), - }, - { - value: "indoor", - label: t("noiseCancelingMode.indoor"), - }, - ]; - if (hasCustomMode) { - values.push({ - value: "custom", - label: t("noiseCancelingMode.custom"), - }); - } + const translations = { + transport: t("noiseCancelingMode.transport"), + outdoor: t("noiseCancelingMode.outdoor"), + indoor: t("noiseCancelingMode.indoor"), + custom: t("noiseCancelingMode.custom"), + }; + const values = availableModes.map((mode) => ({ + value: mode, + label: translations[mode], + })); return ( diff --git a/web/src/components/soundMode/TransparencyModeSelection.tsx b/web/src/components/soundMode/TransparencyModeSelection.tsx index 82f03a95..cb501950 100644 --- a/web/src/components/soundMode/TransparencyModeSelection.tsx +++ b/web/src/components/soundMode/TransparencyModeSelection.tsx @@ -2,40 +2,40 @@ import { Box, Typography } from "@mui/material"; import { ToggleButtonRow } from "./ToggleButtonRow"; import { useTranslation } from "react-i18next"; import React from "react"; -import { SoundModes } from "../../libTypes/DeviceState"; +import { AvailableSoundModes, SoundModes } from "../../libTypes/DeviceState"; import { useFilterNulls } from "../../hooks/useFilterNulls"; interface Props { value: SoundModes["transparencyMode"]; onValueChanged: (newValue: SoundModes["transparencyMode"]) => void; + availableModes: AvailableSoundModes["transparencyModes"]; } export const TransparencyModeSelection = React.memo(function ({ value, onValueChanged, + availableModes, }: Props) { const { t } = useTranslation(); // Don't allow deselecting the button const onValueChangedNotNull = useFilterNulls(onValueChanged); + const translations = { + fullyTransparent: t("transparencyMode.fullyTransparent"), + vocalMode: t("transparencyMode.vocalMode"), + }; + const values = availableModes.map((mode) => ({ + value: mode, + label: translations[mode], + })); + return ( {t("soundModes.transparencyMode")} ); diff --git a/web/src/libTypes/DeviceState.ts b/web/src/libTypes/DeviceState.ts index 0b13423d..9c39190d 100644 --- a/web/src/libTypes/DeviceState.ts +++ b/web/src/libTypes/DeviceState.ts @@ -142,20 +142,18 @@ const firmwareVersionSchema = Type.Object({ minor: Type.Number({ minimum: 0 }), }); -const deviceFeaturesSchema = Type.Object({ - soundMode: Nullable( - Type.Object({ - noiseCancelingModeType: Type.Union([ - Type.Literal("none"), - Type.Literal("basic"), - Type.Literal("custom"), - ]), - transparencyModeType: Type.Union([ - Type.Literal("basic"), - Type.Literal("custom"), - ]), - }), +const availableSoundModesSchema = Type.Object({ + ambientSoundModes: Type.Array(soundModesSchema.properties.ambientSoundMode), + transparencyModes: Type.Array(soundModesSchema.properties.transparencyMode), + noiseCancelingModes: Type.Array( + soundModesSchema.properties.noiseCancelingMode, ), + customNoiseCanceling: Type.Boolean(), +}); +export type AvailableSoundModes = Static; + +const deviceFeaturesSchema = Type.Object({ + availableSoundModes: Nullable(availableSoundModesSchema), hasHearId: Type.Boolean(), numEqualizerChannels: Type.Number({ minimum: 0 }), numEqualizerBands: Type.Number({ minimum: 0 }), diff --git a/web/tests/components/App.spec.tsx b/web/tests/components/App.spec.tsx index a4c5340f..e98a7130 100644 --- a/web/tests/components/App.spec.tsx +++ b/web/tests/components/App.spec.tsx @@ -21,9 +21,11 @@ describe("App", () => { const mockDevice = { state: new BehaviorSubject({ deviceFeatures: { - soundMode: { - noiseCancelingModeType: "none", - transparencyModeType: "basic", + availableSoundModes: { + ambientSoundModes: ["normal", "transparency"], + transparencyModes: [], + noiseCancelingModes: [], + customNoiseCanceling: false, }, hasHearId: false, numEqualizerChannels: 0, diff --git a/web/tests/components/NoiseCancelingModeSelection.spec.tsx b/web/tests/components/NoiseCancelingModeSelection.spec.tsx index b24844fd..6d981be2 100644 --- a/web/tests/components/NoiseCancelingModeSelection.spec.tsx +++ b/web/tests/components/NoiseCancelingModeSelection.spec.tsx @@ -15,7 +15,7 @@ describe("NoiseCancelingModeSelection", () => { const renderResult = render( , ); diff --git a/web/tests/components/deviceSettings/DeviceSettings.spec.tsx b/web/tests/components/deviceSettings/DeviceSettings.spec.tsx index 5b2325e7..622940bd 100644 --- a/web/tests/components/deviceSettings/DeviceSettings.spec.tsx +++ b/web/tests/components/deviceSettings/DeviceSettings.spec.tsx @@ -42,9 +42,11 @@ describe("Device Settings", () => { const mockDevice = { state: new BehaviorSubject({ deviceFeatures: { - soundMode: { - noiseCancelingModeType: "custom", - transparencyModeType: "custom", + availableSoundModes: { + ambientSoundModes: ["normal", "transparency", "noiseCanceling"], + transparencyModes: ["fullyTransparent", "vocalMode"], + noiseCancelingModes: ["indoor", "outdoor", "transport", "custom"], + customNoiseCanceling: false, }, hasHearId: true, numEqualizerChannels: 1, diff --git a/web/tests/components/libTypes/DeviceState.spec.ts b/web/tests/components/libTypes/DeviceState.spec.ts deleted file mode 100644 index 76e66595..00000000 --- a/web/tests/components/libTypes/DeviceState.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { DeviceState } from "../../../src/libTypes/DeviceState"; -import { WasmTest } from "../../../wasm/pkg/openscq30_web_wasm"; - -describe("libTypes", () => { - it("should serialize/deserialize an object with as many nulls as possible", () => { - const expected: DeviceState = { - twsStatus: null, - deviceFeatures: { - soundMode: null, - hasHearId: false, - numEqualizerChannels: 0, - numEqualizerBands: 0, - hasDynamicRangeCompression: false, - hasButtonConfiguration: false, - hasWearDetection: false, - hasTouchTone: false, - hasAutoPowerOff: false, - dynamicRangeCompressionMinFirmwareVersion: null, - hasAmbientSoundModeCycle: false, - }, - ageRange: null, - gender: null, - battery: { - type: "singleBattery", - isCharging: false, - level: 0, - }, - buttonConfiguration: null, - hearId: null, - equalizerConfiguration: { - presetProfile: null, - volumeAdjustments: [0, 0, 0, 0, 0, 0, 0, 0], - }, - firmwareVersion: null, - serialNumber: null, - soundModes: null, - soundModesTypeTwo: null, - ambientSoundModeCycle: null, - }; - const actual: unknown = JSON.parse( - WasmTest.deserializeAndReserializeForTests(JSON.stringify(expected)), - ); - expect(actual).toEqual(expected); - }); - it("should serialize/deserialize an object with as many fields filled as possible", () => { - const expected: DeviceState = { - twsStatus: { - isConnected: true, - hostDevice: "left", - }, - deviceFeatures: { - soundMode: { - noiseCancelingModeType: "custom", - transparencyModeType: "custom", - }, - hasHearId: true, - numEqualizerChannels: 2, - numEqualizerBands: 8, - hasDynamicRangeCompression: true, - hasButtonConfiguration: true, - hasWearDetection: true, - hasTouchTone: true, - hasAutoPowerOff: true, - dynamicRangeCompressionMinFirmwareVersion: { - major: 2, - minor: 3, - }, - hasAmbientSoundModeCycle: true, - }, - ageRange: 1, - gender: 2, - battery: { - type: "dualBattery", - left: { - isCharging: true, - level: 1, - }, - right: { - isCharging: true, - level: 2, - }, - }, - buttonConfiguration: { - leftDoubleClick: { - isEnabled: true, - action: "nextSong", - }, - leftLongPress: { - isEnabled: true, - action: "previousSong", - }, - leftSingleClick: { - isEnabled: true, - action: "playPause", - }, - rightDoubleClick: { - isEnabled: true, - action: "voiceAssistant", - }, - rightLongPress: { - isEnabled: true, - action: "volumeDown", - }, - rightSingleClick: { - isEnabled: false, - action: "playPause", - }, - }, - hearId: { - isEnabled: true, - hearIdMusicType: 1, - hearIdType: 2, - time: 3, - volumeAdjustments: { - left: [11, 12, 13, 14, 15, 16, 17, 18], - right: [21, 22, 23, 24, 25, 26, 27, 28], - }, - type: "custom", - customVolumeAdjustments: { - left: [0, 1, 2, 3, 4, 5, 6, 7], - right: [7, 6, 5, 4, 3, 2, 1, 0], - }, - }, - equalizerConfiguration: { - presetProfile: "Acoustic", - volumeAdjustments: [0, 0, 0, 0, 0, 0, 0, 0], - }, - firmwareVersion: { - major: 1, - minor: 2, - }, - serialNumber: "0123456789ABCDEF", - soundModes: { - ambientSoundMode: "noiseCanceling", - noiseCancelingMode: "custom", - transparencyMode: "fullyTransparent", - customNoiseCanceling: 5, - }, - soundModesTypeTwo: { - adaptiveNoiseCanceling: "highNoise", - ambientSoundMode: "noiseCanceling", - manualNoiseCanceling: "moderate", - noiseCancelingAdaptiveSensitivityLevel: 1, - noiseCancelingMode: "adaptive", - transparencyMode: "fullyTransparent", - windNoiseSuppression: true, - }, - ambientSoundModeCycle: { - noiseCancelingMode: true, - transparencyMode: true, - normalMode: true, - }, - }; - const actual: unknown = JSON.parse( - WasmTest.deserializeAndReserializeForTests(JSON.stringify(expected)), - ); - expect(actual).toEqual(expected); - }); -}); diff --git a/web/wasm/src/lib.rs b/web/wasm/src/lib.rs index d794daed..8d3acb57 100644 --- a/web/wasm/src/lib.rs +++ b/web/wasm/src/lib.rs @@ -7,7 +7,6 @@ pub mod web_bluetooth_connection; pub use device::*; pub use equalizer_helper::*; pub use jsvalue_error::*; -use openscq30_lib::devices::standard::state::DeviceState; pub use soundcore_device_utils::*; use wasm_bindgen::prelude::wasm_bindgen; @@ -17,15 +16,3 @@ pub fn initialize() { console_error_panic_hook::set_once(); tracing_wasm::set_as_global_default(); } - -#[wasm_bindgen] -pub struct WasmTest; -#[wasm_bindgen] -impl WasmTest { - #[wasm_bindgen(js_name = "deserializeAndReserializeForTests")] - pub fn deserialize_and_reserialize_for_tests(input: String) -> Result { - let state = - serde_json::from_str::(&input).map_err(|err| format!("{err:?}"))?; - serde_json::to_string(&state).map_err(|err| format!("{err:?}")) - } -}