feat: unify device detail presentation

This commit is contained in:
2026-08-26 19:39:07 +08:00
parent 9ba17c7d72
commit 171d1daa50
4 changed files with 68 additions and 159 deletions
@@ -47,7 +47,11 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flagship.abox.manager.R
import com.flagship.abox.manager.designsystem.ABoxButton
import com.flagship.abox.manager.designsystem.ABoxDeviceDetailScaffold
import com.flagship.abox.manager.designsystem.ABoxIconButton
import com.flagship.abox.manager.designsystem.ABoxLoadingIndicator
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
import com.flagship.abox.manager.domain.DeviceName
import com.flagship.abox.manager.domain.SocketPowerState
import java.text.SimpleDateFormat
@@ -79,39 +83,10 @@ fun SocketScreen(
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()
var showEditNameDialog by remember { mutableStateOf(false) }
Scaffold(
ABoxDeviceDetailScaffold(
modifier = modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
text = state.deviceName?.value ?: stringResource(R.string.socket_title),
style = MaterialTheme.typography.titleLarge,
)
},
navigationIcon = {
IconButton(onClick = onBackClick) {
Text("<", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
}
},
actions = {
IconButton(
onClick = { showEditNameDialog = true },
enabled = !state.isLoading && !state.isOperating,
) {
Text("✎", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
}
IconButton(
onClick = { onEvent(SocketUiEvent.Refresh) },
enabled = !state.isLoading && !state.isOperating,
) {
Text("↻", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
}
},
)
},
title = state.deviceName?.value ?: stringResource(R.string.socket_title),
onBack = onBackClick,
) { paddingValues ->
Column(
modifier = Modifier
@@ -121,6 +96,17 @@ fun SocketScreen(
.verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
ABoxIconButton(
key = ABoxIconKey.REFRESH,
contentDescription = "刷新插座状态",
onClick = { onEvent(SocketUiEvent.Refresh) },
enabled = !state.isLoading && !state.isOperating,
)
}
if (state.isOffline) {
OfflineBanner(
onRetry = { onEvent(SocketUiEvent.Refresh) },
@@ -248,41 +234,6 @@ fun SocketScreen(
}
}
if (showEditNameDialog) {
var inputName by remember(state.deviceName) { mutableStateOf(state.deviceName?.value.orEmpty()) }
AlertDialog(
onDismissRequest = { showEditNameDialog = false },
title = { Text(stringResource(R.string.dialog_edit_device_name_title)) },
text = {
OutlinedTextField(
value = inputName,
onValueChange = { inputName = it },
label = { Text(stringResource(R.string.dialog_edit_device_name_hint)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
},
confirmButton = {
TextButton(
onClick = {
val trimmed = inputName.trim()
if (trimmed.isNotBlank()) {
onEvent(SocketUiEvent.ChangeDeviceName(DeviceName(trimmed)))
}
showEditNameDialog = false
},
enabled = inputName.isNotBlank(),
) {
Text(stringResource(R.string.action_confirm))
}
},
dismissButton = {
TextButton(onClick = { showEditNameDialog = false }) {
Text(stringResource(R.string.action_cancel))
}
},
)
}
}
@Composable
@@ -310,10 +261,10 @@ private fun PowerIndicatorBadge(
if (isLoading) {
ABoxLoadingIndicator(modifier = Modifier.size(36.dp))
} else {
Text(
text = if (powerState == SocketPowerState.ON) "⚡" else "○",
style = MaterialTheme.typography.headlineLarge,
color = indicatorColor,
ABoxIcon(
key = if (powerState == SocketPowerState.ON) ABoxIconKey.POWER else ABoxIconKey.SOCKET,
contentDescription = if (powerState == SocketPowerState.ON) "插座已开启" else "插座已关闭",
modifier = Modifier.size(40.dp),
)
}
}
@@ -377,9 +328,11 @@ private fun ErrorBanner(
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.weight(1f),
)
TextButton(onClick = onDismiss) {
Text("✕")
}
ABoxIconButton(
key = ABoxIconKey.CLOSE,
contentDescription = "关闭错误提示",
onClick = onDismiss,
)
}
}
}
@@ -39,7 +39,9 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flagship.abox.manager.R
import com.flagship.abox.manager.designsystem.ABoxButton
import com.flagship.abox.manager.designsystem.ABoxDeviceDetailScaffold
import com.flagship.abox.manager.designsystem.ABoxLoadingIndicator
import com.flagship.abox.manager.designsystem.ABoxIconButton
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
import com.flagship.abox.manager.domain.DeviceName
@@ -72,39 +74,10 @@ fun TemperatureHumidityScreen(
modifier: Modifier = Modifier,
) {
val scrollState = rememberScrollState()
var showEditNameDialog by remember { mutableStateOf(false) }
Scaffold(
ABoxDeviceDetailScaffold(
modifier = modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
text = state.deviceName?.value ?: stringResource(R.string.th_title),
style = MaterialTheme.typography.titleLarge,
)
},
navigationIcon = {
IconButton(onClick = onBackClick) {
Text("<", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
}
},
actions = {
IconButton(
onClick = { showEditNameDialog = true },
enabled = !state.isLoading && !state.isRefreshing,
) {
Text("✎", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
}
IconButton(
onClick = { onEvent(TemperatureHumidityUiEvent.Refresh) },
enabled = !state.isLoading && !state.isRefreshing,
) {
Text("↻", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
}
},
)
},
title = state.deviceName?.value ?: stringResource(R.string.th_title),
onBack = onBackClick,
) { paddingValues ->
Column(
modifier = Modifier
@@ -114,6 +87,17 @@ fun TemperatureHumidityScreen(
.verticalScroll(scrollState),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
ABoxIconButton(
key = ABoxIconKey.REFRESH,
contentDescription = "刷新温湿度数据",
onClick = { onEvent(TemperatureHumidityUiEvent.Refresh) },
enabled = !state.isLoading && !state.isRefreshing,
)
}
if (state.isOffline) {
OfflineBanner(
onRetry = { onEvent(TemperatureHumidityUiEvent.Refresh) },
@@ -241,41 +225,6 @@ fun TemperatureHumidityScreen(
}
}
if (showEditNameDialog) {
var inputName by remember(state.deviceName) { mutableStateOf(state.deviceName?.value.orEmpty()) }
AlertDialog(
onDismissRequest = { showEditNameDialog = false },
title = { Text(stringResource(R.string.dialog_edit_device_name_title)) },
text = {
OutlinedTextField(
value = inputName,
onValueChange = { inputName = it },
label = { Text(stringResource(R.string.dialog_edit_device_name_hint)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
)
},
confirmButton = {
TextButton(
onClick = {
val trimmed = inputName.trim()
if (trimmed.isNotBlank()) {
onEvent(TemperatureHumidityUiEvent.ChangeDeviceName(DeviceName(trimmed)))
}
showEditNameDialog = false
},
enabled = inputName.isNotBlank(),
) {
Text(stringResource(R.string.action_confirm))
}
},
dismissButton = {
TextButton(onClick = { showEditNameDialog = false }) {
Text(stringResource(R.string.action_cancel))
}
},
)
}
}
@Composable
@@ -353,7 +302,11 @@ private fun NoDataCard(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text("📭", style = MaterialTheme.typography.displaySmall)
ABoxIcon(
key = ABoxIconKey.SENSOR,
contentDescription = "暂无传感器数据",
modifier = Modifier.size(40.dp),
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(R.string.th_no_data_title),
@@ -432,9 +385,11 @@ private fun ErrorBanner(
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.weight(1f),
)
TextButton(onClick = onDismiss) {
Text("✕")
}
ABoxIconButton(
key = ABoxIconKey.CLOSE,
contentDescription = "关闭错误提示",
onClick = onDismiss,
)
}
}
}