refactor: Rename PodDevice to BlePodSnapshot, MonitoredDevice to PodDevice, extract PodModel

Complete type rename chain: PodDevice (interface) becomes BlePodSnapshot, MonitoredDevice (facade) claims PodDevice name, Model enum extracted to top-level PodModel. Delete redundant type alias files. Fix stale comments and log tags.
This commit is contained in:
darken
2026-03-31 19:17:09 +02:00
committed by Matthias Urhahn
parent e84f8762b4
commit 3fa01bb510
68 changed files with 631 additions and 647 deletions
@@ -9,9 +9,9 @@ import eu.darken.capod.main.core.GeneralSettings
import eu.darken.capod.main.core.MonitorMode
import eu.darken.capod.main.core.PermissionTool
import eu.darken.capod.monitor.core.DeviceMonitor
import eu.darken.capod.monitor.core.MonitoredDevice
import eu.darken.capod.monitor.core.PodDevice
import eu.darken.capod.monitor.core.worker.MonitorControl
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
@@ -32,11 +32,11 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import testhelpers.BaseTest
import testhelpers.coroutine.TestDispatcherProvider
import testhelpers.datastore.FakeDataStoreValue
import testhelpers.livedata.InstantExecutorExtension
import org.junit.jupiter.api.extension.ExtendWith
@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(InstantExecutorExtension::class)
@@ -54,7 +54,7 @@ class OverviewViewModelTest : BaseTest() {
private lateinit var profilesRepo: DeviceProfilesRepo
private lateinit var missingPermissionsFlow: MutableStateFlow<Set<Permission>>
private lateinit var devicesFlow: MutableStateFlow<List<MonitoredDevice>>
private lateinit var devicesFlow: MutableStateFlow<List<PodDevice>>
private lateinit var connectedDevicesFlow: MutableStateFlow<List<BluetoothDevice2>>
private lateinit var isBluetoothEnabledFlow: MutableStateFlow<Boolean>
private lateinit var profilesFlow: MutableStateFlow<List<DeviceProfile>>
@@ -154,7 +154,7 @@ class OverviewViewModelTest : BaseTest() {
@Test
fun `devices passed through when permissions granted`() = runTest(testDispatcher) {
val device = MonitoredDevice(ble = mockk(relaxed = true), aap = null)
val device = PodDevice(ble = mockk(relaxed = true), aap = null)
devicesFlow.value = listOf(device)
val vm = createViewModel()
@@ -165,17 +165,17 @@ class OverviewViewModelTest : BaseTest() {
@Test
fun `profiledDevices returns only devices with non-null profile`() {
val withProfile = object : PodDevice.Meta {
val withProfile = object : BlePodSnapshot.Meta {
override val profile: DeviceProfile = AppleDeviceProfile(label = "Test")
}
val withoutProfile = object : PodDevice.Meta {
val withoutProfile = object : BlePodSnapshot.Meta {
override val profile: DeviceProfile? = null
}
val profiled = MonitoredDevice(
val profiled = PodDevice(
ble = mockk(relaxed = true) { every { meta } returns withProfile },
aap = null,
)
val unmatched = MonitoredDevice(
val unmatched = PodDevice(
ble = mockk(relaxed = true) { every { meta } returns withoutProfile },
aap = null,
)
@@ -196,17 +196,17 @@ class OverviewViewModelTest : BaseTest() {
@Test
fun `unmatchedDevices returns only devices with null profile`() {
val withProfile = object : PodDevice.Meta {
val withProfile = object : BlePodSnapshot.Meta {
override val profile: DeviceProfile = AppleDeviceProfile(label = "Test")
}
val withoutProfile = object : PodDevice.Meta {
val withoutProfile = object : BlePodSnapshot.Meta {
override val profile: DeviceProfile? = null
}
val profiled = MonitoredDevice(
val profiled = PodDevice(
ble = mockk(relaxed = true) { every { meta } returns withProfile },
aap = null,
)
val unmatched = MonitoredDevice(
val unmatched = PodDevice(
ble = mockk(relaxed = true) { every { meta } returns withoutProfile },
aap = null,
)
@@ -1,10 +1,10 @@
package eu.darken.capod.monitor.core
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.HasEarDetectionDual
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.aap.AapConnectionState
@@ -20,15 +20,15 @@ import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import java.time.Instant
class MonitoredDeviceTest : BaseTest() {
class PodDeviceTest : BaseTest() {
private fun mockDualPod(
model: PodModel = PodModel.AIRPODS_PRO3,
leftBattery: Float? = 0.8f,
rightBattery: Float? = 0.9f,
caseBattery: Float? = 0.5f,
): PodDevice {
// DualApplePods implements DualPodDevice + HasCase + other interfaces
): BlePodSnapshot {
// DualApplePods implements DualBlePodSnapshot + HasCase + other interfaces
return mockk<DualApplePods>(relaxed = true) {
every { this@mockk.model } returns model
every { batteryLeftPodPercent } returns leftBattery
@@ -39,14 +39,14 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `BLE-only device exposes battery from BLE`() {
val device = MonitoredDevice(ble = mockDualPod(leftBattery = 0.8f), aap = null)
val device = PodDevice(ble = mockDualPod(leftBattery = 0.8f), aap = null)
device.batteryLeft shouldBe 0.8f
device.isAapConnected shouldBe false
}
@Test
fun `capabilities come from model features`() {
val device = MonitoredDevice(
val device = PodDevice(
ble = mockDualPod(model = PodModel.AIRPODS_PRO3),
aap = null,
)
@@ -58,7 +58,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `Beats Solo 3 has no dual pods or case`() {
val device = MonitoredDevice(
val device = PodDevice(
ble = mockk(relaxed = true) { every { model } returns PodModel.BEATS_SOLO_3 },
aap = null,
)
@@ -77,7 +77,7 @@ class MonitoredDeviceTest : BaseTest() {
),
),
)
val device = MonitoredDevice(ble = mockDualPod(), aap = aap)
val device = PodDevice(ble = mockDualPod(), aap = aap)
device.isAapConnected shouldBe true
device.ancMode.shouldNotBeNull()
device.ancMode!!.current shouldBe AncModeValue.TRANSPARENCY
@@ -85,21 +85,21 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `ANC mode is null when not AAP connected`() {
val device = MonitoredDevice(ble = mockDualPod(), aap = null)
val device = PodDevice(ble = mockDualPod(), aap = null)
device.ancMode.shouldBeNull()
}
@Test
fun `null BLE gives UNKNOWN model`() {
val device = MonitoredDevice(ble = null, aap = null)
val device = PodDevice(ble = null, aap = null)
device.model shouldBe PodModel.UNKNOWN
}
@Test
fun `identity properties delegate to BLE`() {
val id = PodDevice.Id()
val meta = mockk<PodDevice.Meta>(relaxed = true)
val device = MonitoredDevice(
val id = BlePodSnapshot.Id()
val meta = mockk<BlePodSnapshot.Meta>(relaxed = true)
val device = PodDevice(
ble = mockk(relaxed = true) {
every { identifier } returns id
every { this@mockk.meta } returns meta
@@ -112,7 +112,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `identity properties null when BLE null`() {
val device = MonitoredDevice(ble = null, aap = null)
val device = PodDevice(ble = null, aap = null)
device.identifier.shouldBeNull()
device.meta.shouldBeNull()
}
@@ -121,7 +121,7 @@ class MonitoredDeviceTest : BaseTest() {
fun `signal timing properties delegate to BLE`() {
val now = Instant.now()
val earlier = now.minusSeconds(60)
val device = MonitoredDevice(
val device = PodDevice(
ble = mockk(relaxed = true) {
every { seenLastAt } returns now
every { seenFirstAt } returns earlier
@@ -138,7 +138,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `signal timing defaults when BLE null`() {
val device = MonitoredDevice(ble = null, aap = null)
val device = PodDevice(ble = null, aap = null)
device.seenLastAt.shouldBeNull()
device.seenFirstAt.shouldBeNull()
device.signalQuality shouldBe 0f
@@ -153,7 +153,7 @@ class MonitoredDeviceTest : BaseTest() {
every { (this@mockk as HasChargeDetectionDual).isRightPodCharging } returns false
every { (this@mockk as HasCase).isCaseCharging } returns true
}
val device = MonitoredDevice(ble = mock, aap = null)
val device = PodDevice(ble = mock, aap = null)
device.isLeftPodCharging shouldBe true
device.isRightPodCharging shouldBe false
device.isCaseCharging shouldBe true
@@ -168,7 +168,7 @@ class MonitoredDeviceTest : BaseTest() {
every { (this@mockk as HasEarDetection).isBeingWorn } returns false
every { (this@mockk as HasEarDetectionDual).isEitherPodInEar } returns true
}
val device = MonitoredDevice(ble = mock, aap = null)
val device = PodDevice(ble = mock, aap = null)
device.isLeftInEar shouldBe true
device.isRightInEar shouldBe false
device.isBeingWorn shouldBe false
@@ -177,7 +177,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `icon and label properties delegate to BLE`() {
val device = MonitoredDevice(
val device = PodDevice(
ble = mockk(relaxed = true) {
every { model } returns PodModel.AIRPODS_PRO3
every { iconRes } returns 42
@@ -189,7 +189,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `rawDataHex empty when BLE null`() {
val device = MonitoredDevice(ble = null, aap = null)
val device = PodDevice(ble = null, aap = null)
device.rawDataHex shouldBe emptyList()
}
}
@@ -1,9 +1,9 @@
package eu.darken.capod.pods.core.apple
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice
import eu.darken.capod.pods.core.apple.misc.UnknownAppleSnapshotBle
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import kotlinx.coroutines.test.runTest
@@ -28,13 +28,13 @@ class AirPodsFactoryTest : BaseAirPodsTest() {
@Test
fun `unknown AppleDevice`() = runTest {
create<ApplePods>("07 19 01 FF FF 2B 99 8F 01 00 >09< 10 30 EE F3 41 B5 D8 9F A3 B0 B4 17 9F 85 97 5F") {
this shouldBe instanceOf<UnknownAppleDevice>()
this shouldBe instanceOf<UnknownAppleSnapshotBle>()
}
}
@Test
fun `invalid data`() = runTest {
create<PodDevice?>("abcd") {
create<BlePodSnapshot?>("abcd") {
this shouldBe null
}
}
@@ -6,7 +6,7 @@ import eu.darken.capod.common.SystemClockWrap
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.fromHex
import eu.darken.capod.common.serialization.SerializationModule
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.BlePodSnapshot
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesRepo
@@ -60,7 +60,7 @@ abstract class BaseAirPodsTest : BaseTest() {
every { SystemClockWrap.elapsedRealtimeNanos } returns 1000L
}
internal suspend inline fun <reified T : PodDevice?> create(
internal suspend inline fun <reified T : BlePodSnapshot?> create(
hex: String,
address: String = "77:49:4C:D8:25:0C",
block: T.() -> Unit