mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 19:26:12 -04:00
feat(overview): Sort and collapse inactive device cards
Sort profiled devices into tiers (system-connected > nearby > cached), ordered by profile list within each tier. Collapse non-pinned cards to a compact battery tray with mini gauge rings matching the expanded card design. System-connected and top devices stay expanded; others can be tapped to toggle.
This commit is contained in:
@@ -171,6 +171,34 @@ inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST", "LongParameterList")
|
||||||
|
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
|
||||||
|
flow: Flow<T1>,
|
||||||
|
flow2: Flow<T2>,
|
||||||
|
flow3: Flow<T3>,
|
||||||
|
flow4: Flow<T4>,
|
||||||
|
flow5: Flow<T5>,
|
||||||
|
flow6: Flow<T6>,
|
||||||
|
flow7: Flow<T7>,
|
||||||
|
flow8: Flow<T8>,
|
||||||
|
flow9: Flow<T9>,
|
||||||
|
crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
|
||||||
|
): Flow<R> = kotlinx.coroutines.flow.combine(
|
||||||
|
flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8, flow9
|
||||||
|
) { args: Array<*> ->
|
||||||
|
transform(
|
||||||
|
args[0] as T1,
|
||||||
|
args[1] as T2,
|
||||||
|
args[2] as T3,
|
||||||
|
args[3] as T4,
|
||||||
|
args[4] as T5,
|
||||||
|
args[5] as T6,
|
||||||
|
args[6] as T7,
|
||||||
|
args[7] as T8,
|
||||||
|
args[8] as T9
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST", "LongParameterList")
|
@Suppress("UNCHECKED_CAST", "LongParameterList")
|
||||||
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R> combine(
|
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R> combine(
|
||||||
flow: Flow<T1>,
|
flow: Flow<T1>,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.twotone.Bluetooth
|
import androidx.compose.material.icons.twotone.Bluetooth
|
||||||
import androidx.compose.material.icons.twotone.BluetoothConnected
|
import androidx.compose.material.icons.twotone.BluetoothConnected
|
||||||
@@ -143,6 +144,9 @@ fun OverviewScreenHost(vm: OverviewViewModel = hiltViewModel()) {
|
|||||||
onAncModeChange = { device, mode -> vm.setAncMode(device, mode) },
|
onAncModeChange = { device, mode -> vm.setAncMode(device, mode) },
|
||||||
onDeviceSettings = { device -> vm.goToDeviceSettings(device) },
|
onDeviceSettings = { device -> vm.goToDeviceSettings(device) },
|
||||||
onEditProfile = { device -> vm.goToEditProfile(device) },
|
onEditProfile = { device -> vm.goToEditProfile(device) },
|
||||||
|
onToggleDeviceExpansion = { device ->
|
||||||
|
device.profileId?.let { vm.toggleDeviceExpansion(it) }
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +162,7 @@ fun OverviewScreen(
|
|||||||
onAncModeChange: (PodDevice, AapSetting.AncMode.Value) -> Unit = { _, _ -> },
|
onAncModeChange: (PodDevice, AapSetting.AncMode.Value) -> Unit = { _, _ -> },
|
||||||
onDeviceSettings: (PodDevice) -> Unit = {},
|
onDeviceSettings: (PodDevice) -> Unit = {},
|
||||||
onEditProfile: (PodDevice) -> Unit = {},
|
onEditProfile: (PodDevice) -> Unit = {},
|
||||||
|
onToggleDeviceExpansion: (PodDevice) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -263,15 +268,21 @@ fun OverviewScreen(
|
|||||||
|
|
||||||
// 4. Profiled device cards (limited to 1 for free users)
|
// 4. Profiled device cards (limited to 1 for free users)
|
||||||
if (!state.isScanBlocked && state.isBluetoothEnabled) {
|
if (!state.isScanBlocked && state.isBluetoothEnabled) {
|
||||||
items(
|
itemsIndexed(
|
||||||
items = state.visibleProfiledDevices,
|
items = state.visibleProfiledDevices,
|
||||||
key = { it.identifier?.toString() ?: it.hashCode() },
|
key = { _, device -> requireNotNull(device.profileId) },
|
||||||
) { device ->
|
) { index, device ->
|
||||||
|
val isCollapsed = !state.isExpanded(device, index)
|
||||||
|
val isToggleable = state.isToggleable(device, index)
|
||||||
PodDeviceCard(
|
PodDeviceCard(
|
||||||
device = device,
|
device = device,
|
||||||
isPro = state.upgradeInfo.isPro,
|
isPro = state.upgradeInfo.isPro,
|
||||||
showDebug = state.isDebugMode,
|
showDebug = state.isDebugMode,
|
||||||
now = state.now,
|
now = state.now,
|
||||||
|
isCollapsed = isCollapsed,
|
||||||
|
onToggleCollapse = if (isToggleable) {
|
||||||
|
{ onToggleDeviceExpansion(device) }
|
||||||
|
} else null,
|
||||||
onAncModeChange = { mode -> onAncModeChange(device, mode) },
|
onAncModeChange = { mode -> onAncModeChange(device, mode) },
|
||||||
onUpgrade = onUpgrade,
|
onUpgrade = onUpgrade,
|
||||||
onDeviceSettings = { onDeviceSettings(device) },
|
onDeviceSettings = { onDeviceSettings(device) },
|
||||||
@@ -335,6 +346,8 @@ private fun PodDeviceCard(
|
|||||||
isPro: Boolean,
|
isPro: Boolean,
|
||||||
showDebug: Boolean,
|
showDebug: Boolean,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
|
isCollapsed: Boolean = false,
|
||||||
|
onToggleCollapse: (() -> Unit)? = null,
|
||||||
onAncModeChange: (AapSetting.AncMode.Value) -> Unit,
|
onAncModeChange: (AapSetting.AncMode.Value) -> Unit,
|
||||||
onUpgrade: () -> Unit,
|
onUpgrade: () -> Unit,
|
||||||
onDeviceSettings: (() -> Unit)? = null,
|
onDeviceSettings: (() -> Unit)? = null,
|
||||||
@@ -343,6 +356,8 @@ private fun PodDeviceCard(
|
|||||||
when {
|
when {
|
||||||
device.hasDualPods -> DualPodsCard(
|
device.hasDualPods -> DualPodsCard(
|
||||||
device = device, isPro = isPro, showDebug = showDebug, now = now,
|
device = device, isPro = isPro, showDebug = showDebug, now = now,
|
||||||
|
isCollapsed = isCollapsed,
|
||||||
|
onToggleCollapse = onToggleCollapse,
|
||||||
onAncModeChange = onAncModeChange,
|
onAncModeChange = onAncModeChange,
|
||||||
onUpgrade = onUpgrade,
|
onUpgrade = onUpgrade,
|
||||||
onDeviceSettings = onDeviceSettings,
|
onDeviceSettings = onDeviceSettings,
|
||||||
@@ -350,6 +365,8 @@ private fun PodDeviceCard(
|
|||||||
)
|
)
|
||||||
device.model != PodModel.UNKNOWN -> SinglePodsCard(
|
device.model != PodModel.UNKNOWN -> SinglePodsCard(
|
||||||
device = device, isPro = isPro, showDebug = showDebug, now = now,
|
device = device, isPro = isPro, showDebug = showDebug, now = now,
|
||||||
|
isCollapsed = isCollapsed,
|
||||||
|
onToggleCollapse = onToggleCollapse,
|
||||||
onAncModeChange = onAncModeChange,
|
onAncModeChange = onAncModeChange,
|
||||||
onUpgrade = onUpgrade,
|
onUpgrade = onUpgrade,
|
||||||
onDeviceSettings = onDeviceSettings,
|
onDeviceSettings = onDeviceSettings,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class OverviewViewModel @Inject constructor(
|
|||||||
val requestPermissionEvent = SingleEventFlow<Permission>()
|
val requestPermissionEvent = SingleEventFlow<Permission>()
|
||||||
|
|
||||||
private val showUnmatchedDevices = MutableStateFlow(false)
|
private val showUnmatchedDevices = MutableStateFlow(false)
|
||||||
|
private val userExpansionOverrides = MutableStateFlow<Set<String>>(emptySet())
|
||||||
|
|
||||||
val workerAutolaunch = permissionTool.missingScanPermissions
|
val workerAutolaunch = permissionTool.missingScanPermissions
|
||||||
.onEach { permissions ->
|
.onEach { permissions ->
|
||||||
@@ -114,7 +115,12 @@ class OverviewViewModel @Inject constructor(
|
|||||||
profilesRepo.profiles,
|
profilesRepo.profiles,
|
||||||
upgradeRepo.upgradeInfo,
|
upgradeRepo.upgradeInfo,
|
||||||
showUnmatchedDevices,
|
showUnmatchedDevices,
|
||||||
) { _, permissions, devices, isDebugMode, isBluetoothEnabled, profiles, upgradeInfo, showUnmatched ->
|
userExpansionOverrides,
|
||||||
|
) { _, permissions, devices, isDebugMode, isBluetoothEnabled, profiles, upgradeInfo, showUnmatched, expandedIds ->
|
||||||
|
// Prune stale overrides (profiles that no longer exist)
|
||||||
|
val currentProfileIds = profiles.map { it.id }.toSet()
|
||||||
|
val prunedExpandedIds = expandedIds.filter { it in currentProfileIds }.toSet()
|
||||||
|
|
||||||
State(
|
State(
|
||||||
now = timeSource.now(),
|
now = timeSource.now(),
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
@@ -124,6 +130,7 @@ class OverviewViewModel @Inject constructor(
|
|||||||
profiles = profiles,
|
profiles = profiles,
|
||||||
upgradeInfo = upgradeInfo,
|
upgradeInfo = upgradeInfo,
|
||||||
showUnmatchedDevices = showUnmatched,
|
showUnmatchedDevices = showUnmatched,
|
||||||
|
userExpandedIds = prunedExpandedIds,
|
||||||
)
|
)
|
||||||
}.asLiveState()
|
}.asLiveState()
|
||||||
|
|
||||||
@@ -138,9 +145,22 @@ class OverviewViewModel @Inject constructor(
|
|||||||
val profiles: List<DeviceProfile>,
|
val profiles: List<DeviceProfile>,
|
||||||
val upgradeInfo: UpgradeRepo.Info,
|
val upgradeInfo: UpgradeRepo.Info,
|
||||||
val showUnmatchedDevices: Boolean,
|
val showUnmatchedDevices: Boolean,
|
||||||
|
val userExpandedIds: Set<String> = emptySet(),
|
||||||
) {
|
) {
|
||||||
val isScanBlocked: Boolean get() = permissions.any { it.isScanBlocking }
|
val isScanBlocked: Boolean get() = permissions.any { it.isScanBlocking }
|
||||||
val profiledDevices: List<PodDevice> get() = devices.filter { it.profileId != null }
|
|
||||||
|
/** Profile list order used as tiebreaker within each connection tier. */
|
||||||
|
private val profileOrder: Map<String, Int> by lazy {
|
||||||
|
profiles.mapIndexed { index, profile -> profile.id to index }.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
val profiledDevices: List<PodDevice> by lazy {
|
||||||
|
devices.filter { it.profileId != null }.sortedWith(
|
||||||
|
compareBy<PodDevice> { deviceTierRank(it) }
|
||||||
|
.thenBy { profileOrder[it.profileId] ?: Int.MAX_VALUE }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val visibleProfiledDevices: List<PodDevice>
|
val visibleProfiledDevices: List<PodDevice>
|
||||||
get() = if (upgradeInfo.isPro) profiledDevices else profiledDevices.take(FREE_DEVICE_LIMIT)
|
get() = if (upgradeInfo.isPro) profiledDevices else profiledDevices.take(FREE_DEVICE_LIMIT)
|
||||||
val hiddenProfiledDeviceCount: Int get() = profiledDevices.size - visibleProfiledDevices.size
|
val hiddenProfiledDeviceCount: Int get() = profiledDevices.size - visibleProfiledDevices.size
|
||||||
@@ -155,6 +175,15 @@ class OverviewViewModel @Inject constructor(
|
|||||||
profiledDevices.any { it.isLive } -> BluetoothIconState.NEARBY
|
profiledDevices.any { it.isLive } -> BluetoothIconState.NEARBY
|
||||||
else -> BluetoothIconState.HIDDEN
|
else -> BluetoothIconState.HIDDEN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isPinned(device: PodDevice, index: Int): Boolean =
|
||||||
|
device.isSystemConnected || index == 0
|
||||||
|
|
||||||
|
fun isExpanded(device: PodDevice, index: Int): Boolean =
|
||||||
|
isPinned(device, index) || device.profileId in userExpandedIds
|
||||||
|
|
||||||
|
fun isToggleable(device: PodDevice, index: Int): Boolean =
|
||||||
|
!isPinned(device, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPermissionResult() {
|
fun onPermissionResult() {
|
||||||
@@ -202,6 +231,13 @@ class OverviewViewModel @Inject constructor(
|
|||||||
showUnmatchedDevices.value = !showUnmatchedDevices.value
|
showUnmatchedDevices.value = !showUnmatchedDevices.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleDeviceExpansion(profileId: String) {
|
||||||
|
log(TAG, INFO) { "toggleDeviceExpansion(profileId=$profileId)" }
|
||||||
|
userExpansionOverrides.value = userExpansionOverrides.value.let { current ->
|
||||||
|
if (profileId in current) current - profileId else current + profileId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun requestPermission(permission: Permission) {
|
fun requestPermission(permission: Permission) {
|
||||||
log(TAG, INFO) { "requestPermission($permission)" }
|
log(TAG, INFO) { "requestPermission($permission)" }
|
||||||
requestPermissionEvent.tryEmit(permission)
|
requestPermissionEvent.tryEmit(permission)
|
||||||
@@ -222,5 +258,12 @@ class OverviewViewModel @Inject constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
private const val FREE_DEVICE_LIMIT = 1
|
private const val FREE_DEVICE_LIMIT = 1
|
||||||
private val TAG = logTag("Overview", "VM")
|
private val TAG = logTag("Overview", "VM")
|
||||||
|
|
||||||
|
/** Connection tier rank for sorting: lower = higher priority. */
|
||||||
|
internal fun deviceTierRank(device: PodDevice): Int = when {
|
||||||
|
device.isSystemConnected -> 0
|
||||||
|
device.isLive -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package eu.darken.capod.main.ui.overview.cards
|
package eu.darken.capod.main.ui.overview.cards
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -46,6 +49,7 @@ import eu.darken.capod.R
|
|||||||
import eu.darken.capod.monitor.core.visibleAncModes
|
import eu.darken.capod.monitor.core.visibleAncModes
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.AncModeSelector
|
import eu.darken.capod.main.ui.overview.cards.components.AncModeSelector
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.BatteryCapsule
|
import eu.darken.capod.main.ui.overview.cards.components.BatteryCapsule
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.components.CompactBatterySummary
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.DebugSection
|
import eu.darken.capod.main.ui.overview.cards.components.DebugSection
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.DeviceConnectionBadge
|
import eu.darken.capod.main.ui.overview.cards.components.DeviceConnectionBadge
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.SignalIndicator
|
import eu.darken.capod.main.ui.overview.cards.components.SignalIndicator
|
||||||
@@ -72,6 +76,8 @@ fun DualPodsCard(
|
|||||||
isPro: Boolean = true,
|
isPro: Boolean = true,
|
||||||
showDebug: Boolean,
|
showDebug: Boolean,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
|
isCollapsed: Boolean = false,
|
||||||
|
onToggleCollapse: (() -> Unit)? = null,
|
||||||
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)? = null,
|
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)? = null,
|
||||||
onUpgrade: (() -> Unit)? = null,
|
onUpgrade: (() -> Unit)? = null,
|
||||||
onDeviceSettings: (() -> Unit)? = null,
|
onDeviceSettings: (() -> Unit)? = null,
|
||||||
@@ -87,12 +93,22 @@ fun DualPodsCard(
|
|||||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
|
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.animateContentSize(),
|
||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.then(
|
||||||
|
if (onToggleCollapse != null) {
|
||||||
|
Modifier.clickable(onClick = onToggleCollapse)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(device.iconRes),
|
painter = painterResource(device.iconRes),
|
||||||
@@ -170,89 +186,108 @@ fun DualPodsCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
if (isCollapsed) {
|
||||||
|
CompactBatterySummary(device = device)
|
||||||
// Circular battery gauges side by side
|
} else {
|
||||||
Surface(
|
DualPodsCardExpanded(
|
||||||
modifier = Modifier
|
device = device,
|
||||||
.fillMaxWidth()
|
showDebug = showDebug,
|
||||||
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
now = now,
|
||||||
shape = RoundedCornerShape(12.dp),
|
onAncModeChange = onAncModeChange,
|
||||||
tonalElevation = 4.dp,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
) {
|
|
||||||
PodGauge(
|
|
||||||
iconRes = device.leftPodIcon,
|
|
||||||
batteryPercent = device.batteryLeft.toBatteryFloat(),
|
|
||||||
isCharging = device.isLeftPodCharging ?: false,
|
|
||||||
isInEar = device.isLeftInEar ?: false,
|
|
||||||
showEarDetection = device.hasEarDetection && device.hasDualPods,
|
|
||||||
isMicrophone = device.isLeftPodMicrophone ?: false,
|
|
||||||
showMicrophone = device.hasDualMicrophone,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
|
|
||||||
PodGauge(
|
|
||||||
iconRes = device.rightPodIcon,
|
|
||||||
batteryPercent = device.batteryRight.toBatteryFloat(),
|
|
||||||
isCharging = device.isRightPodCharging ?: false,
|
|
||||||
isInEar = device.isRightInEar ?: false,
|
|
||||||
showEarDetection = device.hasEarDetection && device.hasDualPods,
|
|
||||||
isMicrophone = device.isRightPodMicrophone ?: false,
|
|
||||||
showMicrophone = device.hasDualMicrophone,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case row
|
|
||||||
if (device.hasCase) {
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp),
|
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
|
|
||||||
)
|
|
||||||
|
|
||||||
CaseRow(device = device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cached battery indicator
|
|
||||||
if (device.isBatteryCached && !device.isLive) {
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.battery_cached_label, device.cachedBatteryFormatted(now)),
|
|
||||||
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(8.dp))
|
|
||||||
AncModeSelector(
|
|
||||||
currentMode = ancMode.current,
|
|
||||||
supportedModes = device.visibleAncModes,
|
|
||||||
onModeSelected = { onAncModeChange?.invoke(it) },
|
|
||||||
pendingMode = device.pendingAncMode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug info
|
|
||||||
if (showDebug) {
|
|
||||||
DebugSection(rawDataHex = device.rawDataHex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.DualPodsCardExpanded(
|
||||||
|
device: PodDevice,
|
||||||
|
showDebug: Boolean,
|
||||||
|
now: Instant,
|
||||||
|
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)?,
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Circular battery gauges side by side
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
tonalElevation = 4.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
PodGauge(
|
||||||
|
iconRes = device.leftPodIcon,
|
||||||
|
batteryPercent = device.batteryLeft.toBatteryFloat(),
|
||||||
|
isCharging = device.isLeftPodCharging ?: false,
|
||||||
|
isInEar = device.isLeftInEar ?: false,
|
||||||
|
showEarDetection = device.hasEarDetection && device.hasDualPods,
|
||||||
|
isMicrophone = device.isLeftPodMicrophone ?: false,
|
||||||
|
showMicrophone = device.hasDualMicrophone,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
|
||||||
|
PodGauge(
|
||||||
|
iconRes = device.rightPodIcon,
|
||||||
|
batteryPercent = device.batteryRight.toBatteryFloat(),
|
||||||
|
isCharging = device.isRightPodCharging ?: false,
|
||||||
|
isInEar = device.isRightInEar ?: false,
|
||||||
|
showEarDetection = device.hasEarDetection && device.hasDualPods,
|
||||||
|
isMicrophone = device.isRightPodMicrophone ?: false,
|
||||||
|
showMicrophone = device.hasDualMicrophone,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case row
|
||||||
|
if (device.hasCase) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
|
||||||
|
)
|
||||||
|
|
||||||
|
CaseRow(device = device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached battery indicator
|
||||||
|
if (device.isBatteryCached && !device.isLive) {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.battery_cached_label, device.cachedBatteryFormatted(now)),
|
||||||
|
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(8.dp))
|
||||||
|
AncModeSelector(
|
||||||
|
currentMode = ancMode.current,
|
||||||
|
supportedModes = device.visibleAncModes,
|
||||||
|
onModeSelected = { onAncModeChange?.invoke(it) },
|
||||||
|
pendingMode = device.pendingAncMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug info
|
||||||
|
if (showDebug) {
|
||||||
|
DebugSection(rawDataHex = device.rawDataHex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PodGauge(
|
private fun PodGauge(
|
||||||
iconRes: Int,
|
iconRes: Int,
|
||||||
@@ -439,6 +474,18 @@ private fun DualPodsCardCachedPreview() = PreviewWrapper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview2
|
||||||
|
@Composable
|
||||||
|
private fun DualPodsCardCollapsedPreview() = PreviewWrapper {
|
||||||
|
DualPodsCard(
|
||||||
|
device = MockPodDataProvider.dualPodMonitored(),
|
||||||
|
showDebug = false,
|
||||||
|
now = SystemTimeSource.now(),
|
||||||
|
isCollapsed = true,
|
||||||
|
onToggleCollapse = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Preview2
|
@Preview2
|
||||||
@Composable
|
@Composable
|
||||||
private fun DualPodsCardMissingAddressPreview() = PreviewWrapper {
|
private fun DualPodsCardMissingAddressPreview() = PreviewWrapper {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import androidx.compose.animation.core.FastOutSlowInEasing
|
|||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -45,6 +47,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.monitor.core.visibleAncModes
|
import eu.darken.capod.monitor.core.visibleAncModes
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.AncModeSelector
|
import eu.darken.capod.main.ui.overview.cards.components.AncModeSelector
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.components.CompactBatterySummary
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.DebugSection
|
import eu.darken.capod.main.ui.overview.cards.components.DebugSection
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.DeviceConnectionBadge
|
import eu.darken.capod.main.ui.overview.cards.components.DeviceConnectionBadge
|
||||||
import eu.darken.capod.main.ui.overview.cards.components.SignalIndicator
|
import eu.darken.capod.main.ui.overview.cards.components.SignalIndicator
|
||||||
@@ -66,6 +69,8 @@ fun SinglePodsCard(
|
|||||||
isPro: Boolean = true,
|
isPro: Boolean = true,
|
||||||
showDebug: Boolean,
|
showDebug: Boolean,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
|
isCollapsed: Boolean = false,
|
||||||
|
onToggleCollapse: (() -> Unit)? = null,
|
||||||
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)? = null,
|
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)? = null,
|
||||||
onUpgrade: (() -> Unit)? = null,
|
onUpgrade: (() -> Unit)? = null,
|
||||||
onDeviceSettings: (() -> Unit)? = null,
|
onDeviceSettings: (() -> Unit)? = null,
|
||||||
@@ -73,20 +78,6 @@ fun SinglePodsCard(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val clamped = device.batteryHeadset?.coerceIn(0f, 1f)
|
|
||||||
val animatedProgress by animateFloatAsState(
|
|
||||||
targetValue = clamped ?: 0f,
|
|
||||||
animationSpec = tween(600, easing = FastOutSlowInEasing),
|
|
||||||
label = "gaugeProgress",
|
|
||||||
)
|
|
||||||
|
|
||||||
val ringColor = when {
|
|
||||||
clamped == null -> MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
clamped > 0.30f -> MaterialTheme.colorScheme.primary
|
|
||||||
clamped >= 0.15f -> MaterialTheme.colorScheme.tertiary
|
|
||||||
else -> MaterialTheme.colorScheme.error
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -95,12 +86,22 @@ fun SinglePodsCard(
|
|||||||
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
|
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 2.dp),
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.animateContentSize(),
|
||||||
) {
|
) {
|
||||||
// Header
|
// Header
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.then(
|
||||||
|
if (onToggleCollapse != null) {
|
||||||
|
Modifier.clickable(onClick = onToggleCollapse)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(device.iconRes),
|
painter = painterResource(device.iconRes),
|
||||||
@@ -167,115 +168,150 @@ fun SinglePodsCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
if (isCollapsed) {
|
||||||
|
CompactBatterySummary(device = device)
|
||||||
// Central gauge
|
} else {
|
||||||
Surface(
|
SinglePodsCardExpanded(
|
||||||
modifier = Modifier
|
device = device,
|
||||||
.fillMaxWidth()
|
showDebug = showDebug,
|
||||||
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
now = now,
|
||||||
shape = RoundedCornerShape(12.dp),
|
onAncModeChange = onAncModeChange,
|
||||||
tonalElevation = 4.dp,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
modifier = Modifier.size(88.dp),
|
|
||||||
) {
|
|
||||||
// Track ring
|
|
||||||
CircularProgressIndicator(
|
|
||||||
progress = { 1f },
|
|
||||||
modifier = Modifier.size(88.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
strokeWidth = 8.dp,
|
|
||||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
strokeCap = StrokeCap.Round,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Progress ring
|
|
||||||
if (clamped != null) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
progress = { animatedProgress },
|
|
||||||
modifier = Modifier.size(88.dp),
|
|
||||||
color = ringColor,
|
|
||||||
strokeWidth = 8.dp,
|
|
||||||
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
strokeCap = StrokeCap.Round,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Battery text inside ring
|
|
||||||
Text(
|
|
||||||
text = formatBatteryPercent(context, device.batteryHeadset),
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
color = if (device.batteryHeadset != null) {
|
|
||||||
MaterialTheme.colorScheme.onSurface
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// Status chips
|
|
||||||
FlowRow(
|
|
||||||
modifier = Modifier.animateContentSize(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
) {
|
|
||||||
if (device.isHeadsetBeingCharged == true) {
|
|
||||||
StatusChip(
|
|
||||||
icon = Icons.TwoTone.BatteryChargingFull,
|
|
||||||
label = stringResource(R.string.pods_charging_label),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (device.isBeingWorn == true) {
|
|
||||||
StatusChip(
|
|
||||||
icon = Icons.TwoTone.Hearing,
|
|
||||||
label = stringResource(R.string.pods_inear_label),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cached battery indicator
|
|
||||||
if (device.isBatteryCached && !device.isLive) {
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.battery_cached_label, device.cachedBatteryFormatted(now)),
|
|
||||||
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))
|
|
||||||
AncModeSelector(
|
|
||||||
currentMode = ancMode.current,
|
|
||||||
supportedModes = device.visibleAncModes,
|
|
||||||
onModeSelected = { onAncModeChange?.invoke(it) },
|
|
||||||
pendingMode = device.pendingAncMode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug info
|
|
||||||
if (showDebug) {
|
|
||||||
DebugSection(rawDataHex = device.rawDataHex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ColumnScope.SinglePodsCardExpanded(
|
||||||
|
device: PodDevice,
|
||||||
|
showDebug: Boolean,
|
||||||
|
now: Instant,
|
||||||
|
onAncModeChange: ((AapSetting.AncMode.Value) -> Unit)?,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val clamped = device.batteryHeadset?.coerceIn(0f, 1f)
|
||||||
|
val animatedProgress by animateFloatAsState(
|
||||||
|
targetValue = clamped ?: 0f,
|
||||||
|
animationSpec = tween(600, easing = FastOutSlowInEasing),
|
||||||
|
label = "gaugeProgress",
|
||||||
|
)
|
||||||
|
|
||||||
|
val ringColor = when {
|
||||||
|
clamped == null -> MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
clamped > 0.30f -> MaterialTheme.colorScheme.primary
|
||||||
|
clamped >= 0.15f -> MaterialTheme.colorScheme.tertiary
|
||||||
|
else -> MaterialTheme.colorScheme.error
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Central gauge
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.then(if (!device.isLive) Modifier.alpha(0.7f) else Modifier),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
tonalElevation = 4.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
modifier = Modifier.size(88.dp),
|
||||||
|
) {
|
||||||
|
// Track ring
|
||||||
|
CircularProgressIndicator(
|
||||||
|
progress = { 1f },
|
||||||
|
modifier = Modifier.size(88.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
strokeWidth = 8.dp,
|
||||||
|
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Progress ring
|
||||||
|
if (clamped != null) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
progress = { animatedProgress },
|
||||||
|
modifier = Modifier.size(88.dp),
|
||||||
|
color = ringColor,
|
||||||
|
strokeWidth = 8.dp,
|
||||||
|
trackColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
strokeCap = StrokeCap.Round,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery text inside ring
|
||||||
|
Text(
|
||||||
|
text = formatBatteryPercent(context, device.batteryHeadset),
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = if (device.batteryHeadset != null) {
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Status chips
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.animateContentSize(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
if (device.isHeadsetBeingCharged == true) {
|
||||||
|
StatusChip(
|
||||||
|
icon = Icons.TwoTone.BatteryChargingFull,
|
||||||
|
label = stringResource(R.string.pods_charging_label),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (device.isBeingWorn == true) {
|
||||||
|
StatusChip(
|
||||||
|
icon = Icons.TwoTone.Hearing,
|
||||||
|
label = stringResource(R.string.pods_inear_label),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached battery indicator
|
||||||
|
if (device.isBatteryCached && !device.isLive) {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.battery_cached_label, device.cachedBatteryFormatted(now)),
|
||||||
|
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))
|
||||||
|
AncModeSelector(
|
||||||
|
currentMode = ancMode.current,
|
||||||
|
supportedModes = device.visibleAncModes,
|
||||||
|
onModeSelected = { onAncModeChange?.invoke(it) },
|
||||||
|
pendingMode = device.pendingAncMode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug info
|
||||||
|
if (showDebug) {
|
||||||
|
DebugSection(rawDataHex = device.rawDataHex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview2
|
@Preview2
|
||||||
@Composable
|
@Composable
|
||||||
private fun SinglePodsCardFullPreview() = PreviewWrapper {
|
private fun SinglePodsCardFullPreview() = PreviewWrapper {
|
||||||
@@ -307,6 +343,18 @@ private fun SinglePodsCardCachedPreview() = PreviewWrapper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview2
|
||||||
|
@Composable
|
||||||
|
private fun SinglePodsCardCollapsedPreview() = PreviewWrapper {
|
||||||
|
SinglePodsCard(
|
||||||
|
device = MockPodDataProvider.singlePodMonitored(),
|
||||||
|
showDebug = false,
|
||||||
|
now = SystemTimeSource.now(),
|
||||||
|
isCollapsed = true,
|
||||||
|
onToggleCollapse = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Preview2
|
@Preview2
|
||||||
@Composable
|
@Composable
|
||||||
private fun SinglePodsCardMissingAddressPreview() = PreviewWrapper {
|
private fun SinglePodsCardMissingAddressPreview() = PreviewWrapper {
|
||||||
|
|||||||
@@ -259,7 +259,10 @@
|
|||||||
|
|
||||||
<string name="pods_dual_left_label">Left pod</string>
|
<string name="pods_dual_left_label">Left pod</string>
|
||||||
<string name="pods_dual_right_label">Right pod</string>
|
<string name="pods_dual_right_label">Right pod</string>
|
||||||
|
<string name="pods_dual_left_short_label">L</string>
|
||||||
|
<string name="pods_dual_right_short_label">R</string>
|
||||||
<string name="pods_case_label">Case</string>
|
<string name="pods_case_label">Case</string>
|
||||||
|
<string name="battery_unavailable_label">Battery unavailable</string>
|
||||||
<string name="pods_case_status_open_label">Open</string>
|
<string name="pods_case_status_open_label">Open</string>
|
||||||
<string name="pods_case_status_closed_label">Closed</string>
|
<string name="pods_case_status_closed_label">Closed</string>
|
||||||
<string name="pods_connection_state_disconnected_label">Not connected to a device</string>
|
<string name="pods_connection_state_disconnected_label">Not connected to a device</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user