mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 02:36:12 -04:00
feat(device-settings): Surface hardware, pending firmware, and bonded dates
This commit is contained in:
@@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -42,13 +43,14 @@ import eu.darken.capod.common.settings.SettingsInfoBox
|
||||
import eu.darken.capod.common.settings.SettingsSection
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.AapUnavailableCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.ControlsCard
|
||||
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.NoiseControlCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.NotConnectedCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.ReactionsCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.SoundCard
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.buildDeviceInfoDetailItems
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.buildModelLabel
|
||||
import eu.darken.capod.main.ui.devicesettings.cards.rememberDeviceInfoDetailLabels
|
||||
import eu.darken.capod.main.ui.devicesettings.components.ConnectedDevicesList
|
||||
import eu.darken.capod.main.ui.devicesettings.components.EqBarsChart
|
||||
import eu.darken.capod.main.ui.devicesettings.dialogs.SystemRenameUnavailableDialog
|
||||
@@ -61,6 +63,10 @@ import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.HasStateDetection
|
||||
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
|
||||
@Composable
|
||||
fun DeviceSettingsScreenHost(
|
||||
@@ -239,86 +245,18 @@ 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.marketingVersion.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.marketingVersion!!
|
||||
),
|
||||
)
|
||||
)
|
||||
} 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.marketingVersion!!
|
||||
)
|
||||
)
|
||||
}
|
||||
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!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val locale = LocalConfiguration.current.locales[0]
|
||||
val zoneId = ZoneId.systemDefault()
|
||||
val dateFormatter = remember(locale, zoneId) {
|
||||
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
|
||||
.withLocale(locale)
|
||||
.withZone(zoneId)
|
||||
}
|
||||
val detailItems = buildDeviceInfoDetailItems(
|
||||
info = info,
|
||||
labels = rememberDeviceInfoDetailLabels(),
|
||||
formatDate = { instant -> dateFormatter.format(instant) },
|
||||
)
|
||||
DeviceInfoCard(
|
||||
deviceInfo = device.deviceInfo,
|
||||
modelLabel = buildModelLabel(device),
|
||||
@@ -506,6 +444,12 @@ internal fun previewFullState(isPro: Boolean) = DeviceSettingsViewModel.State(
|
||||
manufacturer = "Apple Inc.",
|
||||
serialNumber = "W5J7KV0N04",
|
||||
firmwareVersion = "7A305",
|
||||
hardwareVersion = "1.0.0",
|
||||
leftEarbudSerial = "H3KL7HR926JY",
|
||||
rightEarbudSerial = "H3KL2AYL26K0",
|
||||
marketingVersion = "8454624",
|
||||
leftEarbudFirstPaired = Instant.ofEpochSecond(1697480211L),
|
||||
rightEarbudFirstPaired = Instant.ofEpochSecond(1697480211L),
|
||||
),
|
||||
settings = mapOf(
|
||||
AapSetting.AncMode::class to AapSetting.AncMode(
|
||||
|
||||
+103
-45
@@ -18,6 +18,11 @@ 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
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapDeviceInfo
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
internal fun DeviceInfoBottomSheet(
|
||||
@@ -28,57 +33,110 @@ internal fun DeviceInfoBottomSheet(
|
||||
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,
|
||||
)
|
||||
}
|
||||
DeviceInfoDetailsContent(items)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeviceInfoDetailsContent(items: List<DeviceDetailItem>) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val previewDateFormatter: DateTimeFormatter =
|
||||
DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.US).withZone(ZoneOffset.UTC)
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
private fun DeviceInfoBottomSheetPreviewSameDayPairing() = PreviewWrapper {
|
||||
val pairedAt = Instant.parse("2023-10-16T12:00:00Z")
|
||||
val info = AapDeviceInfo(
|
||||
name = "AirPods Pro",
|
||||
modelNumber = "A2699",
|
||||
manufacturer = "Apple Inc.",
|
||||
serialNumber = "W5J7KV0N04",
|
||||
firmwareVersion = "81.2675000075000000.6814",
|
||||
hardwareVersion = "1.0.0",
|
||||
leftEarbudSerial = "H3KL7HR926JY",
|
||||
rightEarbudSerial = "H3KL2AYL26K0",
|
||||
marketingVersion = "8454768",
|
||||
leftEarbudFirstPaired = pairedAt,
|
||||
rightEarbudFirstPaired = pairedAt,
|
||||
)
|
||||
DeviceInfoDetailsContent(
|
||||
buildDeviceInfoDetailItems(info, rememberDeviceInfoDetailLabels()) {
|
||||
previewDateFormatter.format(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DeviceInfoBottomSheetPreviewMismatchedPairing() = PreviewWrapper {
|
||||
val info = AapDeviceInfo(
|
||||
name = "AirPods Pro",
|
||||
modelNumber = "A2699",
|
||||
manufacturer = "Apple Inc.",
|
||||
serialNumber = "W5J7KV0N04",
|
||||
firmwareVersion = "81.2675000075000000.6814",
|
||||
firmwareVersionPending = "82.1000000075000000.7000",
|
||||
hardwareVersion = "1.0.0",
|
||||
leftEarbudSerial = "H3KL7HR926JY",
|
||||
rightEarbudSerial = "H3KL2AYL26K0",
|
||||
marketingVersion = "8454768",
|
||||
leftEarbudFirstPaired = Instant.parse("2024-02-14T12:00:00Z"),
|
||||
rightEarbudFirstPaired = Instant.parse("2023-10-16T12:00:00Z"),
|
||||
)
|
||||
DeviceInfoDetailsContent(
|
||||
buildDeviceInfoDetailItems(info, rememberDeviceInfoDetailLabels()) {
|
||||
previewDateFormatter.format(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DeviceInfoBottomSheetPreviewOneSidedPairing() = PreviewWrapper {
|
||||
val info = AapDeviceInfo(
|
||||
name = "AirPods Pro",
|
||||
modelNumber = "A2699",
|
||||
manufacturer = "Apple Inc.",
|
||||
serialNumber = "W5J7KV0N04",
|
||||
firmwareVersion = "7A305",
|
||||
leftEarbudSerial = "H3KL7HR926JY",
|
||||
leftEarbudFirstPaired = Instant.parse("2023-10-16T12:00:00Z"),
|
||||
)
|
||||
DeviceInfoDetailsContent(
|
||||
buildDeviceInfoDetailItems(info, rememberDeviceInfoDetailLabels()) {
|
||||
previewDateFormatter.format(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package eu.darken.capod.main.ui.devicesettings.cards
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapDeviceInfo
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
internal fun rememberDeviceInfoDetailLabels() = DeviceInfoDetailLabels(
|
||||
manufacturer = stringResource(R.string.device_settings_info_manufacturer_label),
|
||||
hardware = stringResource(R.string.device_settings_info_hardware_label),
|
||||
serial = stringResource(R.string.device_settings_info_serial_label),
|
||||
firmware = stringResource(R.string.device_settings_info_firmware_label),
|
||||
firmwarePending = stringResource(R.string.device_settings_info_firmware_pending_label),
|
||||
build = stringResource(R.string.device_settings_info_build_label),
|
||||
leftSerial = stringResource(R.string.device_settings_info_left_serial_label),
|
||||
rightSerial = stringResource(R.string.device_settings_info_right_serial_label),
|
||||
leftBonded = stringResource(R.string.device_settings_info_left_bonded_label),
|
||||
rightBonded = stringResource(R.string.device_settings_info_right_bonded_label),
|
||||
)
|
||||
|
||||
internal data class DeviceInfoDetailLabels(
|
||||
val manufacturer: String,
|
||||
val hardware: String,
|
||||
val serial: String,
|
||||
val firmware: String,
|
||||
val firmwarePending: String,
|
||||
val build: String,
|
||||
val leftSerial: String,
|
||||
val rightSerial: String,
|
||||
val leftBonded: String,
|
||||
val rightBonded: String,
|
||||
)
|
||||
|
||||
internal fun buildDeviceInfoDetailItems(
|
||||
info: AapDeviceInfo?,
|
||||
labels: DeviceInfoDetailLabels,
|
||||
formatDate: (Instant) -> String,
|
||||
): List<DeviceDetailItem> {
|
||||
if (info == null) return emptyList()
|
||||
return buildList {
|
||||
info.manufacturer.takeIf { it.isNotBlank() }?.let {
|
||||
add(DeviceDetailItem.Single(labels.manufacturer, it))
|
||||
}
|
||||
info.hardwareVersion?.takeIf { it.isNotBlank() }?.let {
|
||||
add(DeviceDetailItem.Single(labels.hardware, it))
|
||||
}
|
||||
info.serialNumber.takeIf { it.isNotBlank() }?.let {
|
||||
add(DeviceDetailItem.Single(labels.serial, it))
|
||||
}
|
||||
info.firmwareVersion.takeIf { it.isNotBlank() }?.let {
|
||||
add(DeviceDetailItem.Single(labels.firmware, it))
|
||||
}
|
||||
info.firmwareVersionPending?.takeIf { it.isNotBlank() }?.let {
|
||||
add(DeviceDetailItem.Single(labels.firmwarePending, it))
|
||||
}
|
||||
info.marketingVersion?.takeIf { it.isNotBlank() }?.let {
|
||||
add(DeviceDetailItem.Single(labels.build, it))
|
||||
}
|
||||
val leftSerial = info.leftEarbudSerial?.takeIf { it.isNotBlank() }
|
||||
val rightSerial = info.rightEarbudSerial?.takeIf { it.isNotBlank() }
|
||||
when {
|
||||
leftSerial != null && rightSerial != null -> add(
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single(labels.leftSerial, leftSerial),
|
||||
end = DeviceDetailItem.Single(labels.rightSerial, rightSerial),
|
||||
)
|
||||
)
|
||||
leftSerial != null -> add(DeviceDetailItem.Single(labels.leftSerial, leftSerial))
|
||||
rightSerial != null -> add(DeviceDetailItem.Single(labels.rightSerial, rightSerial))
|
||||
}
|
||||
val leftBonded = info.leftEarbudFirstPaired?.let(formatDate)
|
||||
val rightBonded = info.rightEarbudFirstPaired?.let(formatDate)
|
||||
when {
|
||||
leftBonded != null && rightBonded != null -> add(
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single(labels.leftBonded, leftBonded),
|
||||
end = DeviceDetailItem.Single(labels.rightBonded, rightBonded),
|
||||
)
|
||||
)
|
||||
leftBonded != null -> add(DeviceDetailItem.Single(labels.leftBonded, leftBonded))
|
||||
rightBonded != null -> add(DeviceDetailItem.Single(labels.rightBonded, rightBonded))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,11 +453,15 @@
|
||||
<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_hardware_label">Hardware</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_firmware_pending_label">Pending Firmware</string>
|
||||
<string name="device_settings_info_build_label">Build</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_left_bonded_label">Left Bonded</string>
|
||||
<string name="device_settings_info_right_bonded_label">Right Bonded</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>
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package eu.darken.capod.main.ui.devicesettings.cards
|
||||
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapDeviceInfo
|
||||
import io.kotest.matchers.collections.shouldContainExactly
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.junit.jupiter.api.Test
|
||||
import testhelpers.BaseTest
|
||||
import java.time.Instant
|
||||
|
||||
class DeviceInfoDetailItemsTest : BaseTest() {
|
||||
|
||||
private val labels = DeviceInfoDetailLabels(
|
||||
manufacturer = "Manufacturer",
|
||||
hardware = "Hardware",
|
||||
serial = "Serial Number",
|
||||
firmware = "Firmware",
|
||||
firmwarePending = "Pending Firmware",
|
||||
build = "Build",
|
||||
leftSerial = "Left Pod Serial",
|
||||
rightSerial = "Right Pod Serial",
|
||||
leftBonded = "Left Bonded",
|
||||
rightBonded = "Right Bonded",
|
||||
)
|
||||
|
||||
private val formatter: (Instant) -> String = { "fmt:${it.epochSecond}" }
|
||||
|
||||
private fun info(
|
||||
manufacturer: String = "",
|
||||
serialNumber: String = "",
|
||||
firmwareVersion: String = "",
|
||||
firmwareVersionPending: String? = null,
|
||||
hardwareVersion: String? = null,
|
||||
leftEarbudSerial: String? = null,
|
||||
rightEarbudSerial: String? = null,
|
||||
marketingVersion: String? = null,
|
||||
leftEarbudFirstPaired: Instant? = null,
|
||||
rightEarbudFirstPaired: Instant? = null,
|
||||
) = AapDeviceInfo(
|
||||
name = "AirPods",
|
||||
modelNumber = "A2084",
|
||||
manufacturer = manufacturer,
|
||||
serialNumber = serialNumber,
|
||||
firmwareVersion = firmwareVersion,
|
||||
firmwareVersionPending = firmwareVersionPending,
|
||||
hardwareVersion = hardwareVersion,
|
||||
leftEarbudSerial = leftEarbudSerial,
|
||||
rightEarbudSerial = rightEarbudSerial,
|
||||
marketingVersion = marketingVersion,
|
||||
leftEarbudFirstPaired = leftEarbudFirstPaired,
|
||||
rightEarbudFirstPaired = rightEarbudFirstPaired,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `null AapDeviceInfo yields empty list`() {
|
||||
buildDeviceInfoDetailItems(null, labels, formatter) shouldBe emptyList()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `minimal info renders only populated rows`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(manufacturer = "Apple", serialNumber = "ABC123", firmwareVersion = "7A305"),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Manufacturer", "Apple"),
|
||||
DeviceDetailItem.Single("Serial Number", "ABC123"),
|
||||
DeviceDetailItem.Single("Firmware", "7A305"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hardware row sits between manufacturer and serial`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(
|
||||
manufacturer = "Apple",
|
||||
hardwareVersion = "1.0.0",
|
||||
serialNumber = "ABC123",
|
||||
firmwareVersion = "7A305",
|
||||
),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Manufacturer", "Apple"),
|
||||
DeviceDetailItem.Single("Hardware", "1.0.0"),
|
||||
DeviceDetailItem.Single("Serial Number", "ABC123"),
|
||||
DeviceDetailItem.Single("Firmware", "7A305"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pending firmware row sits between firmware and build`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(
|
||||
manufacturer = "Apple",
|
||||
serialNumber = "ABC123",
|
||||
firmwareVersion = "81.26",
|
||||
firmwareVersionPending = "82.10",
|
||||
marketingVersion = "8454768",
|
||||
),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Manufacturer", "Apple"),
|
||||
DeviceDetailItem.Single("Serial Number", "ABC123"),
|
||||
DeviceDetailItem.Single("Firmware", "81.26"),
|
||||
DeviceDetailItem.Single("Pending Firmware", "82.10"),
|
||||
DeviceDetailItem.Single("Build", "8454768"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `null pending firmware omits the row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(firmwareVersion = "81.26", marketingVersion = "8454768"),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Firmware", "81.26"),
|
||||
DeviceDetailItem.Single("Build", "8454768"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blank pending firmware omits the row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(firmwareVersion = "81.26", firmwareVersionPending = " "),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Firmware", "81.26"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `both serials present yield a Paired row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(leftEarbudSerial = "LLL", rightEarbudSerial = "RRR"),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single("Left Pod Serial", "LLL"),
|
||||
end = DeviceDetailItem.Single("Right Pod Serial", "RRR"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only left serial yields a Single row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(leftEarbudSerial = "LLL"),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Left Pod Serial", "LLL"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only right serial yields a Single row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(rightEarbudSerial = "RRR"),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Right Pod Serial", "RRR"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `both bonded dates present always yield a Paired row`() {
|
||||
val sameSecond = Instant.ofEpochSecond(1697480211L)
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(leftEarbudFirstPaired = sameSecond, rightEarbudFirstPaired = sameSecond),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single("Left Bonded", "fmt:1697480211"),
|
||||
end = DeviceDetailItem.Single("Right Bonded", "fmt:1697480211"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `both bonded dates with different formatted values yield a Paired row`() {
|
||||
val left = Instant.ofEpochSecond(1708000000L)
|
||||
val right = Instant.ofEpochSecond(1697480211L)
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(leftEarbudFirstPaired = left, rightEarbudFirstPaired = right),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Paired(
|
||||
start = DeviceDetailItem.Single("Left Bonded", "fmt:1708000000"),
|
||||
end = DeviceDetailItem.Single("Right Bonded", "fmt:1697480211"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only left bonded date yields a Single row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(leftEarbudFirstPaired = Instant.ofEpochSecond(1697480211L)),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Left Bonded", "fmt:1697480211"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only right bonded date yields a Single row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(rightEarbudFirstPaired = Instant.ofEpochSecond(1697480211L)),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result shouldContainExactly listOf(
|
||||
DeviceDetailItem.Single("Right Bonded", "fmt:1697480211"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `both bonded dates null yields no bonded row`() {
|
||||
val result = buildDeviceInfoDetailItems(
|
||||
info(
|
||||
manufacturer = "Apple",
|
||||
leftEarbudFirstPaired = null,
|
||||
rightEarbudFirstPaired = null,
|
||||
),
|
||||
labels,
|
||||
formatter,
|
||||
)
|
||||
result.none { it is DeviceDetailItem.Paired } shouldBe true
|
||||
result.none {
|
||||
it is DeviceDetailItem.Single &&
|
||||
(it.label == labels.leftBonded || it.label == labels.rightBonded)
|
||||
} shouldBe true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user