Extract Model to PodModel.kt

This commit is contained in:
darken
2026-03-31 19:17:09 +02:00
committed by Matthias Urhahn
parent ac21261d4d
commit e84f8762b4
77 changed files with 380 additions and 346 deletions
@@ -11,17 +11,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.toArgb
import eu.darken.capod.R
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.main.ui.widget.ComposeWidgetPreview
import eu.darken.capod.main.ui.widget.WidgetRenderState
import eu.darken.capod.reaction.ui.popup.PopUpContent as PopUpCard
import eu.darken.capod.main.ui.widget.WidgetConfigurationScreen
import eu.darken.capod.main.ui.widget.WidgetConfigurationViewModel
import eu.darken.capod.main.ui.widget.WidgetTheme
import eu.darken.capod.common.compose.preview.MOCK_NOW
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import eu.darken.capod.common.theming.CapodTheme
@@ -29,7 +22,12 @@ import eu.darken.capod.main.ui.overview.OverviewScreen
import eu.darken.capod.main.ui.overview.OverviewViewModel
import eu.darken.capod.main.ui.settings.SettingsScreen
import eu.darken.capod.main.ui.settings.SettingsViewModel
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.main.ui.widget.ComposeWidgetPreview
import eu.darken.capod.main.ui.widget.WidgetConfigurationScreen
import eu.darken.capod.main.ui.widget.WidgetConfigurationViewModel
import eu.darken.capod.main.ui.widget.WidgetRenderState
import eu.darken.capod.main.ui.widget.WidgetTheme
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.profiles.ui.DeviceManagerScreen
import eu.darken.capod.profiles.ui.DeviceManagerViewModel
import eu.darken.capod.profiles.ui.creation.DeviceProfileCreationScreen
@@ -37,6 +35,7 @@ import eu.darken.capod.profiles.ui.creation.DeviceProfileCreationViewModel
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
import eu.darken.capod.reaction.ui.ReactionSettingsScreen
import eu.darken.capod.reaction.ui.ReactionSettingsViewModel
import eu.darken.capod.reaction.ui.popup.PopUpContent as PopUpCard
internal const val DS = "spec:width=1080px,height=2400px,dpi=428"
@@ -54,8 +53,8 @@ internal fun DashboardContent() = PreviewWrapper {
isDebugMode = false,
isBluetoothEnabled = true,
profiles = listOf(
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
),
upgradeInfo = MockPodDataProvider.gplayInfo(isPro = true),
showUnmatchedDevices = false,
@@ -73,9 +72,9 @@ internal fun DeviceProfilesContent() = PreviewWrapper {
DeviceManagerScreen(
state = DeviceManagerViewModel.State(
profiles = listOf(
MockPodDataProvider.profile("Work AirPods", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
MockPodDataProvider.profile("Gym Beats", PodDevice.Model.POWERBEATS_PRO),
MockPodDataProvider.profile("Work AirPods", PodModel.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
MockPodDataProvider.profile("Gym Beats", PodModel.POWERBEATS_PRO),
),
),
onBack = {},
@@ -92,7 +91,7 @@ internal fun AddProfileContent() = PreviewWrapper {
name = "",
nameError = null,
selectedModel = null,
availableModels = PodDevice.Model.entries.filter { it != PodDevice.Model.UNKNOWN },
availableModels = PodModel.entries.filter { it != PodModel.UNKNOWN },
identityKey = null,
encryptionKey = null,
selectedDevice = null,
@@ -158,8 +157,8 @@ internal fun ReactionSettingsContent() = PreviewWrapper {
@Composable
internal fun WidgetConfigurationContent() = PreviewWrapper {
val profiles = listOf(
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
)
WidgetConfigurationScreen(
state = WidgetConfigurationViewModel.State(
@@ -13,11 +13,12 @@ import eu.darken.capod.pods.core.HasDualMicrophone
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.SinglePodDevice
import eu.darken.capod.pods.core.unknown.UnknownDevice
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
import eu.darken.capod.pods.core.unknown.UnknownDevice
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import java.time.Instant
@@ -39,7 +40,7 @@ object MockPodDataProvider {
// --- Dual pod scenarios ---
fun airPodsProFullCharge(): DualPodDevice = MockDualPodDevice(
_model = PodDevice.Model.AIRPODS_PRO2,
_model = PodModel.AIRPODS_PRO2,
_label = "Work AirPods",
batteryLeftPodPercent = 1.0f,
batteryRightPodPercent = 1.0f,
@@ -50,7 +51,7 @@ object MockPodDataProvider {
)
fun airPodsProMixed(): DualPodDevice = MockDualPodDevice(
_model = PodDevice.Model.AIRPODS_PRO2,
_model = PodModel.AIRPODS_PRO2,
_label = "My AirPods Pro",
batteryLeftPodPercent = 0.80f,
batteryRightPodPercent = 0.45f,
@@ -62,7 +63,7 @@ object MockPodDataProvider {
)
fun airPodsProLowBattery(): DualPodDevice = MockDualPodDevice(
_model = PodDevice.Model.AIRPODS_PRO2,
_model = PodModel.AIRPODS_PRO2,
_label = "My AirPods Pro",
batteryLeftPodPercent = 0.10f,
batteryRightPodPercent = 0.05f,
@@ -75,7 +76,7 @@ object MockPodDataProvider {
)
fun airPodsProInCase(): DualPodDevice = MockDualPodDevice(
_model = PodDevice.Model.AIRPODS_PRO2,
_model = PodModel.AIRPODS_PRO2,
_label = "My AirPods Pro",
batteryLeftPodPercent = null,
batteryRightPodPercent = null,
@@ -86,7 +87,7 @@ object MockPodDataProvider {
)
fun airPodsGen1Wearing(): DualPodDevice = MockDualPodDevice(
_model = PodDevice.Model.AIRPODS_GEN1,
_model = PodModel.AIRPODS_GEN1,
_label = "Old AirPods",
batteryLeftPodPercent = 0.70f,
batteryRightPodPercent = 0.65f,
@@ -100,7 +101,7 @@ object MockPodDataProvider {
)
fun powerBeatsPro(): DualPodDevice = MockDualPodDeviceNoCase(
_model = PodDevice.Model.POWERBEATS_PRO,
_model = PodModel.POWERBEATS_PRO,
_label = "PowerBeats Pro",
batteryLeftPodPercent = 0.55f,
batteryRightPodPercent = 0.60f,
@@ -111,14 +112,14 @@ object MockPodDataProvider {
// --- Single pod scenarios ---
fun airPodsMax(): SinglePodDevice = MockSinglePodDevice(
_model = PodDevice.Model.AIRPODS_MAX,
_model = PodModel.AIRPODS_MAX,
_label = "AirPods Max",
batteryHeadsetPercent = 0.85f,
_isBeingWorn = true,
)
fun airPodsMaxCharging(): SinglePodDevice = MockSingleApplePodDevice(
_model = PodDevice.Model.AIRPODS_MAX,
_model = PodModel.AIRPODS_MAX,
_label = "AirPods Max",
batteryHeadsetPercent = 0.40f,
_isHeadsetBeingCharged = true,
@@ -127,7 +128,7 @@ object MockPodDataProvider {
)
fun beatsSolo3(): SinglePodDevice = MockSinglePodDevice(
_model = PodDevice.Model.BEATS_SOLO_3,
_model = PodModel.BEATS_SOLO_3,
_label = "Beats Solo 3",
batteryHeadsetPercent = 0.70f,
)
@@ -140,7 +141,7 @@ object MockPodDataProvider {
// --- Profile helpers ---
fun profile(label: String, model: PodDevice.Model): AppleDeviceProfile = AppleDeviceProfile(
fun profile(label: String, model: PodModel): AppleDeviceProfile = AppleDeviceProfile(
label = label,
model = model,
)
@@ -188,7 +189,7 @@ private data class MockUpgradeInfo(
) : UpgradeRepo.Info
private class MockDualPodDevice(
private val _model: PodDevice.Model,
private val _model: PodModel,
private val _label: String,
override val batteryLeftPodPercent: Float?,
override val batteryRightPodPercent: Float?,
@@ -206,7 +207,7 @@ private class MockDualPodDevice(
rssi: Int = -50,
) : DualPodDevice, HasCase, HasChargeDetectionDual, HasEarDetectionDual, HasDualMicrophone {
override val identifier: PodDevice.Id = PodDevice.Id()
override val model: PodDevice.Model = _model
override val model: PodModel = _model
override val seenLastAt: Instant = MOCK_NOW
override val seenFirstAt: Instant = MOCK_NOW
override val seenCounter: Int = 5
@@ -239,7 +240,7 @@ private class MockDualPodDevice(
}
private class MockDualPodDeviceNoCase(
private val _model: PodDevice.Model,
private val _model: PodModel,
private val _label: String,
override val batteryLeftPodPercent: Float?,
override val batteryRightPodPercent: Float?,
@@ -248,7 +249,7 @@ private class MockDualPodDeviceNoCase(
rssi: Int = -50,
) : DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual {
override val identifier: PodDevice.Id = PodDevice.Id()
override val model: PodDevice.Model = _model
override val model: PodModel = _model
override val seenLastAt: Instant = MOCK_NOW
override val seenFirstAt: Instant = MOCK_NOW
override val seenCounter: Int = 5
@@ -272,7 +273,7 @@ private class MockDualPodDeviceNoCase(
}
private class MockSinglePodDevice(
private val _model: PodDevice.Model,
private val _model: PodModel,
private val _label: String,
override val batteryHeadsetPercent: Float?,
private val _isHeadsetBeingCharged: Boolean = false,
@@ -280,7 +281,7 @@ private class MockSinglePodDevice(
rssi: Int = -50,
) : SinglePodDevice, HasChargeDetection, HasEarDetection {
override val identifier: PodDevice.Id = PodDevice.Id()
override val model: PodDevice.Model = _model
override val model: PodModel = _model
override val seenLastAt: Instant = MOCK_NOW
override val seenFirstAt: Instant = MOCK_NOW
override val seenCounter: Int = 5
@@ -303,7 +304,7 @@ private class MockSinglePodDevice(
@Suppress("PropertyName")
private class MockSingleApplePodDevice(
private val _model: PodDevice.Model,
private val _model: PodModel,
private val _label: String,
override val batteryHeadsetPercent: Float?,
private val _isHeadsetBeingCharged: Boolean = false,
@@ -313,7 +314,7 @@ private class MockSingleApplePodDevice(
rssi: Int = -50,
) : SinglePodDevice, ApplePods, HasChargeDetection, HasEarDetection {
override val identifier: PodDevice.Id = PodDevice.Id()
override val model: PodDevice.Model = _model
override val model: PodModel = _model
override val seenLastAt: Instant = MOCK_NOW
override val seenFirstAt: Instant = MOCK_NOW
override val seenCounter: Int = 5
@@ -16,7 +16,7 @@ import eu.darken.capod.common.serialization.SerializationCapod
import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode
import eu.darken.capod.common.theming.ThemeStyle
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.protocol.IdentityResolvingKey
import eu.darken.capod.pods.core.apple.protocol.ProximityEncryptionKey
import kotlinx.serialization.builtins.nullable
@@ -54,7 +54,8 @@ class GeneralSettings @Inject constructor(
writer = { value -> value },
)
val oldMainDeviceModel = dataStore.createValue("core.maindevice.model", PodDevice.Model.UNKNOWN, json, onErrorFallbackToDefault = true)
val oldMainDeviceModel =
dataStore.createValue("core.maindevice.model", PodModel.UNKNOWN, json, onErrorFallbackToDefault = true)
val oldMainDeviceIdentityKey = dataStore.createValue<IdentityResolvingKey?>(
key = "core.maindevice.identitykey",
@@ -9,9 +9,12 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.DevicesOther
import androidx.compose.material.icons.twotone.Settings
import androidx.compose.material.icons.twotone.Stars
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
@@ -24,10 +27,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.DevicesOther
import androidx.compose.material.icons.twotone.Settings
import androidx.compose.material.icons.twotone.Stars
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
@@ -37,11 +36,11 @@ import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.permissions.Permission
@@ -55,7 +54,7 @@ import eu.darken.capod.main.ui.overview.cards.SinglePodsCard
import eu.darken.capod.main.ui.overview.cards.UnknownPodDeviceCard
import eu.darken.capod.main.ui.overview.cards.UnmatchedDevicesCard
import eu.darken.capod.monitor.core.MonitoredDevice
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import java.time.Instant
@Composable
@@ -261,7 +260,7 @@ fun OverviewScreen(
private fun PodDeviceCard(device: MonitoredDevice, showDebug: Boolean, now: Instant) {
when {
device.hasDualPods -> DualPodsCard(device = device, showDebug = showDebug, now = now)
device.model != PodDevice.Model.UNKNOWN -> SinglePodsCard(device = device, showDebug = showDebug, now = now)
device.model != PodModel.UNKNOWN -> SinglePodsCard(device = device, showDebug = showDebug, now = now)
else -> UnknownPodDeviceCard(device = device, showDebug = showDebug, now = now)
}
}
@@ -281,8 +280,8 @@ private fun OverviewScreenWithDevicesPreview() = PreviewWrapper {
isDebugMode = false,
isBluetoothEnabled = true,
profiles = listOf(
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
),
upgradeInfo = MockPodDataProvider.fossInfo(),
showUnmatchedDevices = false,
@@ -305,7 +304,7 @@ private fun OverviewScreenEmptyPreview() = PreviewWrapper {
devices = emptyList(),
isDebugMode = false,
isBluetoothEnabled = true,
profiles = listOf(MockPodDataProvider.profile("My AirPods", PodDevice.Model.AIRPODS_PRO2)),
profiles = listOf(MockPodDataProvider.profile("My AirPods", PodModel.AIRPODS_PRO2)),
upgradeInfo = MockPodDataProvider.fossInfo(),
showUnmatchedDevices = false,
),
@@ -349,7 +348,7 @@ private fun OverviewScreenBluetoothOffPreview() = PreviewWrapper {
devices = emptyList(),
isDebugMode = false,
isBluetoothEnabled = false,
profiles = listOf(MockPodDataProvider.profile("My AirPods", PodDevice.Model.AIRPODS_PRO2)),
profiles = listOf(MockPodDataProvider.profile("My AirPods", PodModel.AIRPODS_PRO2)),
upgradeInfo = MockPodDataProvider.fossInfo(isPro = true),
showUnmatchedDevices = false,
),
@@ -24,10 +24,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
@@ -77,7 +77,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.profiles.core.DeviceProfile
@OptIn(ExperimentalLayoutApi::class)
@@ -413,7 +413,7 @@ private fun ProfileSelectionItem(
style = MaterialTheme.typography.bodyLarge,
)
val modelText = when (profile.model) {
PodDevice.Model.UNKNOWN -> stringResource(R.string.pods_unknown_label)
PodModel.UNKNOWN -> stringResource(R.string.pods_unknown_label)
else -> profile.model.label
}
Text(
@@ -456,7 +456,9 @@ private fun WidgetConfigPreview(
tonalElevation = 2.dp,
) {
Box(
modifier = Modifier.fillMaxWidth().padding(24.dp),
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
contentAlignment = Alignment.Center,
) {
if (hasTransparency) {
@@ -691,8 +693,8 @@ private fun HexColorInput(
@Composable
private fun WidgetConfigurationScreenPreview() = PreviewWrapper {
val profiles = listOf(
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
)
WidgetConfigurationScreen(
state = WidgetConfigurationViewModel.State(
@@ -720,7 +722,7 @@ private fun WidgetConfigurationScreenPreview() = PreviewWrapper {
@Composable
private fun WidgetConfigurationScreenCustomPreview() = PreviewWrapper {
val profiles = listOf(
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
)
WidgetConfigurationScreen(
state = WidgetConfigurationViewModel.State(
@@ -753,7 +755,7 @@ private fun WidgetConfigurationScreenCustomPreview() = PreviewWrapper {
@Composable
private fun WidgetConfigurationScreenNonProPreview() = PreviewWrapper {
val profiles = listOf(
MockPodDataProvider.profile("My AirPods Pro", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("My AirPods Pro", PodModel.AIRPODS_PRO2),
)
WidgetConfigurationScreen(
state = WidgetConfigurationViewModel.State(
@@ -5,7 +5,7 @@ import androidx.annotation.ColorInt
import androidx.appcompat.view.ContextThemeWrapper
import eu.darken.capod.R
import eu.darken.capod.monitor.core.MonitoredDevice
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.getBatteryDrawable
import eu.darken.capod.pods.core.toBatteryFloat
@@ -54,7 +54,7 @@ object WidgetRenderStateMapper {
caseCharging = device.isCaseCharging == true,
)
device != null && device.model != PodDevice.Model.UNKNOWN -> WidgetRenderState.SinglePod(
device != null && device.model != PodModel.UNKNOWN -> WidgetRenderState.SinglePod(
theme = theme,
resolvedBgColor = bgColor,
resolvedTextColor = textColor,
@@ -11,6 +11,7 @@ import eu.darken.capod.pods.core.HasDualMicrophone
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.SinglePodDevice
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState
@@ -27,7 +28,7 @@ data class MonitoredDevice(
internal val aap: AapPodState?,
) {
// Identity
val model: PodDevice.Model get() = ble?.model ?: PodDevice.Model.UNKNOWN
val model: PodModel get() = ble?.model ?: PodModel.UNKNOWN
val address: BluetoothAddress? get() = ble?.address
val identifier: PodDevice.Id? get() = ble?.identifier
val meta: PodDevice.Meta? get() = ble?.meta
@@ -6,7 +6,7 @@ import android.widget.RemoteViews
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.capod.R
import eu.darken.capod.monitor.core.MonitoredDevice
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.formatBatteryPercent
import eu.darken.capod.pods.core.getBatteryDrawable
import javax.inject.Inject
@@ -19,7 +19,7 @@ class MonitorNotificationViewFactory @Inject constructor(
fun createContentView(device: MonitoredDevice): RemoteViews = when {
device.hasDualPods -> createDualPods(device)
device.model != PodDevice.Model.UNKNOWN -> createSinglePod(device)
device.model != PodModel.UNKNOWN -> createSinglePod(device)
else -> createUnknownDevice(device)
}
@@ -81,7 +81,7 @@ class MonitorNotificationViewFactory @Inject constructor(
fun createBigContentView(device: MonitoredDevice): RemoteViews = when {
device.hasDualPods -> createDualPodsBig(device)
device.model != PodDevice.Model.UNKNOWN -> createSinglePodBig(device)
device.model != PodModel.UNKNOWN -> createSinglePodBig(device)
else -> createUnknownDeviceBig(device)
}
@@ -16,7 +16,7 @@ import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.notifications.PendingIntentCompat
import eu.darken.capod.main.ui.MainActivity
import eu.darken.capod.monitor.core.MonitoredDevice
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.formatBatteryPercent
import javax.inject.Inject
@@ -100,7 +100,7 @@ class MonitorNotifications @Inject constructor(
}
}
device.model != PodDevice.Model.UNKNOWN -> {
device.model != PodModel.UNKNOWN -> {
val headset = formatBatteryPercent(context, device.batteryHeadset)
if (device.hasCase) {
val case = formatBatteryPercent(context, device.batteryCase)
@@ -2,14 +2,11 @@ package eu.darken.capod.pods.core
import android.content.Context
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.profiles.core.DeviceProfile
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.Duration
import java.time.Instant
import java.util.UUID
@@ -20,7 +17,7 @@ interface PodDevice {
val identifier: Id
val model: Model
val model: PodModel
val address: BluetoothAddress
get() = scanResult.address
@@ -74,119 +71,6 @@ interface PodDevice {
@JvmInline
value class Id(private val id: UUID = UUID.randomUUID())
@Serializable
enum class Model(
val label: String,
@DrawableRes val iconRes: Int = R.drawable.device_earbuds_generic_both,
val features: Features = Features(),
) {
@SerialName("airpods.gen1") AIRPODS_GEN1(
"AirPods (Gen 1)", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen2") AIRPODS_GEN2(
"AirPods (Gen 2)", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen3") AIRPODS_GEN3(
"AirPods (Gen 3)", R.drawable.device_airpods_gen3_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen4") AIRPODS_GEN4(
"AirPods (Gen 4)", R.drawable.device_airpods_gen3_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen4.anc") AIRPODS_GEN4_ANC(
"AirPods (Gen 4 ANC)", R.drawable.device_airpods_gen4anc_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro") AIRPODS_PRO(
"AirPods Pro", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro2") AIRPODS_PRO2(
"AirPods Pro 2", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro2.usbc") AIRPODS_PRO2_USBC(
"AirPods Pro 2 USB-C", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro3") AIRPODS_PRO3(
"AirPods Pro 3", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.max") AIRPODS_MAX(
"AirPods Max", R.drawable.device_airpods_max,
Features(hasAncControl = true),
),
@SerialName("airpods.max.usbc") AIRPODS_MAX_USBC(
"AirPods Max USB-C", R.drawable.device_airpods_max,
Features(hasAncControl = true),
),
@SerialName("beats.flex") BEATS_FLEX(
"Beats Flex", R.drawable.device_beats_earbuds,
),
@SerialName("beats.solo.3") BEATS_SOLO_3(
"Beats Solo 3", R.drawable.device_beats_headphones,
),
@SerialName("beats.studio.3") BEATS_STUDIO_3(
"Beats Studio 3", R.drawable.device_beats_studio3,
Features(hasAncControl = true),
),
@SerialName("beats.x") BEATS_X(
"Beats X", R.drawable.device_beats_x,
),
@SerialName("beats.powerbeats.3") POWERBEATS_3(
"Power Beats 3", R.drawable.device_powerbeats_3,
),
@SerialName("beats.powerbeats.4") POWERBEATS_4(
"Power Beats 4", R.drawable.device_powerbeats_4,
),
@SerialName("beats.powerbeats.pro") POWERBEATS_PRO(
"Power Beats Pro", R.drawable.device_powerbeats_pro_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true),
),
@SerialName("beats.powerbeats.pro2") POWERBEATS_PRO2(
"Power Beats Pro 2", R.drawable.device_powerbeats_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("beats.fit.pro") BEATS_FIT_PRO(
"Beats Fit Pro", R.drawable.device_beats_fitpro_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("fakes.tws.i99999") FAKE_AIRPODS_GEN1(
"AirPods (Gen 1)? \uD83C\uDFAD", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("fakes.generic.airpods.gen2") FAKE_AIRPODS_GEN2(
"AirPods (Gen 2)? \uD83C\uDFAD", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("fakes.generic.airpods.gen3") FAKE_AIRPODS_GEN3(
"AirPods (Gen 3)? \uD83C\uDFAD", R.drawable.device_airpods_gen3_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("fakes.varunr.airpodspro") FAKE_AIRPODS_PRO(
"AirPods Pro? \uD83C\uDFAD", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("fakes.generic.airpods.pro2") FAKE_AIRPODS_PRO2(
"AirPods Pro2? \uD83C\uDFAD", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("unknown") UNKNOWN(
"Unknown"
);
data class Features(
val hasDualPods: Boolean = false,
val hasCase: Boolean = false,
val hasEarDetection: Boolean = false,
val hasAncControl: Boolean = false,
)
}
companion object {
const val BASE_CONFIDENCE = 0.0f
}
@@ -0,0 +1,119 @@
package eu.darken.capod.pods.core
import androidx.annotation.DrawableRes
import eu.darken.capod.R
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
enum class PodModel(
val label: String,
@DrawableRes val iconRes: Int = R.drawable.device_earbuds_generic_both,
val features: Features = Features(),
) {
@SerialName("airpods.gen1") AIRPODS_GEN1(
"AirPods (Gen 1)", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen2") AIRPODS_GEN2(
"AirPods (Gen 2)", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen3") AIRPODS_GEN3(
"AirPods (Gen 3)", R.drawable.device_airpods_gen3_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen4") AIRPODS_GEN4(
"AirPods (Gen 4)", R.drawable.device_airpods_gen3_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("airpods.gen4.anc") AIRPODS_GEN4_ANC(
"AirPods (Gen 4 ANC)", R.drawable.device_airpods_gen4anc_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro") AIRPODS_PRO(
"AirPods Pro", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro2") AIRPODS_PRO2(
"AirPods Pro 2", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro2.usbc") AIRPODS_PRO2_USBC(
"AirPods Pro 2 USB-C", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.pro3") AIRPODS_PRO3(
"AirPods Pro 3", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("airpods.max") AIRPODS_MAX(
"AirPods Max", R.drawable.device_airpods_max,
Features(hasAncControl = true),
),
@SerialName("airpods.max.usbc") AIRPODS_MAX_USBC(
"AirPods Max USB-C", R.drawable.device_airpods_max,
Features(hasAncControl = true),
),
@SerialName("beats.flex") BEATS_FLEX(
"Beats Flex", R.drawable.device_beats_earbuds,
),
@SerialName("beats.solo.3") BEATS_SOLO_3(
"Beats Solo 3", R.drawable.device_beats_headphones,
),
@SerialName("beats.studio.3") BEATS_STUDIO_3(
"Beats Studio 3", R.drawable.device_beats_studio3,
Features(hasAncControl = true),
),
@SerialName("beats.x") BEATS_X(
"Beats X", R.drawable.device_beats_x,
),
@SerialName("beats.powerbeats.3") POWERBEATS_3(
"Power Beats 3", R.drawable.device_powerbeats_3,
),
@SerialName("beats.powerbeats.4") POWERBEATS_4(
"Power Beats 4", R.drawable.device_powerbeats_4,
),
@SerialName("beats.powerbeats.pro") POWERBEATS_PRO(
"Power Beats Pro", R.drawable.device_powerbeats_pro_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true),
),
@SerialName("beats.powerbeats.pro2") POWERBEATS_PRO2(
"Power Beats Pro 2", R.drawable.device_powerbeats_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("beats.fit.pro") BEATS_FIT_PRO(
"Beats Fit Pro", R.drawable.device_beats_fitpro_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("fakes.tws.i99999") FAKE_AIRPODS_GEN1(
"AirPods (Gen 1)? \uD83C\uDFAD", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("fakes.generic.airpods.gen2") FAKE_AIRPODS_GEN2(
"AirPods (Gen 2)? \uD83C\uDFAD", R.drawable.device_airpods_gen1_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("fakes.generic.airpods.gen3") FAKE_AIRPODS_GEN3(
"AirPods (Gen 3)? \uD83C\uDFAD", R.drawable.device_airpods_gen3_both,
Features(hasDualPods = true, hasCase = true),
),
@SerialName("fakes.varunr.airpodspro") FAKE_AIRPODS_PRO(
"AirPods Pro? \uD83C\uDFAD", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("fakes.generic.airpods.pro2") FAKE_AIRPODS_PRO2(
"AirPods Pro2? \uD83C\uDFAD", R.drawable.device_airpods_pro2_both,
Features(hasDualPods = true, hasCase = true, hasEarDetection = true, hasAncControl = true),
),
@SerialName("unknown") UNKNOWN(
"Unknown"
);
data class Features(
val hasDualPods: Boolean = false,
val hasCase: Boolean = false,
val hasEarDetection: Boolean = false,
val hasAncControl: Boolean = false,
)
}
@@ -7,6 +7,7 @@ import eu.darken.capod.common.debug.logging.asLog
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
@@ -98,7 +99,7 @@ class AppleFactory @Inject constructor(
meta = ApplePods.AppleMeta(),
)
profile = profiles
.filter { it.model == PodDevice.Model.UNKNOWN || it.model == tempDevice.model }
.filter { it.model == PodModel.UNKNOWN || it.model == tempDevice.model }
.firstOrNull { it.minimumSignalQuality <= tempDevice.signalQuality }
}
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -27,7 +28,7 @@ data class AirPodsGen1(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN1
override val model: PodModel = PodModel.AIRPODS_GEN1
override val batteryCasePercent: Float?
get() = super.batteryCasePercent ?: cachedBatteryPercentage
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -27,7 +28,7 @@ data class AirPodsGen2(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN2
override val model: PodModel = PodModel.AIRPODS_GEN2
override val batteryCasePercent: Float?
get() = super.batteryCasePercent ?: cachedBatteryPercentage
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -29,7 +30,7 @@ data class AirPodsGen3(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN3
override val model: PodModel = PodModel.AIRPODS_GEN3
@get:DrawableRes
override val leftPodIcon: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -29,7 +30,7 @@ data class AirPodsGen4(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN4
override val model: PodModel = PodModel.AIRPODS_GEN4
@get:DrawableRes
override val leftPodIcon: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -29,7 +30,7 @@ data class AirPodsGen4Anc(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_GEN4_ANC
override val model: PodModel = PodModel.AIRPODS_GEN4_ANC
@get:DrawableRes
override val leftPodIcon: Int
@@ -6,6 +6,7 @@ import eu.darken.capod.common.isBitSet
import eu.darken.capod.pods.core.HasChargeDetection
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -29,7 +30,7 @@ data class AirPodsMax(
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX
override val model: PodModel = PodModel.AIRPODS_MAX
override val rssi: Int
get() = rssiAverage ?: super<SingleApplePods>.rssi
@@ -6,6 +6,7 @@ import eu.darken.capod.common.isBitSet
import eu.darken.capod.pods.core.HasChargeDetection
import eu.darken.capod.pods.core.HasEarDetection
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -29,7 +30,7 @@ data class AirPodsMaxUsbc(
private val rssiAverage: Int? = null,
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX_USBC
override val model: PodModel = PodModel.AIRPODS_MAX_USBC
override val rssi: Int
get() = rssiAverage ?: super<SingleApplePods>.rssi
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -30,7 +31,7 @@ data class AirPodsPro(
private val cachedCaseState: LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO
override val model: PodModel = PodModel.AIRPODS_PRO
@get:DrawableRes
override val iconRes: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -30,7 +31,7 @@ data class AirPodsPro2(
private val cachedCaseState: LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO2
override val model: PodModel = PodModel.AIRPODS_PRO2
@get:DrawableRes
override val iconRes: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -30,7 +31,7 @@ data class AirPodsPro2Usbc(
private val cachedCaseState: LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO2_USBC
override val model: PodModel = PodModel.AIRPODS_PRO2_USBC
@get:DrawableRes
override val iconRes: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -30,7 +31,7 @@ data class AirPodsPro3(
private val cachedCaseState: LidState? = null
) : DualApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO3
override val model: PodModel = PodModel.AIRPODS_PRO3
@get:DrawableRes
override val iconRes: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -29,7 +30,7 @@ data class BeatsFitPro(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodDevice.Model = PodDevice.Model.BEATS_FIT_PRO
override val model: PodModel = PodModel.BEATS_FIT_PRO
@get:DrawableRes
override val leftPodIcon: Int
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.SingleApplePods
@@ -25,7 +26,7 @@ data class BeatsFlex(
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodDevice.Model = PodDevice.Model.BEATS_FLEX
override val model: PodModel = PodModel.BEATS_FLEX
override val rssi: Int
get() = rssiAverage ?: super.rssi
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.SingleApplePods
@@ -25,7 +26,7 @@ data class BeatsSolo3(
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodDevice.Model = PodDevice.Model.BEATS_SOLO_3
override val model: PodModel = PodModel.BEATS_SOLO_3
override val rssi: Int
get() = rssiAverage ?: super.rssi
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.SingleApplePods
@@ -25,7 +26,7 @@ data class BeatsStudio3(
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodDevice.Model = PodDevice.Model.BEATS_STUDIO_3
override val model: PodModel = PodModel.BEATS_STUDIO_3
class Factory @Inject constructor(
private val repo: PodHistoryRepo,
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.SingleApplePods
@@ -25,7 +26,7 @@ data class BeatsX(
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodDevice.Model = PodDevice.Model.BEATS_X
override val model: PodModel = PodModel.BEATS_X
override val rssi: Int
get() = rssiAverage ?: super.rssi
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.SingleApplePods
@@ -25,7 +26,7 @@ data class PowerBeats3(
private val rssiAverage: Int? = null,
) : SingleApplePods {
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_3
override val model: PodModel = PodModel.POWERBEATS_3
override val rssi: Int
get() = rssiAverage ?: super.rssi
@@ -3,6 +3,7 @@ package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.SingleApplePods
@@ -26,7 +27,7 @@ data class PowerBeats4(
private val rssiAverage: Int? = null,
) : SingleApplePods, HasStateDetectionAirPods {
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_4
override val model: PodModel = PodModel.POWERBEATS_4
override val rssi: Int
get() = rssiAverage ?: super<SingleApplePods>.rssi
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -29,7 +30,7 @@ data class PowerBeatsPro(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO
override val model: PodModel = PodModel.POWERBEATS_PRO
@get:DrawableRes
override val leftPodIcon: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -29,7 +30,7 @@ data class PowerBeatsPro2(
private val cachedCaseState: DualApplePods.LidState? = null
) : DualApplePods {
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO2
override val model: PodModel = PodModel.POWERBEATS_PRO2
@get:DrawableRes
override val leftPodIcon: Int
@@ -9,6 +9,7 @@ import eu.darken.capod.common.lowerNibble
import eu.darken.capod.common.upperNibble
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
@@ -65,7 +66,7 @@ class PodHistoryRepo @Inject constructor(
val chargingLeft: Boolean,
val chargingRight: Boolean,
val color: UByte,
val model: PodDevice.Model,
val model: PodModel,
)
suspend fun search(current: ApplePods): KnownDevice? {
@@ -6,6 +6,7 @@ import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
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.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -33,13 +34,13 @@ data class FakeAirPodsGen1(
private val cachedBatteryPercentage: Float? = null,
) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN1
override val model: PodModel = PodModel.FAKE_AIRPODS_GEN1
override val batteryCasePercent: Float?
get() = super.batteryCasePercent ?: cachedBatteryPercentage
override val rssi: Int
get() = rssiAverage ?: super<DualApplePods>.rssi
get() = rssiAverage ?: super.rssi
class Factory @Inject constructor(
private val repo: PodHistoryRepo,
@@ -6,6 +6,7 @@ import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
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.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -31,10 +32,10 @@ data class FakeAirPodsGen2(
private val cachedBatteryPercentage: Float? = null,
) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN2
override val model: PodModel = PodModel.FAKE_AIRPODS_GEN2
override val rssi: Int
get() = rssiAverage ?: super<DualApplePods>.rssi
get() = rssiAverage ?: super.rssi
override val batteryCasePercent: Float?
get() = super.batteryCasePercent ?: cachedBatteryPercentage
@@ -8,6 +8,7 @@ import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
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.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -35,7 +36,7 @@ data class FakeAirPodsGen3(
private val cachedBatteryPercentage: Float? = null,
) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN3
override val model: PodModel = PodModel.FAKE_AIRPODS_GEN3
@get:DrawableRes
override val leftPodIcon: Int
@@ -50,7 +51,7 @@ data class FakeAirPodsGen3(
get() = R.drawable.device_airpods_gen3_case
override val rssi: Int
get() = rssiAverage ?: super<DualApplePods>.rssi
get() = rssiAverage ?: super.rssi
override val batteryCasePercent: Float?
get() = super.batteryCasePercent ?: cachedBatteryPercentage
@@ -8,6 +8,7 @@ import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
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.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -35,7 +36,7 @@ data class FakeAirPodsPro(
private val cachedBatteryPercentage: Float? = null,
) : DualApplePods, HasChargeDetectionDual, HasEarDetectionDual, HasCase {
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO
override val model: PodModel = PodModel.FAKE_AIRPODS_PRO
@get:DrawableRes
override val iconRes: Int
@@ -54,7 +55,7 @@ data class FakeAirPodsPro(
get() = R.drawable.device_airpods_pro2_right
override val rssi: Int
get() = rssiAverage ?: super<DualApplePods>.rssi
get() = rssiAverage ?: super.rssi
override val batteryCasePercent: Float?
get() = super.batteryCasePercent ?: cachedBatteryPercentage
@@ -9,6 +9,7 @@ import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
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.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.DualApplePods
@@ -37,7 +38,7 @@ data class FakeAirPodsPro2(
private val cachedCaseState: LidState? = null,
) : DualApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase {
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO2
override val model: PodModel = PodModel.FAKE_AIRPODS_PRO2
@get:DrawableRes
override val iconRes: Int
@@ -5,6 +5,7 @@ import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.ApplePods
import eu.darken.capod.pods.core.apple.ApplePodsFactory
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
@@ -25,7 +26,7 @@ data class UnknownAppleDevice(
private val rssiAverage: Int? = null,
) : ApplePods {
override val model: PodDevice.Model = PodDevice.Model.UNKNOWN
override val model: PodModel = PodModel.UNKNOWN
override fun getLabel(context: Context): String = context.getString(R.string.pods_unknown_label)
@@ -4,7 +4,7 @@ import android.bluetooth.BluetoothDevice
import android.util.Log
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.bluetooth.l2cap.L2capSocketFactory
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -36,7 +36,7 @@ class AapConnectionManager @Inject constructor(
suspend fun connect(
address: BluetoothAddress,
device: BluetoothDevice,
model: PodDevice.Model,
model: PodModel,
) {
if (connections.containsKey(address)) {
Log.d(TAG, "Already connected to $address")
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.protocol.aap
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import kotlin.reflect.KClass
/**
@@ -36,6 +36,6 @@ interface AapDeviceProfile {
fun encodeHandshake(): ByteArray
companion object {
fun forModel(model: PodDevice.Model): AapDeviceProfile = DefaultAapDeviceProfile()
fun forModel(model: PodModel): AapDeviceProfile = DefaultAapDeviceProfile()
}
}
@@ -4,8 +4,9 @@ import android.content.Context
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.profiles.core.DeviceProfile
import java.time.Instant
data class UnknownDevice(
@@ -19,7 +20,7 @@ data class UnknownDevice(
private val rssiAverage: Int? = null,
) : PodDevice {
override val model: PodDevice.Model = PodDevice.Model.UNKNOWN
override val model: PodModel = PodModel.UNKNOWN
override fun getLabel(context: Context): String = context.getString(R.string.pods_unknown_label)
@@ -1,7 +1,7 @@
package eu.darken.capod.profiles.core
import eu.darken.capod.common.serialization.ByteArrayBase64Serializer
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.protocol.IdentityResolvingKey
import eu.darken.capod.pods.core.apple.protocol.ProximityEncryptionKey
import kotlinx.parcelize.Parcelize
@@ -16,7 +16,7 @@ data class AppleDeviceProfile(
@SerialName("id") override val id: ProfileId = UUID.randomUUID().toString(),
@SerialName("label") override val label: String,
@SerialName("priority") override val priority: Int = 0,
@SerialName("model") override val model: PodDevice.Model = PodDevice.Model.UNKNOWN,
@SerialName("model") override val model: PodModel = PodModel.UNKNOWN,
@SerialName("minimumSignalQuality") override val minimumSignalQuality: Float = DeviceProfile.DEFAULT_MINIMUM_SIGNAL_QUALITY,
@SerialName("identityKey") @Serializable(with = ByteArrayBase64Serializer::class) val identityKey: IdentityResolvingKey? = null,
@SerialName("encryptionKey") @Serializable(with = ByteArrayBase64Serializer::class) val encryptionKey: ProximityEncryptionKey? = null,
@@ -1,7 +1,7 @@
package eu.darken.capod.profiles.core
import android.os.Parcelable
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import kotlinx.serialization.Serializable
@Serializable
@@ -9,7 +9,7 @@ sealed interface DeviceProfile : Parcelable {
val id: ProfileId
val label: String
val priority: Int
val model: PodDevice.Model
val model: PodModel
val minimumSignalQuality: Float?
val address: String?
@@ -34,14 +34,14 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.profiles.core.DeviceProfile
@Composable
@@ -129,9 +129,9 @@ private fun DeviceManagerScreenWithProfilesPreview() = PreviewWrapper {
DeviceManagerScreen(
state = DeviceManagerViewModel.State(
profiles = listOf(
MockPodDataProvider.profile("Work AirPods", PodDevice.Model.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodDevice.Model.AIRPODS_MAX),
MockPodDataProvider.profile("Gym Beats", PodDevice.Model.POWERBEATS_PRO),
MockPodDataProvider.profile("Work AirPods", PodModel.AIRPODS_PRO2),
MockPodDataProvider.profile("AirPods Max", PodModel.AIRPODS_MAX),
MockPodDataProvider.profile("Gym Beats", PodModel.POWERBEATS_PRO),
),
),
onBack = {},
@@ -24,13 +24,13 @@ import androidx.compose.material.icons.twotone.SettingsInputAntenna
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
@@ -56,15 +56,15 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.R
import eu.darken.capod.common.bluetooth.BluetoothDevice2
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import eu.darken.capod.common.error.ErrorEventHandler
import eu.darken.capod.common.navigation.NavigationEventHandler
import eu.darken.capod.common.toHex
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
@Composable
fun DeviceProfileCreationScreenHost(
@@ -144,7 +144,7 @@ fun DeviceProfileCreationScreen(
onSave: () -> Unit,
onDelete: () -> Unit,
onNameChange: (String) -> Unit,
onModelChange: (PodDevice.Model) -> Unit,
onModelChange: (PodModel) -> Unit,
onDeviceChange: (BluetoothDevice2?) -> Unit,
onIdentityKeyChange: (ByteArray?) -> Unit,
onEncryptionKeyChange: (ByteArray?) -> Unit,
@@ -294,12 +294,12 @@ private fun ProfileSectionCard(
private fun DeviceInfoCard(
name: String,
nameError: String?,
selectedModel: PodDevice.Model?,
availableModels: List<PodDevice.Model>,
selectedModel: PodModel?,
availableModels: List<PodModel>,
selectedDevice: BluetoothDevice2?,
bondedDevices: List<BluetoothDevice2>,
onNameChange: (String) -> Unit,
onModelChange: (PodDevice.Model) -> Unit,
onModelChange: (PodModel) -> Unit,
onDeviceChange: (BluetoothDevice2?) -> Unit,
) {
ProfileSectionCard(
@@ -656,7 +656,7 @@ private fun DeviceProfileCreationScreenNewPreview() = PreviewWrapper {
name = "",
nameError = null,
selectedModel = null,
availableModels = PodDevice.Model.entries.filter { it != PodDevice.Model.UNKNOWN },
availableModels = PodModel.entries.filter { it != PodModel.UNKNOWN },
identityKey = null,
encryptionKey = null,
selectedDevice = null,
@@ -685,8 +685,8 @@ private fun DeviceProfileCreationScreenEditPreview() = PreviewWrapper {
isEditMode = true,
name = "My AirPods Pro",
nameError = null,
selectedModel = PodDevice.Model.AIRPODS_PRO2,
availableModels = PodDevice.Model.entries.filter { it != PodDevice.Model.UNKNOWN },
selectedModel = PodModel.AIRPODS_PRO2,
availableModels = PodModel.entries.filter { it != PodModel.UNKNOWN },
identityKey = null,
encryptionKey = null,
selectedDevice = null,
@@ -715,8 +715,8 @@ private fun DeviceProfileCreationScreenWithKeysPreview() = PreviewWrapper {
isEditMode = true,
name = "My AirPods Pro",
nameError = null,
selectedModel = PodDevice.Model.AIRPODS_PRO2,
availableModels = PodDevice.Model.entries.filter { it != PodDevice.Model.UNKNOWN },
selectedModel = PodModel.AIRPODS_PRO2,
availableModels = PodModel.entries.filter { it != PodModel.UNKNOWN },
identityKey = byteArrayOf(
0xFE.toByte(), 0xD0.toByte(), 0x1C.toByte(), 0x54.toByte(),
0x11.toByte(), 0x81.toByte(), 0xBC.toByte(), 0xBC.toByte(),
@@ -14,7 +14,7 @@ import eu.darken.capod.common.flow.SingleEventFlow
import eu.darken.capod.common.fromHex
import eu.darken.capod.common.toHex
import eu.darken.capod.common.uix.ViewModel4
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.protocol.IdentityResolvingKey
import eu.darken.capod.pods.core.apple.protocol.ProximityEncryptionKey
import eu.darken.capod.profiles.core.AppleDeviceProfile
@@ -31,7 +31,7 @@ import javax.inject.Inject
private data class ProfileEditorState(
val name: String = "",
val selectedModel: PodDevice.Model? = null,
val selectedModel: PodModel? = null,
val identityKeyHex: String? = null,
val encryptionKeyHex: String? = null,
val selectedDeviceAddress: String? = null,
@@ -109,7 +109,7 @@ class DeviceProfileCreationViewModel @Inject constructor(
name = editorState.name,
nameError = nameErr,
selectedModel = editorState.selectedModel,
availableModels = PodDevice.Model.entries,
availableModels = PodModel.entries,
identityKey = editorState.identityKeyHex?.fromHex(),
encryptionKey = editorState.encryptionKeyHex?.fromHex(),
selectedDevice = selectedDevice,
@@ -123,8 +123,8 @@ class DeviceProfileCreationViewModel @Inject constructor(
val isEditMode: Boolean,
val name: String,
val nameError: String?,
val selectedModel: PodDevice.Model?,
val availableModels: List<PodDevice.Model>,
val selectedModel: PodModel?,
val availableModels: List<PodModel>,
val identityKey: IdentityResolvingKey?,
val encryptionKey: ProximityEncryptionKey?,
val selectedDevice: BluetoothDevice2?,
@@ -138,7 +138,7 @@ class DeviceProfileCreationViewModel @Inject constructor(
_nameError.value = if (name.isBlank()) context.getString(R.string.profiles_name_empty_error) else null
}
fun updateModel(model: PodDevice.Model) {
fun updateModel(model: PodModel) {
_currentState.value = _currentState.value.copy(selectedModel = model)
log(TAG) { "Selected model: $model" }
}
@@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.SignalCellularAlt
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
@@ -23,8 +25,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.SignalCellularAlt
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -36,7 +36,7 @@ import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.common.compose.preview.MockPodDataProvider
import eu.darken.capod.monitor.core.MonitoredDevice
import eu.darken.capod.monitor.core.getSignalQuality
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.formatBatteryPercent
import eu.darken.capod.pods.core.getBatteryDrawable
import eu.darken.capod.pods.core.toBatteryFloat
@@ -98,7 +98,7 @@ fun PopUpContent(
// Device-specific content
when {
device.hasDualPods -> DualPodContent(device)
device.model != PodDevice.Model.UNKNOWN -> SinglePodContent(device)
device.model != PodModel.UNKNOWN -> SinglePodContent(device)
}
Spacer(modifier = Modifier.height(20.dp))
@@ -5,7 +5,7 @@ import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode
import eu.darken.capod.common.theming.ThemeStyle
import eu.darken.capod.main.core.MonitorMode
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesContainer
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
@@ -145,21 +145,21 @@ class DataStoreMigrationCompatTest : BaseTest() {
@Test
fun `PodDevice Model encodes to canonical string`() {
json.encodeToString(serializer<PodDevice.Model>(), PodDevice.Model.AIRPODS_PRO) shouldBe "\"airpods.pro\""
json.encodeToString(serializer<PodModel>(), PodModel.AIRPODS_PRO) shouldBe "\"airpods.pro\""
}
@Test
fun `all PodDevice Model values round-trip`() {
for (model in PodDevice.Model.entries) {
val encoded = json.encodeToString(serializer<PodDevice.Model>(), model)
json.decodeFromString(serializer<PodDevice.Model>(), encoded) shouldBe model
for (model in PodModel.entries) {
val encoded = json.encodeToString(serializer<PodModel>(), model)
json.decodeFromString(serializer<PodModel>(), encoded) shouldBe model
}
}
@Test
fun `PodDevice Model legacy string decodes correctly`() {
val legacyOutput = "\"airpods.pro\""
json.decodeFromString(serializer<PodDevice.Model>(), legacyOutput) shouldBe PodDevice.Model.AIRPODS_PRO
json.decodeFromString(serializer<PodModel>(), legacyOutput) shouldBe PodModel.AIRPODS_PRO
}
@Test
@@ -186,7 +186,7 @@ class DataStoreMigrationCompatTest : BaseTest() {
val profile = result.profiles[0] as AppleDeviceProfile
profile.id shouldBe "test-uuid"
profile.label shouldBe "My AirPods Pro"
profile.model shouldBe PodDevice.Model.AIRPODS_PRO
profile.model shouldBe PodModel.AIRPODS_PRO
profile.minimumSignalQuality shouldBe 0.15f
profile.address shouldBe "AA:BB:CC:DD:EE:FF"
profile.identityKey shouldBe null
@@ -200,7 +200,7 @@ class DataStoreMigrationCompatTest : BaseTest() {
AppleDeviceProfile(
id = "round-trip-id",
label = "Test Profile",
model = PodDevice.Model.AIRPODS_GEN2,
model = PodModel.AIRPODS_GEN2,
address = "11:22:33:44:55:66",
)
)
@@ -212,7 +212,7 @@ class DataStoreMigrationCompatTest : BaseTest() {
val profile = decoded.profiles[0] as AppleDeviceProfile
profile.id shouldBe "round-trip-id"
profile.label shouldBe "Test Profile"
profile.model shouldBe PodDevice.Model.AIRPODS_GEN2
profile.model shouldBe PodModel.AIRPODS_GEN2
profile.address shouldBe "11:22:33:44:55:66"
}
@@ -8,14 +8,14 @@ import eu.darken.capod.common.theming.ThemeColor
import eu.darken.capod.common.theming.ThemeMode
import eu.darken.capod.common.theming.ThemeStyle
import eu.darken.capod.main.core.MonitorMode
import kotlinx.serialization.builtins.nullable
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.profiles.core.AppleDeviceProfile
import eu.darken.capod.profiles.core.DeviceProfile
import eu.darken.capod.profiles.core.DeviceProfilesContainer
import eu.darken.capod.reaction.core.autoconnect.AutoConnectCondition
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
@@ -23,7 +23,6 @@ import testhelpers.BaseTest
import testhelpers.coroutine.runTest2
import java.io.File
import java.time.Instant
import eu.darken.capod.common.datastore.value
class DataStoreValueSerializationTest : BaseTest() {
@@ -59,9 +58,9 @@ class DataStoreValueSerializationTest : BaseTest() {
@Test
fun `enum round-trip - PodDevice Model`() = runTest2 {
val ds = createDataStore()
val pref = ds.createValue("model", PodDevice.Model.UNKNOWN, json)
val pref = ds.createValue("model", PodModel.UNKNOWN, json)
PodDevice.Model.entries.forEach { model ->
PodModel.entries.forEach { model ->
pref.value(model)
pref.value() shouldBe model
}
@@ -75,7 +74,7 @@ class DataStoreValueSerializationTest : BaseTest() {
AppleDeviceProfile(
id = "test-id-1",
label = "My AirPods",
model = PodDevice.Model.AIRPODS_PRO,
model = PodModel.AIRPODS_PRO,
address = "AA:BB:CC:DD:EE:FF",
)
)
@@ -89,7 +88,7 @@ class DataStoreValueSerializationTest : BaseTest() {
val profile = result.profiles[0] as AppleDeviceProfile
profile.id shouldBe "test-id-1"
profile.label shouldBe "My AirPods"
profile.model shouldBe PodDevice.Model.AIRPODS_PRO
profile.model shouldBe PodModel.AIRPODS_PRO
profile.address shouldBe "AA:BB:CC:DD:EE:FF"
}
@@ -229,7 +228,7 @@ class DataStoreValueSerializationTest : BaseTest() {
val profile: DeviceProfile = AppleDeviceProfile(
id = "poly-test",
label = "Test Profile",
model = PodDevice.Model.AIRPODS_GEN2,
model = PodModel.AIRPODS_GEN2,
identityKey = byteArrayOf(0x01, 0x02),
encryptionKey = byteArrayOf(0x03, 0x04),
)
@@ -243,7 +242,7 @@ class DataStoreValueSerializationTest : BaseTest() {
val restored = result.profiles[0] as AppleDeviceProfile
restored.id shouldBe "poly-test"
restored.label shouldBe "Test Profile"
restored.model shouldBe PodDevice.Model.AIRPODS_GEN2
restored.model shouldBe PodModel.AIRPODS_GEN2
restored.identityKey!!.toList() shouldBe listOf<Byte>(0x01, 0x02)
restored.encryptionKey!!.toList() shouldBe listOf<Byte>(0x03, 0x04)
}
@@ -2,10 +2,10 @@ package eu.darken.capod.monitor.core
import eu.darken.capod.pods.core.HasCase
import eu.darken.capod.pods.core.HasChargeDetectionDual
import eu.darken.capod.pods.core.HasDualMicrophone
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
import eu.darken.capod.pods.core.apple.protocol.aap.AapPodState
@@ -23,7 +23,7 @@ import java.time.Instant
class MonitoredDeviceTest : BaseTest() {
private fun mockDualPod(
model: PodDevice.Model = PodDevice.Model.AIRPODS_PRO3,
model: PodModel = PodModel.AIRPODS_PRO3,
leftBattery: Float? = 0.8f,
rightBattery: Float? = 0.9f,
caseBattery: Float? = 0.5f,
@@ -47,7 +47,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `capabilities come from model features`() {
val device = MonitoredDevice(
ble = mockDualPod(model = PodDevice.Model.AIRPODS_PRO3),
ble = mockDualPod(model = PodModel.AIRPODS_PRO3),
aap = null,
)
device.hasDualPods shouldBe true
@@ -59,7 +59,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `Beats Solo 3 has no dual pods or case`() {
val device = MonitoredDevice(
ble = mockk(relaxed = true) { every { model } returns PodDevice.Model.BEATS_SOLO_3 },
ble = mockk(relaxed = true) { every { model } returns PodModel.BEATS_SOLO_3 },
aap = null,
)
device.hasDualPods shouldBe false
@@ -92,7 +92,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `null BLE gives UNKNOWN model`() {
val device = MonitoredDevice(ble = null, aap = null)
device.model shouldBe PodDevice.Model.UNKNOWN
device.model shouldBe PodModel.UNKNOWN
}
@Test
@@ -148,7 +148,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `charging properties delegate to BLE interfaces`() {
val mock = mockk<DualApplePods>(relaxed = true) {
every { model } returns PodDevice.Model.AIRPODS_PRO3
every { model } returns PodModel.AIRPODS_PRO3
every { (this@mockk as HasChargeDetectionDual).isLeftPodCharging } returns true
every { (this@mockk as HasChargeDetectionDual).isRightPodCharging } returns false
every { (this@mockk as HasCase).isCaseCharging } returns true
@@ -162,7 +162,7 @@ class MonitoredDeviceTest : BaseTest() {
@Test
fun `ear detection properties delegate to BLE interfaces`() {
val mock = mockk<DualApplePods>(relaxed = true) {
every { model } returns PodDevice.Model.AIRPODS_PRO3
every { model } returns PodModel.AIRPODS_PRO3
every { (this@mockk as HasEarDetectionDual).isLeftPodInEar } returns true
every { (this@mockk as HasEarDetectionDual).isRightPodInEar } returns false
every { (this@mockk as HasEarDetection).isBeingWorn } returns false
@@ -179,7 +179,7 @@ class MonitoredDeviceTest : BaseTest() {
fun `icon and label properties delegate to BLE`() {
val device = MonitoredDevice(
ble = mockk(relaxed = true) {
every { model } returns PodDevice.Model.AIRPODS_PRO3
every { model } returns PodModel.AIRPODS_PRO3
every { iconRes } returns 42
},
aap = null,
@@ -8,7 +8,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `AirPods Pro 3 has all features`() {
val f = PodDevice.Model.AIRPODS_PRO3.features
val f = PodModel.AIRPODS_PRO3.features
f.hasDualPods shouldBe true
f.hasCase shouldBe true
f.hasEarDetection shouldBe true
@@ -17,7 +17,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `AirPods Gen 1 has dual pods and case but no ear detection or ANC`() {
val f = PodDevice.Model.AIRPODS_GEN1.features
val f = PodModel.AIRPODS_GEN1.features
f.hasDualPods shouldBe true
f.hasCase shouldBe true
f.hasEarDetection shouldBe false
@@ -26,7 +26,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `AirPods Max is single device with ANC but no dual pods or case`() {
val f = PodDevice.Model.AIRPODS_MAX.features
val f = PodModel.AIRPODS_MAX.features
f.hasDualPods shouldBe false
f.hasCase shouldBe false
f.hasEarDetection shouldBe false
@@ -35,7 +35,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `Beats Solo 3 has no features`() {
val f = PodDevice.Model.BEATS_SOLO_3.features
val f = PodModel.BEATS_SOLO_3.features
f.hasDualPods shouldBe false
f.hasCase shouldBe false
f.hasEarDetection shouldBe false
@@ -44,7 +44,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `PowerBeats Pro has dual pods, case, ear detection but no ANC`() {
val f = PodDevice.Model.POWERBEATS_PRO.features
val f = PodModel.POWERBEATS_PRO.features
f.hasDualPods shouldBe true
f.hasCase shouldBe true
f.hasEarDetection shouldBe true
@@ -53,7 +53,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `UNKNOWN model has no features`() {
val f = PodDevice.Model.UNKNOWN.features
val f = PodModel.UNKNOWN.features
f.hasDualPods shouldBe false
f.hasCase shouldBe false
f.hasEarDetection shouldBe false
@@ -62,7 +62,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `all ANC-capable models also have ear detection or are headphones`() {
PodDevice.Model.entries
PodModel.entries
.filter { it.features.hasAncControl && it.features.hasDualPods }
.forEach { model ->
model.features.hasEarDetection shouldBe true
@@ -71,7 +71,7 @@ class ModelFeaturesTest : BaseTest() {
@Test
fun `all models with case also have dual pods`() {
PodDevice.Model.entries
PodModel.entries
.filter { it.features.hasCase }
.forEach { model ->
model.features.hasDualPods shouldBe true
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -39,7 +39,7 @@ class AirPodsGen1Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_GEN1
model shouldBe PodModel.AIRPODS_GEN1
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -40,7 +40,7 @@ class AirPodsGen2Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_GEN2
model shouldBe PodModel.AIRPODS_GEN2
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -40,7 +40,7 @@ class AirPodsGen3Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_GEN3
model shouldBe PodModel.AIRPODS_GEN3
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -40,7 +40,7 @@ class AirPodsGen4AncTest : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_GEN4_ANC
model shouldBe PodModel.AIRPODS_GEN4_ANC
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.DualApplePods
import eu.darken.capod.pods.core.apple.HasAppleColor
@@ -40,7 +40,7 @@ class AirPodsGen4Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_GEN4
model shouldBe PodModel.AIRPODS_GEN4
}
}
}
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.common.isBitSet
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -28,7 +28,7 @@ class AirPodsMaxTest : BaseAirPodsTest() {
isHeadsetBeingCharged shouldBe false
model shouldBe PodDevice.Model.AIRPODS_MAX
model shouldBe PodModel.AIRPODS_MAX
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -26,7 +26,7 @@ class AirPodsMaxUsbcTest : BaseAirPodsTest() {
isHeadsetBeingCharged shouldBe false
model shouldBe PodDevice.Model.AIRPODS_MAX_USBC
model shouldBe PodModel.AIRPODS_MAX_USBC
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -42,7 +42,7 @@ class AirPodsPro2Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO2
model shouldBe PodModel.AIRPODS_PRO2
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -42,7 +42,7 @@ class AirPodsPro2UsbcTest : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO2_USBC
model shouldBe PodModel.AIRPODS_PRO2_USBC
}
}
@@ -66,7 +66,7 @@ class AirPodsPro2UsbcTest : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO2_USBC
model shouldBe PodModel.AIRPODS_PRO2_USBC
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -45,7 +45,7 @@ class AirPodsPro3Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO3
model shouldBe PodModel.AIRPODS_PRO3
}
}
@@ -71,7 +71,7 @@ class AirPodsPro3Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO3
model shouldBe PodModel.AIRPODS_PRO3
}
}
@@ -88,7 +88,7 @@ class AirPodsPro3Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO3
model shouldBe PodModel.AIRPODS_PRO3
}
}
}
@@ -1,7 +1,7 @@
package eu.darken.capod.pods.core.apple.airpods
import eu.darken.capod.common.toHex
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import eu.darken.capod.profiles.core.AppleDeviceProfile
@@ -38,7 +38,7 @@ class AirPodsProTest : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.AIRPODS_PRO
model shouldBe PodModel.AIRPODS_PRO
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -37,7 +37,7 @@ class BeatsFitProTest : BaseAirPodsTest() {
batteryCasePercent shouldBe null
podStyle.identifier shouldBe HasAppleColor.DeviceColor.UNKNOWN.name
model shouldBe PodDevice.Model.BEATS_FIT_PRO
model shouldBe PodModel.BEATS_FIT_PRO
}
}
@@ -66,7 +66,7 @@ class BeatsFitProTest : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.2f
podStyle.identifier shouldBe HasAppleColor.DeviceColor.UNKNOWN.name
model shouldBe PodDevice.Model.BEATS_FIT_PRO
model shouldBe PodModel.BEATS_FIT_PRO
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -24,7 +24,7 @@ class BeatsFlexText : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodDevice.Model.BEATS_FLEX
model shouldBe PodModel.BEATS_FLEX
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -24,7 +24,7 @@ class BeatsSolo3Test : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodDevice.Model.BEATS_SOLO_3
model shouldBe PodModel.BEATS_SOLO_3
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -24,7 +24,7 @@ class BeatsStudio3Test : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodDevice.Model.BEATS_STUDIO_3
model shouldBe PodModel.BEATS_STUDIO_3
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -24,7 +24,7 @@ class BeatsXTest : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.5f
model shouldBe PodDevice.Model.BEATS_X
model shouldBe PodModel.BEATS_X
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -24,7 +24,7 @@ class PowerBeats3Test : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.4f
model shouldBe PodDevice.Model.POWERBEATS_3
model shouldBe PodModel.POWERBEATS_3
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
import io.kotest.matchers.shouldBe
@@ -24,7 +24,7 @@ class PowerBeats4Test : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.2f
model shouldBe PodDevice.Model.POWERBEATS_4
model shouldBe PodModel.POWERBEATS_4
state shouldBe HasStateDetectionAirPods.ConnectionState.MUSIC
}
@@ -45,7 +45,7 @@ class PowerBeats4Test : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.2f
model shouldBe PodDevice.Model.POWERBEATS_4
model shouldBe PodModel.POWERBEATS_4
state shouldBe HasStateDetectionAirPods.ConnectionState.IDLE
}
@@ -67,7 +67,7 @@ class PowerBeats4Test : BaseAirPodsTest() {
batteryHeadsetPercent shouldBe 0.2f
model shouldBe PodDevice.Model.POWERBEATS_4
model shouldBe PodModel.POWERBEATS_4
state shouldBe HasStateDetectionAirPods.ConnectionState.DISCONNECTED
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -35,7 +35,7 @@ class PowerBeatsPro2Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.RED.name
model shouldBe PodDevice.Model.POWERBEATS_PRO2
model shouldBe PodModel.POWERBEATS_PRO2
}
}
@@ -55,7 +55,7 @@ class PowerBeatsPro2Test : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.RED.name
model shouldBe PodDevice.Model.POWERBEATS_PRO2
model shouldBe PodModel.POWERBEATS_PRO2
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.beats
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import eu.darken.capod.pods.core.apple.HasAppleColor
import io.kotest.matchers.shouldBe
@@ -35,7 +35,7 @@ class PowerBeatsProTest : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.WHITE.name
model shouldBe PodDevice.Model.POWERBEATS_PRO
model shouldBe PodModel.POWERBEATS_PRO
}
}
@@ -66,7 +66,7 @@ class PowerBeatsProTest : BaseAirPodsTest() {
podStyle.identifier shouldBe HasAppleColor.DeviceColor.UNKNOWN.name
model shouldBe PodDevice.Model.POWERBEATS_PRO
model shouldBe PodModel.POWERBEATS_PRO
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.misc
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -34,7 +34,7 @@ class FakeAirPodsGen1Test : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.7f
model shouldBe PodDevice.Model.FAKE_AIRPODS_GEN2
model shouldBe PodModel.FAKE_AIRPODS_GEN2
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.misc
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -34,7 +34,7 @@ class FakeAirPodsGen2Test : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.7f
model shouldBe PodDevice.Model.FAKE_AIRPODS_GEN1
model shouldBe PodModel.FAKE_AIRPODS_GEN1
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.misc
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -33,7 +33,7 @@ class FakeAirPodsGen3Test : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.7f
model shouldBe PodDevice.Model.FAKE_AIRPODS_GEN3
model shouldBe PodModel.FAKE_AIRPODS_GEN3
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.misc
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -28,7 +28,7 @@ class FakeAirPodsPro2Test : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.8f
model shouldBe PodDevice.Model.FAKE_AIRPODS_PRO2
model shouldBe PodModel.FAKE_AIRPODS_PRO2
}
}
@@ -55,7 +55,7 @@ class FakeAirPodsPro2Test : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.2f
model shouldBe PodDevice.Model.FAKE_AIRPODS_PRO2
model shouldBe PodModel.FAKE_AIRPODS_PRO2
}
}
}
@@ -1,6 +1,6 @@
package eu.darken.capod.pods.core.apple.misc
import eu.darken.capod.pods.core.PodDevice
import eu.darken.capod.pods.core.PodModel
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.test.runTest
@@ -34,7 +34,7 @@ class FakeAirPodsProTest : BaseAirPodsTest() {
batteryCasePercent shouldBe 0.7f
model shouldBe PodDevice.Model.FAKE_AIRPODS_PRO
model shouldBe PodModel.FAKE_AIRPODS_PRO
}
}
}