diff --git a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt index 55d048ab..d59706db 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/DeviceSettingsScreen.kt @@ -47,6 +47,7 @@ import eu.darken.capod.main.ui.devicesettings.cards.BatteryCard import eu.darken.capod.main.ui.devicesettings.cards.BatteryHealthTexts import eu.darken.capod.main.ui.devicesettings.cards.BatteryRuntimeWarningBanner import eu.darken.capod.main.ui.devicesettings.cards.ControlsCard +import eu.darken.capod.main.ui.devicesettings.cards.DeviceInfoBottomSheet import eu.darken.capod.main.ui.devicesettings.cards.DeviceInfoCard import eu.darken.capod.main.ui.devicesettings.cards.NoiseControlCard import eu.darken.capod.main.ui.devicesettings.cards.NotConnectedCard @@ -242,9 +243,65 @@ fun DeviceSettingsScreen( val enabled = device?.isAapReady == true val isPro = state.isPro // Hoisted out of DeviceInfoCard: the runtime warning banner opens the same detail sheet as the - // card's info icon. + // card's info icon, and both entry points can be scrolled out of composition independently. var showDeviceDetails by rememberSaveable { mutableStateOf(false) } + val context = LocalContext.current + val stateDetection = device?.ble as? HasStateDetection + val seenFirst = device?.seenFirstAt + val seenLast = device?.seenLastAt + val firstSeen = if (device != null && seenFirst != null && seenLast != null && + Duration.between(seenFirst, seenLast).toMinutes() >= 1 + ) { + device.firstSeenFormatted(state.now) + } else null + val locale = LocalConfiguration.current.locales[0] + val zoneId = ZoneId.systemDefault() + val dateFormatter = remember(locale, zoneId) { + DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) + .withLocale(locale) + .withZone(zoneId) + } + val detailLabels = rememberDeviceInfoDetailLabels() + val batteryHealthTexts = when { + state.batteryHealth != null -> BatteryHealthTexts( + left = state.batteryHealth.left?.let { + stringResource(R.string.device_settings_info_battery_health_value, it.percent) + }, + right = state.batteryHealth.right?.let { + stringResource(R.string.device_settings_info_battery_health_value, it.percent) + }, + headset = state.batteryHealth.headset?.let { + stringResource(R.string.device_settings_info_battery_health_value, it.percent) + }, + ) + state.batteryHealthPending -> BatteryHealthTexts( + pending = stringResource(R.string.device_settings_info_battery_health_pending), + ) + else -> null + } + val detailItems = if (device != null) { + buildDeviceInfoDetailItems( + info = device.deviceInfo, + labels = detailLabels, + formatDate = { instant -> dateFormatter.format(instant) }, + batteryHealth = batteryHealthTexts, + ) + } else { + emptyList() + } + + LaunchedEffect(detailItems) { + if (detailItems.isEmpty()) showDeviceDetails = false + } + + if (showDeviceDetails && detailItems.isNotEmpty()) { + DeviceInfoBottomSheet( + items = detailItems, + onDismiss = { showDeviceDetails = false }, + ) + } + Scaffold( topBar = { TopAppBar( @@ -286,45 +343,6 @@ fun DeviceSettingsScreen( // Device Info if (device != null) { item("device_info") { - 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 - val info = device.deviceInfo - val locale = LocalConfiguration.current.locales[0] - val zoneId = ZoneId.systemDefault() - val dateFormatter = remember(locale, zoneId) { - DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) - .withLocale(locale) - .withZone(zoneId) - } - val detailItems = buildDeviceInfoDetailItems( - info = info, - labels = rememberDeviceInfoDetailLabels(), - formatDate = { instant -> dateFormatter.format(instant) }, - batteryHealth = when { - state.batteryHealth != null -> BatteryHealthTexts( - left = state.batteryHealth.left?.let { - stringResource(R.string.device_settings_info_battery_health_value, it.percent) - }, - right = state.batteryHealth.right?.let { - stringResource(R.string.device_settings_info_battery_health_value, it.percent) - }, - headset = state.batteryHealth.headset?.let { - stringResource(R.string.device_settings_info_battery_health_value, it.percent) - }, - ) - state.batteryHealthPending -> BatteryHealthTexts( - pending = stringResource(R.string.device_settings_info_battery_health_pending), - ) - else -> null - }, - ) DeviceInfoCard( deviceInfo = device.deviceInfo, modelLabel = buildModelLabel(device), @@ -335,8 +353,7 @@ fun DeviceSettingsScreen( detailItems = detailItems, canRename = device.isAapReady, onRename = onDeviceNameChange, - showDetails = showDeviceDetails, - onShowDetailsChange = { showDeviceDetails = it }, + onShowDetails = { showDeviceDetails = true }, ) } } diff --git a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/cards/DeviceInfoCard.kt b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/cards/DeviceInfoCard.kt index 985445ba..f3426927 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/devicesettings/cards/DeviceInfoCard.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/devicesettings/cards/DeviceInfoCard.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Edit import androidx.compose.material.icons.twotone.Info -import androidx.compose.runtime.LaunchedEffect import androidx.compose.material3.CardDefaults import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon @@ -53,15 +52,10 @@ internal fun DeviceInfoCard( detailItems: List = emptyList(), canRename: Boolean = false, onRename: (String) -> Unit = {}, - showDetails: Boolean = false, - onShowDetailsChange: (Boolean) -> Unit = {}, + onShowDetails: () -> Unit = {}, ) { var showRenameDialog by remember { mutableStateOf(false) } - LaunchedEffect(detailItems) { - if (detailItems.isEmpty()) onShowDetailsChange(false) - } - if (showRenameDialog && deviceInfo != null) { RenameDialog( currentName = deviceInfo.name, @@ -73,13 +67,6 @@ internal fun DeviceInfoCard( ) } - if (showDetails && detailItems.isNotEmpty()) { - DeviceInfoBottomSheet( - items = detailItems, - onDismiss = { onShowDetailsChange(false) }, - ) - } - ElevatedCard( modifier = Modifier .fillMaxWidth() @@ -104,7 +91,7 @@ internal fun DeviceInfoCard( modifier = Modifier.weight(1f), ) if (showInfoIconInModelRow) { - IconButton(onClick = { onShowDetailsChange(true) }) { + IconButton(onClick = onShowDetails) { Icon( imageVector = Icons.TwoTone.Info, contentDescription = stringResource(R.string.device_settings_info_details_action), @@ -145,7 +132,7 @@ internal fun DeviceInfoCard( } else null, ) if (showInfoIconInNameRow) { - IconButton(onClick = { onShowDetailsChange(true) }) { + IconButton(onClick = onShowDetails) { Icon( imageVector = Icons.TwoTone.Info, contentDescription = stringResource(R.string.device_settings_info_details_action), @@ -160,7 +147,7 @@ internal fun DeviceInfoCard( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, ) { - IconButton(onClick = { onShowDetailsChange(true) }) { + IconButton(onClick = onShowDetails) { Icon( imageVector = Icons.TwoTone.Info, contentDescription = stringResource(R.string.device_settings_info_details_action),