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,
)
}
}
}
@@ -59,6 +59,8 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flagship.abox.manager.designsystem.ABoxSurface
import com.flagship.abox.manager.designsystem.ABoxIconButton
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
import com.flagship.abox.manager.domain.ProviderProfileId
import com.flagship.abox.manager.domain.infrared.PanelLayoutStyle
import com.flagship.abox.manager.domain.infrared.PresetCodeLibrary
@@ -154,17 +156,15 @@ internal fun InfraredHubScreen(
}
},
navigationIcon = {
TextButton(onClick = onBack) {
Text(stringResource(R.string.infrared_back))
}
ABoxIconButton(ABoxIconKey.BACK, "返回", onBack)
},
actions = {
IconButton(
ABoxIconButton(
key = ABoxIconKey.EDIT,
contentDescription = "打开红外设置",
onClick = onNavigateToSettings,
modifier = Modifier.testTag("hub_settings_button"),
) {
Text("⚙️", fontSize = 20.sp)
}
)
},
)
},
@@ -199,7 +199,7 @@ internal fun InfraredHubScreen(
onClick = { showCreateDialog = true },
modifier = Modifier.testTag("hub_add_remote_button"),
) {
Text("➕ " + stringResource(R.string.infrared_hub_add_remote))
Text(stringResource(R.string.infrared_hub_add_remote))
}
}
@@ -278,7 +278,7 @@ private fun VirtualRemoteCard(
}
Text(icon, fontSize = 28.sp)
IconButton(onClick = onDelete, modifier = Modifier.testTag("delete_remote_${remote.id.value}")) {
Text("🗑️", fontSize = 16.sp)
Text(stringResource(R.string.infrared_delete))
}
}
@@ -60,6 +60,9 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.flagship.abox.manager.designsystem.ABoxSurface
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.infrared.AcFanSpeed
import com.flagship.abox.manager.domain.infrared.AcMode
import com.flagship.abox.manager.domain.infrared.AcStateSnapshot
@@ -175,9 +178,7 @@ internal fun RemotePanelScreen(
}
},
navigationIcon = {
TextButton(onClick = onBack) {
Text(stringResource(R.string.infrared_back))
}
ABoxIconButton(ABoxIconKey.BACK, "返回", onBack)
},
actions = {
// 非空调面板显示学习模式开关
@@ -185,7 +186,7 @@ internal fun RemotePanelScreen(
FilterChip(
selected = state.isLearnMode,
onClick = onToggleLearnMode,
label = { Text(if (state.isLearnMode) "🔴 录制中" else "⚪ 学习模式") },
label = { Text(if (state.isLearnMode) "录制中" else "学习模式") },
colors = FilterChipDefaults.filterChipColors(
selectedContainerColor = Color(0xFFFF5252),
selectedLabelColor = Color.White,
@@ -222,7 +223,7 @@ internal fun RemotePanelScreen(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Text("🔴", fontSize = 16.sp)
ABoxIcon(ABoxIconKey.PENDING, "录制中", modifier = Modifier.size(20.dp))
Text(
text = "学习模式已开启:点击任意按键,然后将物理实体遥控器对准转发器按下即可录码",
color = Color(0xFFC62828),