mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
feat(device-settings): Move device details to bottom sheet
Move serial, firmware, build, manufacturer, and per-pod serials from the info card into a ModalBottomSheet triggered by an info icon. Firmware+build and left+right pod serials render as paired rows.
This commit is contained in:
@@ -87,6 +87,7 @@ import eu.darken.capod.common.settings.SettingsSection
|
||||
import eu.darken.capod.common.settings.SettingsSliderItem
|
||||
import eu.darken.capod.common.settings.SettingsSwitchItem
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.AapUnavailableCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.DeviceDetailItem
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.DeviceInfoCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.buildModelLabel
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.NotConnectedCard
|
||||
@@ -276,6 +277,41 @@ fun DeviceSettingsScreen(
|
||||
) {
|
||||
device.firstSeenFormatted(state.now)
|
||||
} else null
|
||||
val info = device.deviceInfo
|
||||
val detailItems = buildList<DeviceDetailItem> {
|
||||
if (info != null) {
|
||||
if (info.manufacturer.isNotBlank()) {
|
||||
add(DeviceDetailItem.Single(stringResource(R.string.device_settings_info_manufacturer_label), info.manufacturer))
|
||||
}
|
||||
if (info.serialNumber.isNotBlank()) {
|
||||
add(DeviceDetailItem.Single(stringResource(R.string.device_settings_info_serial_label), info.serialNumber))
|
||||
}
|
||||
val hasFirmware = info.firmwareVersion.isNotBlank()
|
||||
val hasBuild = !info.buildNumber.isNullOrBlank()
|
||||
if (hasFirmware && hasBuild) {
|
||||
add(DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single(stringResource(R.string.device_settings_info_firmware_label), info.firmwareVersion),
|
||||
end = DeviceDetailItem.Single(stringResource(R.string.device_settings_info_build_label), info.buildNumber!!),
|
||||
))
|
||||
} else if (hasFirmware) {
|
||||
add(DeviceDetailItem.Single(stringResource(R.string.device_settings_info_firmware_label), info.firmwareVersion))
|
||||
} else if (hasBuild) {
|
||||
add(DeviceDetailItem.Single(stringResource(R.string.device_settings_info_build_label), info.buildNumber!!))
|
||||
}
|
||||
val hasLeft = !info.leftEarbudSerial.isNullOrBlank()
|
||||
val hasRight = !info.rightEarbudSerial.isNullOrBlank()
|
||||
if (hasLeft && hasRight) {
|
||||
add(DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single(stringResource(R.string.device_settings_info_left_serial_label), info.leftEarbudSerial!!),
|
||||
end = DeviceDetailItem.Single(stringResource(R.string.device_settings_info_right_serial_label), info.rightEarbudSerial!!),
|
||||
))
|
||||
} else if (hasLeft) {
|
||||
add(DeviceDetailItem.Single(stringResource(R.string.device_settings_info_left_serial_label), info.leftEarbudSerial!!))
|
||||
} else if (hasRight) {
|
||||
add(DeviceDetailItem.Single(stringResource(R.string.device_settings_info_right_serial_label), info.rightEarbudSerial!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
DeviceInfoCard(
|
||||
deviceInfo = device.deviceInfo,
|
||||
modelLabel = buildModelLabel(device),
|
||||
@@ -283,6 +319,7 @@ fun DeviceSettingsScreen(
|
||||
connectionStateLabel = stateDetection?.state?.getLabel(context),
|
||||
lastSeen = device.lastSeenFormatted(state.now),
|
||||
firstSeen = firstSeen,
|
||||
detailItems = detailItems,
|
||||
canRename = device.isAapReady,
|
||||
onRename = onDeviceNameChange,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package eu.darken.capod.main.ui.devicesettings.cards
|
||||
|
||||
sealed interface DeviceDetailItem {
|
||||
data class Single(val label: String, val value: String) : DeviceDetailItem
|
||||
data class Paired(val start: Single, val end: Single) : DeviceDetailItem
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package eu.darken.capod.main.ui.devicesettings.cards
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.compose.Preview2
|
||||
import eu.darken.capod.common.compose.PreviewWrapper
|
||||
|
||||
@Composable
|
||||
internal fun DeviceInfoBottomSheet(
|
||||
items: List<DeviceDetailItem>,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 32.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.device_settings_info_details_label),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
)
|
||||
items.forEach { item ->
|
||||
when (item) {
|
||||
is DeviceDetailItem.Single -> InfoRow(label = item.label, value = item.value)
|
||||
is DeviceDetailItem.Paired -> Row(modifier = Modifier.fillMaxWidth()) {
|
||||
InfoRow(
|
||||
label = item.start.label,
|
||||
value = item.start.value,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
InfoRow(
|
||||
label = item.end.label,
|
||||
value = item.end.value,
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DeviceInfoBottomSheetPreview() = PreviewWrapper {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = "Device Details",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
)
|
||||
InfoRow(label = "Manufacturer", value = "Apple Inc.")
|
||||
InfoRow(label = "Serial Number", value = "W5J7KV0N04")
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
InfoRow(label = "Firmware", value = "7A305", modifier = Modifier.weight(1f))
|
||||
InfoRow(label = "Build", value = "8454624", modifier = Modifier.weight(1f), textAlign = TextAlign.End)
|
||||
}
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
InfoRow(label = "Left Pod Serial", value = "H3KL7HR926JY", modifier = Modifier.weight(1f))
|
||||
InfoRow(label = "Right Pod Serial", value = "H3KL2AYL26K0", modifier = Modifier.weight(1f), textAlign = TextAlign.End)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Edit
|
||||
import androidx.compose.material.icons.twotone.Info
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -46,10 +48,16 @@ internal fun DeviceInfoCard(
|
||||
connectionStateLabel: String?,
|
||||
lastSeen: String?,
|
||||
firstSeen: String?,
|
||||
detailItems: List<DeviceDetailItem> = emptyList(),
|
||||
canRename: Boolean = false,
|
||||
onRename: (String) -> Unit = {},
|
||||
) {
|
||||
var showRenameDialog by remember { mutableStateOf(false) }
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(detailItems) {
|
||||
if (detailItems.isEmpty()) showBottomSheet = false
|
||||
}
|
||||
|
||||
if (showRenameDialog && deviceInfo != null) {
|
||||
RenameDialog(
|
||||
@@ -62,6 +70,13 @@ internal fun DeviceInfoCard(
|
||||
)
|
||||
}
|
||||
|
||||
if (showBottomSheet && detailItems.isNotEmpty()) {
|
||||
DeviceInfoBottomSheet(
|
||||
items = detailItems,
|
||||
onDismiss = { showBottomSheet = false },
|
||||
)
|
||||
}
|
||||
|
||||
ElevatedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -69,70 +84,49 @@ internal fun DeviceInfoCard(
|
||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 1.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
if (modelLabel != null) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_model_label),
|
||||
value = modelLabel,
|
||||
)
|
||||
}
|
||||
if (deviceInfo != null) {
|
||||
if (deviceInfo.name.isNotBlank()) {
|
||||
val nameMismatch = systemBluetoothName != null && systemBluetoothName != deviceInfo.name
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
if (modelLabel != null || detailItems.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
if (modelLabel != null) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_bt_name_label),
|
||||
value = deviceInfo.name,
|
||||
label = stringResource(R.string.device_settings_info_model_label),
|
||||
value = modelLabel,
|
||||
modifier = Modifier.weight(1f),
|
||||
valueFontFamily = if (nameMismatch) FontFamily.Cursive else null,
|
||||
)
|
||||
if (canRename) {
|
||||
IconButton(onClick = { showRenameDialog = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Edit,
|
||||
contentDescription = stringResource(R.string.device_settings_rename_label),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (detailItems.isNotEmpty()) {
|
||||
IconButton(onClick = { showBottomSheet = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Info,
|
||||
contentDescription = stringResource(R.string.device_settings_info_details_action),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deviceInfo.serialNumber.isNotBlank()) {
|
||||
}
|
||||
if (deviceInfo != null && deviceInfo.name.isNotBlank()) {
|
||||
val nameMismatch = systemBluetoothName != null && systemBluetoothName != deviceInfo.name
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_serial_label),
|
||||
value = deviceInfo.serialNumber,
|
||||
label = stringResource(R.string.device_settings_info_bt_name_label),
|
||||
value = deviceInfo.name,
|
||||
modifier = Modifier.weight(1f),
|
||||
valueFontFamily = if (nameMismatch) FontFamily.Cursive else null,
|
||||
)
|
||||
}
|
||||
if (deviceInfo.firmwareVersion.isNotBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_firmware_label),
|
||||
value = deviceInfo.firmwareVersion,
|
||||
)
|
||||
}
|
||||
if (!deviceInfo.buildNumber.isNullOrBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_build_label),
|
||||
value = deviceInfo.buildNumber,
|
||||
)
|
||||
}
|
||||
if (!deviceInfo.leftEarbudSerial.isNullOrBlank() || !deviceInfo.rightEarbudSerial.isNullOrBlank()) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
if (!deviceInfo.leftEarbudSerial.isNullOrBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_left_serial_label),
|
||||
value = deviceInfo.leftEarbudSerial,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
if (!deviceInfo.rightEarbudSerial.isNullOrBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_right_serial_label),
|
||||
value = deviceInfo.rightEarbudSerial,
|
||||
modifier = Modifier.weight(1f),
|
||||
textAlign = if (!deviceInfo.leftEarbudSerial.isNullOrBlank()) TextAlign.End else TextAlign.Start,
|
||||
if (canRename) {
|
||||
IconButton(onClick = { showRenameDialog = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Edit,
|
||||
contentDescription = stringResource(R.string.device_settings_rename_label),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -169,7 +163,7 @@ internal fun DeviceInfoCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoRow(
|
||||
internal fun InfoRow(
|
||||
label: String,
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -215,6 +209,13 @@ private fun DeviceInfoCardFullPreview() = PreviewWrapper {
|
||||
connectionStateLabel = "Connected",
|
||||
lastSeen = "Just now",
|
||||
firstSeen = "5 minutes ago",
|
||||
detailItems = listOf(
|
||||
DeviceDetailItem.Single("Serial Number", "W5J7KV0N04"),
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single("Firmware", "7A305"),
|
||||
end = DeviceDetailItem.Single("Build", "8454624"),
|
||||
),
|
||||
),
|
||||
canRename = true,
|
||||
)
|
||||
}
|
||||
@@ -235,27 +236,14 @@ private fun DeviceInfoCardMismatchPreview() = PreviewWrapper {
|
||||
connectionStateLabel = "Connected",
|
||||
lastSeen = "Just now",
|
||||
firstSeen = "5 minutes ago",
|
||||
canRename = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview2
|
||||
private fun DeviceInfoCardLastSeenOnlyPreview() = PreviewWrapper {
|
||||
DeviceInfoCard(
|
||||
deviceInfo = AapDeviceInfo(
|
||||
name = "AirPods Pro",
|
||||
modelNumber = "A2699",
|
||||
manufacturer = "Apple Inc.",
|
||||
serialNumber = "W5J7KV0N04",
|
||||
firmwareVersion = "7A305",
|
||||
detailItems = listOf(
|
||||
DeviceDetailItem.Single("Serial Number", "W5J7KV0N04"),
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single("Firmware", "7A305"),
|
||||
end = DeviceDetailItem.Single("Build", "8454624"),
|
||||
),
|
||||
),
|
||||
modelLabel = "AirPods Pro 2 (A2699)",
|
||||
systemBluetoothName = "AirPods Pro",
|
||||
connectionStateLabel = "Disconnected",
|
||||
lastSeen = "2 hours ago",
|
||||
firstSeen = null,
|
||||
canRename = false,
|
||||
canRename = true,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ internal class AapConnection(
|
||||
private var lastAncCommandSentAt: Long = 0L
|
||||
private var lastCommandedAncMode: AapSetting.AncMode.Value? = null
|
||||
private var ancResendJob: Job? = null
|
||||
/** True after one automatic resend — prevents further retries for the same user action. */
|
||||
private var ancResendAttempted: Boolean = false
|
||||
private var lastSentCommand: AapCommand? = null
|
||||
private var lastSentAt: Long = 0L
|
||||
|
||||
@@ -140,6 +142,7 @@ internal class AapConnection(
|
||||
ancDebounceJob = null
|
||||
ancResendJob?.cancel()
|
||||
ancResendJob = null
|
||||
ancResendAttempted = false
|
||||
pendingAncMode = null
|
||||
lastCommandedAncMode = null
|
||||
connectionScope = null
|
||||
@@ -158,6 +161,7 @@ internal class AapConnection(
|
||||
lastCommandedAncMode = command.mode
|
||||
ancResendJob?.cancel()
|
||||
ancResendJob = null
|
||||
ancResendAttempted = false
|
||||
val earDetection = currentState.setting<AapSetting.EarDetection>()
|
||||
if (earDetection != null && !earDetection.isEitherPodInEar) {
|
||||
log(TAG) { "No pod in ear, queuing ANC mode: ${command.mode}" }
|
||||
@@ -318,6 +322,7 @@ internal class AapConnection(
|
||||
} finally {
|
||||
ancDebounceJob?.cancel()
|
||||
ancResendJob?.cancel()
|
||||
ancResendAttempted = false
|
||||
pendingAncMode = null
|
||||
lastCommandedAncMode = null
|
||||
cleanupSocket()
|
||||
@@ -409,20 +414,50 @@ internal class AapConnection(
|
||||
ancDebounceJob?.cancel()
|
||||
_state.value = _state.value.withSetting(key, value).copy(lastMessageAt = timeSource.now())
|
||||
log(TAG) { "Setting: ${key.simpleName} = $value [was: $previous]" }
|
||||
applyInferences(value)
|
||||
|
||||
// After our command, firmware may cycle through modes before settling.
|
||||
// Schedule a verification: if settled mode != commanded mode, re-send once.
|
||||
// Capped at one retry per user action — if the device rejects the mode
|
||||
// (e.g. OFF without AllowOffOption), stop instead of looping.
|
||||
lastCommandedAncMode?.let { commanded ->
|
||||
ancResendJob?.cancel()
|
||||
ancResendJob = connectionScope?.launch {
|
||||
delay(1000L)
|
||||
val current = _state.value.setting<AapSetting.AncMode>()?.current
|
||||
if (current != null && current != commanded) {
|
||||
log(TAG) { "ANC mode diverged: commanded=$commanded settled=$current, re-sending" }
|
||||
lastCommandedAncMode = null
|
||||
sendRaw(AapCommand.SetAncMode(commanded))
|
||||
} else {
|
||||
lastCommandedAncMode = null
|
||||
if (ancResendAttempted) {
|
||||
// Already retried once — accept the device's answer as final.
|
||||
val current = (value as AapSetting.AncMode).current
|
||||
if (current != commanded) {
|
||||
log(TAG) { "ANC mode rejected: commanded=$commanded settled=$current, giving up after retry" }
|
||||
// OFF specifically requires AllowOffOption — rejection means it's disabled
|
||||
if (commanded == AapSetting.AncMode.Value.OFF) {
|
||||
val prev = _state.value.setting<AapSetting.AllowOffOption>()
|
||||
if (prev == null || prev.enabled) {
|
||||
_state.value = _state.value.withSetting(
|
||||
AapSetting.AllowOffOption::class,
|
||||
AapSetting.AllowOffOption(enabled = false),
|
||||
)
|
||||
log(TAG) { "Inferred: AllowOffOption = false (OFF mode rejected by device)" }
|
||||
}
|
||||
}
|
||||
}
|
||||
lastCommandedAncMode = null
|
||||
} else {
|
||||
ancResendJob = connectionScope?.launch {
|
||||
delay(1000L)
|
||||
val current = _state.value.setting<AapSetting.AncMode>()?.current
|
||||
val ear = _state.value.setting<AapSetting.EarDetection>()
|
||||
// Abort if pods moved to case/disconnected — firmware is doing its own thing
|
||||
if (ear != null && !ear.isEitherPodInEar) {
|
||||
log(TAG) { "ANC resend aborted: no pod in ear (ear=$ear)" }
|
||||
lastCommandedAncMode = null
|
||||
return@launch
|
||||
}
|
||||
if (current != null && current != commanded) {
|
||||
log(TAG) { "ANC mode diverged: commanded=$commanded settled=$current, re-sending" }
|
||||
ancResendAttempted = true
|
||||
sendRaw(AapCommand.SetAncMode(commanded))
|
||||
} else {
|
||||
lastCommandedAncMode = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,6 +467,7 @@ internal class AapConnection(
|
||||
delay(1500L)
|
||||
_state.value = _state.value.withSetting(key, value).copy(lastMessageAt = timeSource.now())
|
||||
log(TAG) { "Setting (debounced): ${key.simpleName} = $value [was: $previous]" }
|
||||
applyInferences(value)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -449,6 +485,7 @@ internal class AapConnection(
|
||||
}
|
||||
_state.value = newState
|
||||
log(TAG) { "Setting: ${key.simpleName} = $value${if (clearPrimaryPod) " (swap, PrimaryPod cleared)" else ""} [was: $previous]" }
|
||||
applyInferences(value)
|
||||
|
||||
// Flush queued ANC command when a pod goes in ear
|
||||
if (value is AapSetting.EarDetection && value.isEitherPodInEar) {
|
||||
@@ -531,6 +568,29 @@ internal class AapConnection(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer settings that the device never pushes but whose state can be deduced from other signals.
|
||||
* Called after every setting update. Only SETS inferred values — never clears them based on absence.
|
||||
*/
|
||||
private fun applyInferences(trigger: AapSetting) {
|
||||
val inferred = mutableListOf<Pair<KClass<out AapSetting>, AapSetting>>()
|
||||
|
||||
// AncMode=OFF is only possible when AllowOffOption is enabled — the device rejects
|
||||
// SetAncMode(OFF) otherwise. If we see OFF in the burst or after a mode change,
|
||||
// AllowOffOption must be true.
|
||||
if (trigger is AapSetting.AncMode && trigger.current == AapSetting.AncMode.Value.OFF) {
|
||||
val current = _state.value.setting<AapSetting.AllowOffOption>()
|
||||
if (current == null || !current.enabled) {
|
||||
inferred += AapSetting.AllowOffOption::class to AapSetting.AllowOffOption(enabled = true)
|
||||
}
|
||||
}
|
||||
|
||||
for ((key, value) in inferred) {
|
||||
_state.value = _state.value.withSetting(key, value)
|
||||
log(TAG) { "Inferred: ${key.simpleName} = $value (from ${trigger::class.simpleName})" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanupSocket() {
|
||||
try {
|
||||
socket?.close()
|
||||
|
||||
@@ -443,11 +443,14 @@
|
||||
<string name="device_settings_info_name_label">Name</string>
|
||||
<string name="device_settings_info_bt_name_label">Bluetooth Device Label</string>
|
||||
<string name="device_settings_info_model_label">Model</string>
|
||||
<string name="device_settings_info_manufacturer_label">Manufacturer</string>
|
||||
<string name="device_settings_info_serial_label">Serial Number</string>
|
||||
<string name="device_settings_info_firmware_label">Firmware</string>
|
||||
<string name="device_settings_info_build_label">Build</string>
|
||||
<string name="device_settings_info_left_serial_label">Left Serial</string>
|
||||
<string name="device_settings_info_right_serial_label">Right Serial</string>
|
||||
<string name="device_settings_info_left_serial_label">Left Pod Serial</string>
|
||||
<string name="device_settings_info_right_serial_label">Right Pod Serial</string>
|
||||
<string name="device_settings_info_details_label">Device Details</string>
|
||||
<string name="device_settings_info_details_action">Show device details</string>
|
||||
<string name="device_settings_info_status_label">Status</string>
|
||||
<string name="device_settings_info_last_seen_label">Last Seen</string>
|
||||
<string name="device_settings_info_first_seen_label">First Seen</string>
|
||||
|
||||
Reference in New Issue
Block a user