mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
fix(settings): Fix OFF visibility toggle not reacting on first press
Serialize AapConnection.send() with a dedicated sendMutex so two concurrent optimistic state updates cannot clobber each other. Collapse the OFF toggle click into a single combined VM call that runs both SetListeningModeCycle and SetAllowOffOption sequentially.
This commit is contained in:
@@ -155,6 +155,7 @@ fun DeviceSettingsScreenHost(
|
||||
onMicrophoneModeChange = { vm.setMicrophoneMode(it) },
|
||||
onListeningModeCycleChange = { vm.setListeningModeCycle(it) },
|
||||
onAllowOffOptionChange = { vm.setAllowOffOption(it) },
|
||||
onOffVisibilityChange = { enabled, mask -> vm.setListeningModeOffVisibility(enabled, mask) },
|
||||
onSleepDetectionChange = { vm.setSleepDetection(it) },
|
||||
onDeviceNameChange = { vm.setDeviceName(it) },
|
||||
onStemActionsClick = { vm.navToStemConfig() },
|
||||
@@ -190,6 +191,7 @@ fun DeviceSettingsScreen(
|
||||
onMicrophoneModeChange: (AapSetting.MicrophoneMode.Mode) -> Unit = {},
|
||||
onListeningModeCycleChange: (Int) -> Unit = {},
|
||||
onAllowOffOptionChange: (Boolean) -> Unit = {},
|
||||
onOffVisibilityChange: (enabled: Boolean, currentCycleMask: Int) -> Unit = { _, _ -> },
|
||||
onSleepDetectionChange: (Boolean) -> Unit = {},
|
||||
onDeviceNameChange: (String) -> Unit = {},
|
||||
onStemActionsClick: () -> Unit = {},
|
||||
@@ -402,6 +404,7 @@ fun DeviceSettingsScreen(
|
||||
cycleMask = if (isPro) cycleMask else null,
|
||||
onCycleMaskChange = onListeningModeCycleChange,
|
||||
onAllowOffChange = onAllowOffOptionChange,
|
||||
onOffVisibilityChange = onOffVisibilityChange,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
@@ -1253,6 +1256,7 @@ private fun NoiseControlCombined(
|
||||
cycleMask: Int?,
|
||||
onCycleMaskChange: (Int) -> Unit,
|
||||
onAllowOffChange: (Boolean) -> Unit = {},
|
||||
onOffVisibilityChange: (enabled: Boolean, currentCycleMask: Int) -> Unit = { _, _ -> },
|
||||
enabled: Boolean,
|
||||
) {
|
||||
val displayMode = pendingMode ?: currentMode
|
||||
@@ -1288,12 +1292,12 @@ private fun NoiseControlCombined(
|
||||
IconButton(
|
||||
onClick = {
|
||||
val isOff = mode == AapSetting.AncMode.Value.OFF
|
||||
if (inCycle == true && canRemoveFromCycle) {
|
||||
if (isOff) {
|
||||
onOffVisibilityChange(inCycle != true, cycleMask ?: 0)
|
||||
} else if (inCycle == true && canRemoveFromCycle) {
|
||||
onCycleMaskChange(cycleMask xor bit)
|
||||
if (isOff) onAllowOffChange(false)
|
||||
} else if (inCycle != true) {
|
||||
onCycleMaskChange((cycleMask ?: 0) or bit)
|
||||
if (isOff) onAllowOffChange(true)
|
||||
}
|
||||
},
|
||||
enabled = enabled && (inCycle != true || canRemoveFromCycle),
|
||||
|
||||
+18
-3
@@ -167,14 +167,16 @@ class DeviceSettingsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendInternal(command: AapCommand) {
|
||||
val address = currentAddress() ?: return
|
||||
try {
|
||||
private suspend fun sendInternal(command: AapCommand): Boolean {
|
||||
val address = currentAddress() ?: return false
|
||||
return try {
|
||||
aapManager.sendCommand(address, command)
|
||||
log(TAG) { "Sent $command to $address" }
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
log(TAG, WARN) { "Failed to send $command: ${e.message}" }
|
||||
events.emit(Event.SendFailed(command, e.message))
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +225,19 @@ class DeviceSettingsViewModel @Inject constructor(
|
||||
|
||||
fun setAllowOffOption(enabled: Boolean) = sendProGated(AapCommand.SetAllowOffOption(enabled))
|
||||
|
||||
fun setListeningModeOffVisibility(enabled: Boolean, currentCycleMask: Int) = launch {
|
||||
if (!upgradeRepo.isPro()) {
|
||||
navTo(Nav.Main.Upgrade)
|
||||
return@launch
|
||||
}
|
||||
// Keep in sync with cycleBits[OFF] in DeviceSettingsScreen.NoiseControlCombined
|
||||
val offBit = 0x01
|
||||
val newMask = if (enabled) currentCycleMask or offBit else currentCycleMask and offBit.inv()
|
||||
if (sendInternal(AapCommand.SetListeningModeCycle(newMask))) {
|
||||
sendInternal(AapCommand.SetAllowOffOption(enabled))
|
||||
}
|
||||
}
|
||||
|
||||
fun setSleepDetection(enabled: Boolean) = sendProGated(AapCommand.SetSleepDetection(enabled))
|
||||
|
||||
fun setDeviceName(name: String) = launch {
|
||||
|
||||
@@ -65,6 +65,7 @@ internal class AapConnection(
|
||||
private var socket: BluetoothSocket? = null
|
||||
private var readerJob: Job? = null
|
||||
private val writeMutex = Mutex()
|
||||
private val sendMutex = Mutex()
|
||||
private val framer = AapFramer()
|
||||
|
||||
private var pendingAncMode: AapSetting.AncMode.Value? = null
|
||||
@@ -147,7 +148,7 @@ internal class AapConnection(
|
||||
_state.value = AapPodState(connectionState = AapPodState.ConnectionState.DISCONNECTED)
|
||||
}
|
||||
|
||||
suspend fun send(command: AapCommand) {
|
||||
suspend fun send(command: AapCommand) = sendMutex.withLock {
|
||||
val currentState = _state.value
|
||||
if (currentState.connectionState != AapPodState.ConnectionState.READY) {
|
||||
throw IllegalStateException("Cannot send command in state ${currentState.connectionState}")
|
||||
@@ -162,7 +163,7 @@ internal class AapConnection(
|
||||
log(TAG) { "No pod in ear, queuing ANC mode: ${command.mode}" }
|
||||
pendingAncMode = command.mode
|
||||
_state.value = currentState.copy(pendingAncMode = command.mode)
|
||||
return
|
||||
return@withLock
|
||||
}
|
||||
pendingAncMode = null
|
||||
// Optimistically update UI — don't wait for device echo
|
||||
|
||||
Reference in New Issue
Block a user