mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
feat: Slim down overview cards and fix cached device icons
Remove timestamps, connection state, and conversation awareness from overview cards. Move signal badge inline with model subtitle. Relocate removed info to device settings screen info card. Fix cached devices showing Gen 1 icons by adding per-pod icon properties to PodModel. Add profile name subtitle to device settings toolbar. Simplify preview coverage to full/minimal/cached variants.
This commit is contained in:
@@ -8,6 +8,7 @@ import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.monitor.core.cache.CachedDeviceState
|
||||
import eu.darken.capod.pods.core.apple.PodModel
|
||||
import eu.darken.capod.pods.core.apple.aap.AapPodState
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import eu.darken.capod.pods.core.apple.ble.BlePodSnapshot
|
||||
import eu.darken.capod.pods.core.apple.ble.DualBlePodSnapshot
|
||||
import eu.darken.capod.pods.core.apple.ble.SingleBlePodSnapshot
|
||||
@@ -50,6 +51,7 @@ object MockPodDataProvider {
|
||||
leftPodIcon = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIcon = R.drawable.device_airpods_pro2_right,
|
||||
_caseIcon = R.drawable.device_airpods_pro2_case,
|
||||
_address = "AA:BB:CC:DD:EE:FF",
|
||||
)
|
||||
|
||||
fun airPodsProMixed(): DualBlePodSnapshot = MockDualBlePodSnapshot(
|
||||
@@ -127,6 +129,7 @@ object MockPodDataProvider {
|
||||
_isHeadsetBeingCharged = true,
|
||||
_isIRKMatch = true,
|
||||
_hasPrivatePayload = true,
|
||||
_address = "11:22:33:44:55:66",
|
||||
)
|
||||
|
||||
fun beatsSolo3(): SingleBlePodSnapshot = MockSingleBlePodSnapshot(
|
||||
@@ -161,6 +164,7 @@ object MockPodDataProvider {
|
||||
leftPodIcon = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIcon = R.drawable.device_airpods_pro2_right,
|
||||
_caseIcon = R.drawable.device_airpods_pro2_case,
|
||||
_address = "AA:BB:CC:DD:EE:FF",
|
||||
)
|
||||
|
||||
// --- PodDevice wrappers ---
|
||||
@@ -186,7 +190,20 @@ object MockPodDataProvider {
|
||||
fun dualPodMonitoredWithAap(): PodDevice = PodDevice(
|
||||
profileId = "preview-dual-aap",
|
||||
ble = airPodsProWithKeys(),
|
||||
aap = AapPodState(connectionState = AapPodState.ConnectionState.READY),
|
||||
aap = AapPodState(
|
||||
connectionState = AapPodState.ConnectionState.READY,
|
||||
settings = mapOf(
|
||||
AapSetting.AncMode::class to AapSetting.AncMode(
|
||||
current = AapSetting.AncMode.Value.ADAPTIVE,
|
||||
supported = listOf(
|
||||
AapSetting.AncMode.Value.OFF,
|
||||
AapSetting.AncMode.Value.ON,
|
||||
AapSetting.AncMode.Value.TRANSPARENCY,
|
||||
AapSetting.AncMode.Value.ADAPTIVE,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun singlePodMonitored(): PodDevice = PodDevice(
|
||||
@@ -195,6 +212,24 @@ object MockPodDataProvider {
|
||||
aap = null,
|
||||
)
|
||||
|
||||
fun singlePodMonitoredWithAap(): PodDevice = PodDevice(
|
||||
profileId = "preview-single-aap",
|
||||
ble = airPodsMaxCharging(),
|
||||
aap = AapPodState(
|
||||
connectionState = AapPodState.ConnectionState.READY,
|
||||
settings = mapOf(
|
||||
AapSetting.AncMode::class to AapSetting.AncMode(
|
||||
current = AapSetting.AncMode.Value.ON,
|
||||
supported = listOf(
|
||||
AapSetting.AncMode.Value.OFF,
|
||||
AapSetting.AncMode.Value.ON,
|
||||
AapSetting.AncMode.Value.TRANSPARENCY,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun unknownMonitored(): PodDevice = PodDevice(
|
||||
profileId = null,
|
||||
ble = unknownDevice(),
|
||||
@@ -269,6 +304,7 @@ private class MockDualBlePodSnapshot(
|
||||
override val leftPodIcon: Int = R.drawable.device_airpods_gen1_left,
|
||||
override val rightPodIcon: Int = R.drawable.device_airpods_gen1_right,
|
||||
private val _caseIcon: Int = R.drawable.device_airpods_gen1_case,
|
||||
private val _address: String? = null,
|
||||
rssi: Int = -50,
|
||||
) : DualBlePodSnapshot, HasCase, HasChargeDetectionDual, HasEarDetectionDual, HasDualMicrophone {
|
||||
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id()
|
||||
@@ -281,7 +317,7 @@ private class MockDualBlePodSnapshot(
|
||||
override val signalQuality: Float = 0.75f
|
||||
override val iconRes: Int = _model.iconRes
|
||||
override val meta: BlePodSnapshot.Meta = object : BlePodSnapshot.Meta {
|
||||
override val profile: DeviceProfile = AppleDeviceProfile(label = _label, model = _model)
|
||||
override val profile: DeviceProfile = AppleDeviceProfile(label = _label, model = _model, address = _address)
|
||||
}
|
||||
|
||||
override fun getLabel(context: Context): String = _model.label
|
||||
@@ -376,6 +412,7 @@ private class MockSingleAppleBlePodSnapshot(
|
||||
private val _isBeingWorn: Boolean = false,
|
||||
private val _isIRKMatch: Boolean = false,
|
||||
private val _hasPrivatePayload: Boolean = false,
|
||||
private val _address: String? = null,
|
||||
rssi: Int = -50,
|
||||
) : SingleBlePodSnapshot, ApplePods, HasChargeDetection, HasEarDetection {
|
||||
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id()
|
||||
@@ -389,7 +426,7 @@ private class MockSingleAppleBlePodSnapshot(
|
||||
override val iconRes: Int = _model.iconRes
|
||||
override val meta: ApplePods.AppleMeta = ApplePods.AppleMeta(
|
||||
isIRKMatch = _isIRKMatch,
|
||||
profile = AppleDeviceProfile(label = _label, model = _model),
|
||||
profile = AppleDeviceProfile(label = _label, model = _model, address = _address),
|
||||
)
|
||||
override val payload: ProximityPayload = ProximityPayload(
|
||||
public = ProximityPayload.Public(UByteArray(9)),
|
||||
@@ -424,6 +461,7 @@ private class MockDualAppleBlePodSnapshot(
|
||||
override val leftPodIcon: Int = R.drawable.device_airpods_gen1_left,
|
||||
override val rightPodIcon: Int = R.drawable.device_airpods_gen1_right,
|
||||
private val _caseIcon: Int = R.drawable.device_airpods_gen1_case,
|
||||
private val _address: String? = null,
|
||||
rssi: Int = -50,
|
||||
) : DualBlePodSnapshot, ApplePods, HasCase, HasChargeDetectionDual, HasEarDetectionDual, HasDualMicrophone {
|
||||
override val identifier: BlePodSnapshot.Id = BlePodSnapshot.Id()
|
||||
@@ -437,7 +475,7 @@ private class MockDualAppleBlePodSnapshot(
|
||||
override val iconRes: Int = _model.iconRes
|
||||
override val meta: ApplePods.AppleMeta = ApplePods.AppleMeta(
|
||||
isIRKMatch = _isIRKMatch,
|
||||
profile = AppleDeviceProfile(label = _label, model = _model),
|
||||
profile = AppleDeviceProfile(label = _label, model = _model, address = _address),
|
||||
)
|
||||
override val payload: ProximityPayload = ProximityPayload(
|
||||
public = ProximityPayload.Public(UByteArray(9)),
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.twotone.ArrowBack
|
||||
@@ -41,6 +40,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -48,15 +48,24 @@ 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 eu.darken.capod.common.compose.preview.MOCK_NOW
|
||||
import eu.darken.capod.common.error.ErrorEventHandler
|
||||
import eu.darken.capod.common.navigation.NavigationEventHandler
|
||||
import eu.darken.capod.common.settings.SettingsCategoryHeader
|
||||
import eu.darken.capod.common.settings.SettingsSliderItem
|
||||
import eu.darken.capod.common.settings.SettingsSwitchItem
|
||||
import eu.darken.capod.main.ui.overview.cards.AncModeSelector
|
||||
import eu.darken.capod.pods.core.apple.aap.AapPodState
|
||||
import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.monitor.core.firstSeenFormatted
|
||||
import eu.darken.capod.monitor.core.lastSeenFormatted
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapDeviceInfo
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.HasStateDetection
|
||||
import java.time.Duration
|
||||
|
||||
@Composable
|
||||
fun DeviceSettingsScreenHost(
|
||||
@@ -113,11 +122,23 @@ fun DeviceSettingsScreen(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.device_settings_title),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.device_settings_title),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
val profileName = device?.label
|
||||
if (profileName != null) {
|
||||
Text(
|
||||
text = profileName,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
@@ -134,10 +155,21 @@ fun DeviceSettingsScreen(
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
) {
|
||||
// Device Info
|
||||
val deviceInfo = device?.deviceInfo
|
||||
if (deviceInfo != null) {
|
||||
if (device != null) {
|
||||
item("device_info") {
|
||||
DeviceInfoCard(deviceInfo = deviceInfo)
|
||||
val context = LocalContext.current
|
||||
val stateDetection = device.ble as? HasStateDetection
|
||||
val seenFirst = device.seenFirstAt
|
||||
val seenLast = device.seenLastAt
|
||||
val firstSeen = if (seenFirst != null && seenLast != null && Duration.between(seenFirst, seenLast).toMinutes() >= 1) {
|
||||
device.firstSeenFormatted(state.now)
|
||||
} else null
|
||||
DeviceInfoCard(
|
||||
deviceInfo = device.deviceInfo,
|
||||
connectionStateLabel = stateDetection?.state?.getLabel(context),
|
||||
lastSeen = device.lastSeenFormatted(state.now),
|
||||
firstSeen = firstSeen,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +354,12 @@ fun DeviceSettingsScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeviceInfoCard(deviceInfo: AapDeviceInfo) {
|
||||
private fun DeviceInfoCard(
|
||||
deviceInfo: AapDeviceInfo?,
|
||||
connectionStateLabel: String?,
|
||||
lastSeen: String?,
|
||||
firstSeen: String?,
|
||||
) {
|
||||
ElevatedCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -330,22 +367,42 @@ private fun DeviceInfoCard(deviceInfo: AapDeviceInfo) {
|
||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 1.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
if (deviceInfo.name.isNotBlank()) {
|
||||
if (deviceInfo != null) {
|
||||
if (deviceInfo.name.isNotBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_name_label),
|
||||
value = deviceInfo.name,
|
||||
)
|
||||
}
|
||||
if (deviceInfo.serialNumber.isNotBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_serial_label),
|
||||
value = deviceInfo.serialNumber,
|
||||
)
|
||||
}
|
||||
if (deviceInfo.firmwareVersion.isNotBlank()) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_firmware_label),
|
||||
value = deviceInfo.firmwareVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (connectionStateLabel != null) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_name_label),
|
||||
value = deviceInfo.name,
|
||||
label = stringResource(R.string.device_settings_info_status_label),
|
||||
value = connectionStateLabel,
|
||||
)
|
||||
}
|
||||
if (deviceInfo.serialNumber.isNotBlank()) {
|
||||
if (lastSeen != null) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_serial_label),
|
||||
value = deviceInfo.serialNumber,
|
||||
label = stringResource(R.string.device_settings_info_last_seen_label),
|
||||
value = lastSeen,
|
||||
)
|
||||
}
|
||||
if (deviceInfo.firmwareVersion.isNotBlank()) {
|
||||
if (firstSeen != null) {
|
||||
InfoRow(
|
||||
label = stringResource(R.string.device_settings_info_firmware_label),
|
||||
value = deviceInfo.firmwareVersion,
|
||||
label = stringResource(R.string.device_settings_info_first_seen_label),
|
||||
value = firstSeen,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -354,16 +411,15 @@ private fun DeviceInfoCard(deviceInfo: AapDeviceInfo) {
|
||||
|
||||
@Composable
|
||||
private fun InfoRow(label: String, value: String) {
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
.padding(vertical = 2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.width(120.dp),
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
@@ -563,3 +619,82 @@ private fun CallControlOption(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DeviceSettingsFullPreview() = PreviewWrapper {
|
||||
DeviceSettingsScreen(
|
||||
state = DeviceSettingsViewModel.State(
|
||||
device = PodDevice(
|
||||
profileId = "preview",
|
||||
label = "My AirPods Pro",
|
||||
ble = MockPodDataProvider.airPodsProWithKeys(),
|
||||
aap = AapPodState(
|
||||
connectionState = AapPodState.ConnectionState.READY,
|
||||
deviceInfo = AapDeviceInfo(
|
||||
name = "AirPods Pro",
|
||||
modelNumber = "A2699",
|
||||
manufacturer = "Apple Inc.",
|
||||
serialNumber = "W5J7KV0N04",
|
||||
firmwareVersion = "7A305",
|
||||
),
|
||||
settings = mapOf(
|
||||
AapSetting.AncMode::class to AapSetting.AncMode(
|
||||
current = AapSetting.AncMode.Value.ADAPTIVE,
|
||||
supported = listOf(
|
||||
AapSetting.AncMode.Value.OFF,
|
||||
AapSetting.AncMode.Value.ON,
|
||||
AapSetting.AncMode.Value.TRANSPARENCY,
|
||||
AapSetting.AncMode.Value.ADAPTIVE,
|
||||
),
|
||||
),
|
||||
AapSetting.ConversationalAwareness::class to AapSetting.ConversationalAwareness(enabled = true),
|
||||
AapSetting.NcWithOneAirPod::class to AapSetting.NcWithOneAirPod(enabled = true),
|
||||
AapSetting.PersonalizedVolume::class to AapSetting.PersonalizedVolume(enabled = false),
|
||||
AapSetting.ToneVolume::class to AapSetting.ToneVolume(level = 75),
|
||||
AapSetting.AdaptiveAudioNoise::class to AapSetting.AdaptiveAudioNoise(level = 50),
|
||||
AapSetting.PressSpeed::class to AapSetting.PressSpeed(value = AapSetting.PressSpeed.Value.DEFAULT),
|
||||
AapSetting.PressHoldDuration::class to AapSetting.PressHoldDuration(value = AapSetting.PressHoldDuration.Value.DEFAULT),
|
||||
AapSetting.VolumeSwipe::class to AapSetting.VolumeSwipe(enabled = true),
|
||||
AapSetting.VolumeSwipeLength::class to AapSetting.VolumeSwipeLength(value = AapSetting.VolumeSwipeLength.Value.DEFAULT),
|
||||
AapSetting.EndCallMuteMic::class to AapSetting.EndCallMuteMic(
|
||||
muteMic = AapSetting.EndCallMuteMic.MuteMicMode.DOUBLE_PRESS,
|
||||
endCall = AapSetting.EndCallMuteMic.EndCallMode.SINGLE_PRESS,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
now = MOCK_NOW,
|
||||
),
|
||||
onNavigateUp = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DeviceSettingsInfoOnlyPreview() = PreviewWrapper {
|
||||
DeviceSettingsScreen(
|
||||
state = DeviceSettingsViewModel.State(
|
||||
device = PodDevice(
|
||||
profileId = "preview-info",
|
||||
label = "My AirPods Pro",
|
||||
ble = MockPodDataProvider.airPodsProMixed(),
|
||||
aap = null,
|
||||
),
|
||||
now = MOCK_NOW,
|
||||
),
|
||||
onNavigateUp = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DeviceSettingsCachedOnlyPreview() = PreviewWrapper {
|
||||
DeviceSettingsScreen(
|
||||
state = DeviceSettingsViewModel.State(
|
||||
device = MockPodDataProvider.dualPodCachedOnly(),
|
||||
now = MOCK_NOW,
|
||||
),
|
||||
onNavigateUp = {},
|
||||
)
|
||||
}
|
||||
|
||||
+18
-3
@@ -11,10 +11,14 @@ import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.aap.AapConnectionManager
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.isActive
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -33,15 +37,26 @@ class DeviceSettingsViewModel @Inject constructor(
|
||||
targetAddress.value = address
|
||||
}
|
||||
|
||||
private val updateTicker = channelFlow<Unit> {
|
||||
while (isActive) {
|
||||
trySend(Unit)
|
||||
delay(3000)
|
||||
}
|
||||
}
|
||||
|
||||
val state = targetAddress.flatMapLatest { address ->
|
||||
if (address == null) return@flatMapLatest flowOf(State(device = null))
|
||||
deviceMonitor.devices.map { devices ->
|
||||
State(device = devices.firstOrNull { it.address == address })
|
||||
combine(updateTicker, deviceMonitor.devices) { _, devices ->
|
||||
State(
|
||||
device = devices.firstOrNull { it.address == address },
|
||||
now = Instant.now(),
|
||||
)
|
||||
}
|
||||
}.asLiveState()
|
||||
|
||||
data class State(
|
||||
val device: PodDevice?,
|
||||
val now: Instant = Instant.now(),
|
||||
)
|
||||
|
||||
private fun send(command: AapCommand) {
|
||||
|
||||
@@ -130,7 +130,6 @@ fun OverviewScreenHost(vm: OverviewViewModel = hiltViewModel()) {
|
||||
onUpgrade = { vm.onUpgrade() },
|
||||
onToggleUnmatched = { vm.toggleUnmatchedDevices() },
|
||||
onAncModeChange = { device, mode -> vm.setAncMode(device, mode) },
|
||||
onConversationAwarenessChange = { device, enabled -> vm.setConversationalAwareness(device, enabled) },
|
||||
onDeviceSettings = { device -> vm.goToDeviceSettings(device) },
|
||||
)
|
||||
}
|
||||
@@ -144,7 +143,6 @@ fun OverviewScreen(
|
||||
onUpgrade: () -> Unit,
|
||||
onToggleUnmatched: () -> Unit,
|
||||
onAncModeChange: (PodDevice, AapSetting.AncMode.Value) -> Unit = { _, _ -> },
|
||||
onConversationAwarenessChange: (PodDevice, Boolean) -> Unit = { _, _ -> },
|
||||
onDeviceSettings: (PodDevice) -> Unit = {},
|
||||
) {
|
||||
Scaffold(
|
||||
@@ -235,7 +233,6 @@ fun OverviewScreen(
|
||||
showDebug = state.isDebugMode,
|
||||
now = state.now,
|
||||
onAncModeChange = { mode -> onAncModeChange(device, mode) },
|
||||
onConversationAwarenessChange = { enabled -> onConversationAwarenessChange(device, enabled) },
|
||||
onDeviceSettings = { onDeviceSettings(device) },
|
||||
)
|
||||
}
|
||||
@@ -278,7 +275,6 @@ fun OverviewScreen(
|
||||
showDebug = state.isDebugMode,
|
||||
now = state.now,
|
||||
onAncModeChange = { mode -> onAncModeChange(device, mode) },
|
||||
onConversationAwarenessChange = { enabled -> onConversationAwarenessChange(device, enabled) },
|
||||
onDeviceSettings = { onDeviceSettings(device) },
|
||||
)
|
||||
}
|
||||
@@ -295,14 +291,12 @@ private fun PodDeviceCard(
|
||||
showDebug: Boolean,
|
||||
now: Instant,
|
||||
onAncModeChange: (AapSetting.AncMode.Value) -> Unit,
|
||||
onConversationAwarenessChange: (Boolean) -> Unit = {},
|
||||
onDeviceSettings: (() -> Unit)? = null,
|
||||
) {
|
||||
when {
|
||||
device.hasDualPods -> DualPodsCard(
|
||||
device = device, showDebug = showDebug, now = now,
|
||||
onAncModeChange = onAncModeChange,
|
||||
onConversationAwarenessChange = onConversationAwarenessChange,
|
||||
onDeviceSettings = onDeviceSettings,
|
||||
)
|
||||
device.model != PodModel.UNKNOWN -> SinglePodsCard(
|
||||
@@ -310,7 +304,7 @@ private fun PodDeviceCard(
|
||||
onAncModeChange = onAncModeChange,
|
||||
onDeviceSettings = onDeviceSettings,
|
||||
)
|
||||
else -> UnknownPodDeviceCard(device = device, showDebug = showDebug, now = now)
|
||||
else -> UnknownPodDeviceCard(device = device, showDebug = showDebug)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,18 +182,6 @@ class OverviewViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setConversationalAwareness(device: PodDevice, enabled: Boolean) {
|
||||
val address = device.address ?: return
|
||||
launch {
|
||||
try {
|
||||
aapManager.sendCommand(address, AapCommand.SetConversationalAwareness(enabled))
|
||||
log(TAG) { "Conversation awareness set to $enabled for $address" }
|
||||
} catch (e: Exception) {
|
||||
log(TAG) { "Failed to set conversation awareness: ${e.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FREE_DEVICE_LIMIT = 1
|
||||
private val TAG = logTag("Overview", "VM")
|
||||
|
||||
@@ -47,18 +47,14 @@ import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||
import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.monitor.core.cachedBatteryFormatted
|
||||
import eu.darken.capod.monitor.core.firstSeenFormatted
|
||||
import eu.darken.capod.monitor.core.getSignalQuality
|
||||
import eu.darken.capod.monitor.core.lastSeenFormatted
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.HasPodStyle
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.HasStateDetection
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.DualApplePods.LidState
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.HasPodStyle
|
||||
import eu.darken.capod.pods.core.apple.ble.formatBatteryPercent
|
||||
import eu.darken.capod.pods.core.apple.ble.toBatteryFloat
|
||||
import eu.darken.capod.pods.core.apple.ble.toBatteryOrNull
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
@@ -67,7 +63,6 @@ fun DualPodsCard(
|
||||
showDebug: Boolean,
|
||||
now: Instant,
|
||||
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)? = null,
|
||||
onConversationAwarenessChange: ((Boolean) -> Unit)? = null,
|
||||
onDeviceSettings: (() -> Unit)? = null,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -80,9 +75,7 @@ fun DualPodsCard(
|
||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
// Header
|
||||
Row(
|
||||
@@ -92,10 +85,10 @@ fun DualPodsCard(
|
||||
Image(
|
||||
painter = painterResource(device.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp),
|
||||
modifier = Modifier.size(44.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
@@ -115,22 +108,27 @@ fun DualPodsCard(
|
||||
append(" [${dualApple.primaryPod.name}]")
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = deviceLabel,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = deviceLabel,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
SignalBadge(
|
||||
signalText = device.getSignalQuality(context),
|
||||
bleKeyState = device.bleKeyState,
|
||||
isAapConnected = device.isAapConnected,
|
||||
isLive = device.isLive,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SignalBadge(
|
||||
signalText = device.getSignalQuality(context),
|
||||
bleKeyState = device.bleKeyState,
|
||||
isAapConnected = device.isAapConnected,
|
||||
isLive = device.isLive,
|
||||
)
|
||||
|
||||
if (device.address != null && onDeviceSettings != null) {
|
||||
IconButton(onClick = onDeviceSettings) {
|
||||
Icon(
|
||||
@@ -142,31 +140,15 @@ fun DualPodsCard(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
// Timestamps
|
||||
Text(
|
||||
text = stringResource(R.string.last_seen_x, device.lastSeenFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
val seenFirst = device.seenFirstAt
|
||||
val seenLast = device.seenLastAt
|
||||
if (seenFirst != null && seenLast != null && Duration.between(seenFirst, seenLast).toMinutes() >= 1) {
|
||||
Text(
|
||||
text = stringResource(R.string.first_seen_x, device.firstSeenFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Circular battery gauges side by side
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
tonalElevation = 1.dp,
|
||||
tonalElevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
@@ -201,7 +183,7 @@ fun DualPodsCard(
|
||||
// Case row
|
||||
if (device.hasCase) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(vertical = 12.dp),
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
|
||||
)
|
||||
|
||||
@@ -217,24 +199,14 @@ fun DualPodsCard(
|
||||
text = stringResource(R.string.battery_cached_label, device.cachedBatteryFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
// Connection state
|
||||
val stateDetection = device.ble as? HasStateDetection
|
||||
if (stateDetection != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stateDetection.state.getLabel(context),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
)
|
||||
}
|
||||
|
||||
// ANC mode selector
|
||||
val ancMode = device.ancMode
|
||||
if (device.isAapConnected && device.hasAncControl && ancMode != null) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
AncModeSelector(
|
||||
currentMode = ancMode.current,
|
||||
supportedModes = ancMode.supported,
|
||||
@@ -243,16 +215,6 @@ fun DualPodsCard(
|
||||
)
|
||||
}
|
||||
|
||||
// Conversation awareness toggle
|
||||
val convAwareness = device.conversationalAwareness
|
||||
if (device.isAapConnected && device.model.features.hasConversationAwareness && convAwareness != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
ConversationAwarenessToggle(
|
||||
enabled = convAwareness.enabled,
|
||||
onToggle = { onConversationAwarenessChange?.invoke(it) },
|
||||
)
|
||||
}
|
||||
|
||||
// Debug info
|
||||
if (showDebug) {
|
||||
DebugSection(rawDataHex = device.rawDataHex)
|
||||
@@ -294,12 +256,12 @@ private fun PodGauge(
|
||||
// Ring with icon inside
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.size(80.dp),
|
||||
modifier = Modifier.size(68.dp),
|
||||
) {
|
||||
// Track ring
|
||||
CircularProgressIndicator(
|
||||
progress = { 1f },
|
||||
modifier = Modifier.size(80.dp),
|
||||
modifier = Modifier.size(68.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
strokeWidth = 6.dp,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
@@ -310,7 +272,7 @@ private fun PodGauge(
|
||||
if (clamped >= 0f) {
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = Modifier.size(80.dp),
|
||||
modifier = Modifier.size(68.dp),
|
||||
color = ringColor,
|
||||
strokeWidth = 6.dp,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
@@ -417,36 +379,31 @@ private fun CaseRow(
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DualPodsCardFullChargePreview() = PreviewWrapper {
|
||||
DualPodsCard(device = MockPodDataProvider.dualPodMonitored(), showDebug = false, now = Instant.now())
|
||||
private fun DualPodsCardFullPreview() = PreviewWrapper {
|
||||
DualPodsCard(
|
||||
device = MockPodDataProvider.dualPodMonitoredWithAap(),
|
||||
showDebug = false,
|
||||
now = Instant.now(),
|
||||
onDeviceSettings = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DualPodsCardMixedBatteryPreview() = PreviewWrapper {
|
||||
DualPodsCard(device = MockPodDataProvider.dualPodMonitoredMixed(), showDebug = false, now = Instant.now())
|
||||
private fun DualPodsCardMinimalPreview() = PreviewWrapper {
|
||||
DualPodsCard(
|
||||
device = MockPodDataProvider.dualPodMonitored(),
|
||||
showDebug = false,
|
||||
now = Instant.now(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DualPodsCardDebugPreview() = PreviewWrapper {
|
||||
DualPodsCard(device = MockPodDataProvider.dualPodMonitoredMixed(), showDebug = true, now = Instant.now())
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DualPodsCardWithKeysPreview() = PreviewWrapper {
|
||||
DualPodsCard(device = MockPodDataProvider.dualPodMonitoredWithKeys(), showDebug = false, now = Instant.now())
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DualPodsCardWithAapPreview() = PreviewWrapper {
|
||||
DualPodsCard(device = MockPodDataProvider.dualPodMonitoredWithAap(), showDebug = false, now = Instant.now())
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun DualPodsCardCachedOnlyPreview() = PreviewWrapper {
|
||||
DualPodsCard(device = MockPodDataProvider.dualPodCachedOnly(), showDebug = false, now = Instant.now())
|
||||
private fun DualPodsCardCachedPreview() = PreviewWrapper {
|
||||
DualPodsCard(
|
||||
device = MockPodDataProvider.dualPodCachedOnly(),
|
||||
showDebug = false,
|
||||
now = Instant.now(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,12 +47,9 @@ import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||
import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.monitor.core.cachedBatteryFormatted
|
||||
import eu.darken.capod.monitor.core.firstSeenFormatted
|
||||
import eu.darken.capod.monitor.core.getSignalQuality
|
||||
import eu.darken.capod.monitor.core.lastSeenFormatted
|
||||
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
|
||||
import eu.darken.capod.pods.core.apple.ble.formatBatteryPercent
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@@ -88,9 +85,7 @@ fun SinglePodsCard(
|
||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
// Header
|
||||
Row(
|
||||
@@ -100,10 +95,10 @@ fun SinglePodsCard(
|
||||
Image(
|
||||
painter = painterResource(device.iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(48.dp),
|
||||
modifier = Modifier.size(44.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
@@ -112,22 +107,27 @@ fun SinglePodsCard(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = device.getLabel(context),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = device.getLabel(context),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, fill = false),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
SignalBadge(
|
||||
signalText = device.getSignalQuality(context),
|
||||
bleKeyState = device.bleKeyState,
|
||||
isAapConnected = device.isAapConnected,
|
||||
isLive = device.isLive,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SignalBadge(
|
||||
signalText = device.getSignalQuality(context),
|
||||
bleKeyState = device.bleKeyState,
|
||||
isAapConnected = device.isAapConnected,
|
||||
isLive = device.isLive,
|
||||
)
|
||||
|
||||
if (device.address != null && onDeviceSettings != null) {
|
||||
IconButton(onClick = onDeviceSettings) {
|
||||
Icon(
|
||||
@@ -139,31 +139,15 @@ fun SinglePodsCard(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
// Timestamps
|
||||
Text(
|
||||
text = stringResource(R.string.last_seen_x, device.lastSeenFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
val seenFirst = device.seenFirstAt
|
||||
val seenLast = device.seenLastAt
|
||||
if (seenFirst != null && seenLast != null && Duration.between(seenFirst, seenLast).toMinutes() >= 1) {
|
||||
Text(
|
||||
text = stringResource(R.string.first_seen_x, device.firstSeenFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// Central gauge
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
tonalElevation = 1.dp,
|
||||
tonalElevation = 4.dp,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -173,12 +157,12 @@ fun SinglePodsCard(
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.size(100.dp),
|
||||
modifier = Modifier.size(88.dp),
|
||||
) {
|
||||
// Track ring
|
||||
CircularProgressIndicator(
|
||||
progress = { 1f },
|
||||
modifier = Modifier.size(100.dp),
|
||||
modifier = Modifier.size(88.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
strokeWidth = 8.dp,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
@@ -189,7 +173,7 @@ fun SinglePodsCard(
|
||||
if (clamped != null) {
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
modifier = Modifier.size(100.dp),
|
||||
modifier = Modifier.size(88.dp),
|
||||
color = ringColor,
|
||||
strokeWidth = 8.dp,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
@@ -200,7 +184,7 @@ fun SinglePodsCard(
|
||||
// Battery text inside ring
|
||||
Text(
|
||||
text = formatBatteryPercent(context, device.batteryHeadset),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = if (device.batteryHeadset != null) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
@@ -240,6 +224,7 @@ fun SinglePodsCard(
|
||||
text = stringResource(R.string.battery_cached_label, device.cachedBatteryFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.align(Alignment.End),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -265,18 +250,31 @@ fun SinglePodsCard(
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun SinglePodsCardPreview() = PreviewWrapper {
|
||||
SinglePodsCard(device = MockPodDataProvider.singlePodMonitored(), showDebug = false, now = Instant.now())
|
||||
private fun SinglePodsCardFullPreview() = PreviewWrapper {
|
||||
SinglePodsCard(
|
||||
device = MockPodDataProvider.singlePodMonitoredWithAap(),
|
||||
showDebug = false,
|
||||
now = Instant.now(),
|
||||
onDeviceSettings = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun SinglePodsCardDebugPreview() = PreviewWrapper {
|
||||
SinglePodsCard(device = MockPodDataProvider.singlePodMonitored(), showDebug = true, now = Instant.now())
|
||||
private fun SinglePodsCardMinimalPreview() = PreviewWrapper {
|
||||
SinglePodsCard(
|
||||
device = MockPodDataProvider.singlePodMonitored(),
|
||||
showDebug = false,
|
||||
now = Instant.now(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun SinglePodsCardCachedOnlyPreview() = PreviewWrapper {
|
||||
SinglePodsCard(device = MockPodDataProvider.singlePodCachedOnly(), showDebug = false, now = Instant.now())
|
||||
private fun SinglePodsCardCachedPreview() = PreviewWrapper {
|
||||
SinglePodsCard(
|
||||
device = MockPodDataProvider.singlePodCachedOnly(),
|
||||
showDebug = false,
|
||||
now = Instant.now(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,15 +26,12 @@ import eu.darken.capod.common.compose.PreviewWrapper
|
||||
import eu.darken.capod.common.compose.preview.MockPodDataProvider
|
||||
import eu.darken.capod.monitor.core.PodDevice
|
||||
import eu.darken.capod.monitor.core.getSignalQuality
|
||||
import eu.darken.capod.monitor.core.lastSeenFormatted
|
||||
import eu.darken.capod.pods.core.apple.ble.devices.ApplePods
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
fun UnknownPodDeviceCard(
|
||||
device: PodDevice,
|
||||
showDebug: Boolean,
|
||||
now: Instant,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -71,14 +68,6 @@ fun UnknownPodDeviceCard(
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.last_seen_x, device.lastSeenFormatted(now)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
@@ -111,11 +100,11 @@ fun UnknownPodDeviceCard(
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun UnknownPodDeviceCardPreview() = PreviewWrapper {
|
||||
UnknownPodDeviceCard(device = MockPodDataProvider.unknownMonitored(), showDebug = false, now = Instant.now())
|
||||
UnknownPodDeviceCard(device = MockPodDataProvider.unknownMonitored(), showDebug = false)
|
||||
}
|
||||
|
||||
@Preview2
|
||||
@Composable
|
||||
private fun UnknownPodDeviceCardDebugPreview() = PreviewWrapper {
|
||||
UnknownPodDeviceCard(device = MockPodDataProvider.unknownMonitored(), showDebug = true, now = Instant.now())
|
||||
UnknownPodDeviceCard(device = MockPodDataProvider.unknownMonitored(), showDebug = true)
|
||||
}
|
||||
|
||||
@@ -197,13 +197,19 @@ data class PodDevice(
|
||||
val iconRes: Int get() = ble?.iconRes ?: model.iconRes
|
||||
|
||||
val leftPodIcon: Int
|
||||
get() = (ble as? DualBlePodSnapshot)?.leftPodIcon ?: R.drawable.device_airpods_gen1_left
|
||||
get() = (ble as? DualBlePodSnapshot)?.leftPodIcon
|
||||
?: model.leftPodIconRes
|
||||
?: R.drawable.device_airpods_gen1_left
|
||||
|
||||
val rightPodIcon: Int
|
||||
get() = (ble as? DualBlePodSnapshot)?.rightPodIcon ?: R.drawable.device_airpods_gen1_right
|
||||
get() = (ble as? DualBlePodSnapshot)?.rightPodIcon
|
||||
?: model.rightPodIconRes
|
||||
?: R.drawable.device_airpods_gen1_right
|
||||
|
||||
val caseIcon: Int
|
||||
get() = (ble as? HasCase)?.caseIcon ?: R.drawable.device_airpods_gen1_case
|
||||
get() = (ble as? HasCase)?.caseIcon
|
||||
?: model.caseIconRes
|
||||
?: R.drawable.device_airpods_gen1_case
|
||||
|
||||
fun getLabel(context: Context): String = ble?.getLabel(context) ?: model.label
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ enum class PodModel(
|
||||
@DrawableRes val iconRes: Int = R.drawable.device_earbuds_generic_both,
|
||||
val features: Features = Features(),
|
||||
val modelNumbers: Set<String> = emptySet(),
|
||||
@DrawableRes val leftPodIconRes: Int? = null,
|
||||
@DrawableRes val rightPodIconRes: Int? = null,
|
||||
@DrawableRes val caseIconRes: Int? = null,
|
||||
) {
|
||||
@SerialName("airpods.gen1")
|
||||
AIRPODS_GEN1(
|
||||
@@ -21,6 +24,9 @@ enum class PodModel(
|
||||
hasCase = true,
|
||||
),
|
||||
modelNumbers = setOf("A1523", "A1722"), // L/R earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_gen1_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen1_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen1_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.gen2")
|
||||
@@ -32,6 +38,9 @@ enum class PodModel(
|
||||
hasCase = true,
|
||||
),
|
||||
modelNumbers = setOf("A2031", "A2032"), // L/R earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_gen1_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen1_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen1_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.gen3")
|
||||
@@ -43,6 +52,9 @@ enum class PodModel(
|
||||
hasCase = true,
|
||||
),
|
||||
modelNumbers = setOf("A2564", "A2565"), // L/R earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_gen3_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen3_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen3_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.gen4")
|
||||
@@ -55,6 +67,9 @@ enum class PodModel(
|
||||
hasEarDetection = true,
|
||||
),
|
||||
modelNumbers = setOf("A3050", "A3053", "A3054"), // earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_gen3_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen3_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen3_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.gen4.anc")
|
||||
@@ -78,6 +93,9 @@ enum class PodModel(
|
||||
needsInitExt = true,
|
||||
),
|
||||
modelNumbers = setOf("A3055", "A3056", "A3057"), // earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_gen4anc_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen4anc_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen4anc_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.pro")
|
||||
@@ -96,6 +114,9 @@ enum class PodModel(
|
||||
hasEndCallMuteMic = true,
|
||||
),
|
||||
modelNumbers = setOf("A2083", "A2084"), // L/R earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_pro2_right,
|
||||
caseIconRes = R.drawable.device_airpods_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.pro2")
|
||||
@@ -121,6 +142,9 @@ enum class PodModel(
|
||||
needsInitExt = true,
|
||||
),
|
||||
modelNumbers = setOf("A2698", "A2699", "A2931"), // earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_pro2_right,
|
||||
caseIconRes = R.drawable.device_airpods_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.pro2.usbc")
|
||||
@@ -146,6 +170,9 @@ enum class PodModel(
|
||||
needsInitExt = true,
|
||||
),
|
||||
modelNumbers = setOf("A3047", "A3048", "A3049"), // earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_pro2_right,
|
||||
caseIconRes = R.drawable.device_airpods_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.pro3")
|
||||
@@ -171,6 +198,9 @@ enum class PodModel(
|
||||
needsInitExt = true,
|
||||
),
|
||||
modelNumbers = setOf("A3063", "A3064", "A3065"), // earphones
|
||||
leftPodIconRes = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_pro2_right,
|
||||
caseIconRes = R.drawable.device_airpods_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("airpods.max")
|
||||
@@ -318,6 +348,9 @@ enum class PodModel(
|
||||
hasEarDetection = true,
|
||||
),
|
||||
modelNumbers = setOf("A2047", "A2048", "A2453", "A2454"), // L/R earbuds, 2019 + 2020 revisions
|
||||
leftPodIconRes = R.drawable.device_powerbeats_pro_left,
|
||||
rightPodIconRes = R.drawable.device_powerbeats_pro_right,
|
||||
caseIconRes = R.drawable.device_powerbeats_pro_case,
|
||||
),
|
||||
|
||||
@SerialName("beats.powerbeats.pro2")
|
||||
@@ -331,6 +364,9 @@ enum class PodModel(
|
||||
hasAncControl = true,
|
||||
),
|
||||
modelNumbers = setOf("A3157", "A3158", "A3159"), // L/R earbuds + case
|
||||
leftPodIconRes = R.drawable.device_powerbeats_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_powerbeats_pro2_right,
|
||||
caseIconRes = R.drawable.device_powerbeats_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("beats.fit.pro")
|
||||
@@ -344,6 +380,9 @@ enum class PodModel(
|
||||
hasAncControl = true,
|
||||
),
|
||||
modelNumbers = setOf("A2576", "A2577", "A2578"), // L/R earbuds + case
|
||||
leftPodIconRes = R.drawable.device_beats_fitpro_left,
|
||||
rightPodIconRes = R.drawable.device_beats_fitpro_right,
|
||||
caseIconRes = R.drawable.device_beats_fitpro_case,
|
||||
),
|
||||
|
||||
@SerialName("fakes.tws.i99999")
|
||||
@@ -354,6 +393,9 @@ enum class PodModel(
|
||||
hasDualPods = true,
|
||||
hasCase = true,
|
||||
),
|
||||
leftPodIconRes = R.drawable.device_airpods_gen1_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen1_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen1_case,
|
||||
),
|
||||
|
||||
@SerialName("fakes.generic.airpods.gen2")
|
||||
@@ -364,6 +406,9 @@ enum class PodModel(
|
||||
hasDualPods = true,
|
||||
hasCase = true,
|
||||
),
|
||||
leftPodIconRes = R.drawable.device_airpods_gen1_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen1_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen1_case,
|
||||
),
|
||||
|
||||
@SerialName("fakes.generic.airpods.gen3")
|
||||
@@ -374,6 +419,9 @@ enum class PodModel(
|
||||
hasDualPods = true,
|
||||
hasCase = true,
|
||||
),
|
||||
leftPodIconRes = R.drawable.device_airpods_gen3_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_gen3_right,
|
||||
caseIconRes = R.drawable.device_airpods_gen3_case,
|
||||
),
|
||||
|
||||
@SerialName("fakes.varunr.airpodspro")
|
||||
@@ -386,6 +434,9 @@ enum class PodModel(
|
||||
hasEarDetection = true,
|
||||
hasAncControl = true,
|
||||
),
|
||||
leftPodIconRes = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_pro2_right,
|
||||
caseIconRes = R.drawable.device_airpods_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("fakes.generic.airpods.pro2")
|
||||
@@ -398,6 +449,9 @@ enum class PodModel(
|
||||
hasEarDetection = true,
|
||||
hasAncControl = true,
|
||||
),
|
||||
leftPodIconRes = R.drawable.device_airpods_pro2_left,
|
||||
rightPodIconRes = R.drawable.device_airpods_pro2_right,
|
||||
caseIconRes = R.drawable.device_airpods_pro2_case,
|
||||
),
|
||||
|
||||
@SerialName("unknown")
|
||||
|
||||
@@ -425,6 +425,9 @@
|
||||
<string name="device_settings_info_name_label">Name</string>
|
||||
<string name="device_settings_info_serial_label">Serial Number</string>
|
||||
<string name="device_settings_info_firmware_label">Firmware</string>
|
||||
<string name="device_settings_info_status_label">Status</string>
|
||||
<string name="device_settings_info_last_seen_label">Last Seen</string>
|
||||
<string name="device_settings_info_first_seen_label">First Seen</string>
|
||||
<string name="device_settings_category_sound_label">Sound</string>
|
||||
<string name="device_settings_category_controls_label">Controls</string>
|
||||
<string name="device_settings_nc_one_airpod_label">Noise Cancellation with One AirPod</string>
|
||||
|
||||
Reference in New Issue
Block a user