feat(ui): complete ABox app experience redesign #10
@@ -47,7 +47,11 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.flagship.abox.manager.R
|
import com.flagship.abox.manager.R
|
||||||
import com.flagship.abox.manager.designsystem.ABoxButton
|
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.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.DeviceName
|
||||||
import com.flagship.abox.manager.domain.SocketPowerState
|
import com.flagship.abox.manager.domain.SocketPowerState
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -79,39 +83,10 @@ fun SocketScreen(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
var showEditNameDialog by remember { mutableStateOf(false) }
|
ABoxDeviceDetailScaffold(
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
topBar = {
|
title = state.deviceName?.value ?: stringResource(R.string.socket_title),
|
||||||
TopAppBar(
|
onBack = onBackClick,
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -121,6 +96,17 @@ fun SocketScreen(
|
|||||||
.verticalScroll(scrollState),
|
.verticalScroll(scrollState),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
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) {
|
if (state.isOffline) {
|
||||||
OfflineBanner(
|
OfflineBanner(
|
||||||
onRetry = { onEvent(SocketUiEvent.Refresh) },
|
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
|
@Composable
|
||||||
@@ -310,10 +261,10 @@ private fun PowerIndicatorBadge(
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
ABoxLoadingIndicator(modifier = Modifier.size(36.dp))
|
ABoxLoadingIndicator(modifier = Modifier.size(36.dp))
|
||||||
} else {
|
} else {
|
||||||
Text(
|
ABoxIcon(
|
||||||
text = if (powerState == SocketPowerState.ON) "⚡" else "○",
|
key = if (powerState == SocketPowerState.ON) ABoxIconKey.POWER else ABoxIconKey.SOCKET,
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
contentDescription = if (powerState == SocketPowerState.ON) "插座已开启" else "插座已关闭",
|
||||||
color = indicatorColor,
|
modifier = Modifier.size(40.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,9 +328,11 @@ private fun ErrorBanner(
|
|||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
TextButton(onClick = onDismiss) {
|
ABoxIconButton(
|
||||||
Text("✕")
|
key = ABoxIconKey.CLOSE,
|
||||||
}
|
contentDescription = "关闭错误提示",
|
||||||
|
onClick = onDismiss,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-71
@@ -39,7 +39,9 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.flagship.abox.manager.R
|
import com.flagship.abox.manager.R
|
||||||
import com.flagship.abox.manager.designsystem.ABoxButton
|
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.ABoxLoadingIndicator
|
||||||
|
import com.flagship.abox.manager.designsystem.ABoxIconButton
|
||||||
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
|
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
|
||||||
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
|
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
|
||||||
import com.flagship.abox.manager.domain.DeviceName
|
import com.flagship.abox.manager.domain.DeviceName
|
||||||
@@ -72,39 +74,10 @@ fun TemperatureHumidityScreen(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
var showEditNameDialog by remember { mutableStateOf(false) }
|
ABoxDeviceDetailScaffold(
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
topBar = {
|
title = state.deviceName?.value ?: stringResource(R.string.th_title),
|
||||||
TopAppBar(
|
onBack = onBackClick,
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -114,6 +87,17 @@ fun TemperatureHumidityScreen(
|
|||||||
.verticalScroll(scrollState),
|
.verticalScroll(scrollState),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
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) {
|
if (state.isOffline) {
|
||||||
OfflineBanner(
|
OfflineBanner(
|
||||||
onRetry = { onEvent(TemperatureHumidityUiEvent.Refresh) },
|
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
|
@Composable
|
||||||
@@ -353,7 +302,11 @@ private fun NoDataCard(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
Text("📭", style = MaterialTheme.typography.displaySmall)
|
ABoxIcon(
|
||||||
|
key = ABoxIconKey.SENSOR,
|
||||||
|
contentDescription = "暂无传感器数据",
|
||||||
|
modifier = Modifier.size(40.dp),
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.th_no_data_title),
|
text = stringResource(R.string.th_no_data_title),
|
||||||
@@ -432,9 +385,11 @@ private fun ErrorBanner(
|
|||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
TextButton(onClick = onDismiss) {
|
ABoxIconButton(
|
||||||
Text("✕")
|
key = ABoxIconKey.CLOSE,
|
||||||
}
|
contentDescription = "关闭错误提示",
|
||||||
|
onClick = onDismiss,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -59,6 +59,8 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.flagship.abox.manager.designsystem.ABoxSurface
|
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.ProviderProfileId
|
||||||
import com.flagship.abox.manager.domain.infrared.PanelLayoutStyle
|
import com.flagship.abox.manager.domain.infrared.PanelLayoutStyle
|
||||||
import com.flagship.abox.manager.domain.infrared.PresetCodeLibrary
|
import com.flagship.abox.manager.domain.infrared.PresetCodeLibrary
|
||||||
@@ -154,17 +156,15 @@ internal fun InfraredHubScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
TextButton(onClick = onBack) {
|
ABoxIconButton(ABoxIconKey.BACK, "返回", onBack)
|
||||||
Text(stringResource(R.string.infrared_back))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(
|
ABoxIconButton(
|
||||||
|
key = ABoxIconKey.EDIT,
|
||||||
|
contentDescription = "打开红外设置",
|
||||||
onClick = onNavigateToSettings,
|
onClick = onNavigateToSettings,
|
||||||
modifier = Modifier.testTag("hub_settings_button"),
|
modifier = Modifier.testTag("hub_settings_button"),
|
||||||
) {
|
)
|
||||||
Text("⚙️", fontSize = 20.sp)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -199,7 +199,7 @@ internal fun InfraredHubScreen(
|
|||||||
onClick = { showCreateDialog = true },
|
onClick = { showCreateDialog = true },
|
||||||
modifier = Modifier.testTag("hub_add_remote_button"),
|
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)
|
Text(icon, fontSize = 28.sp)
|
||||||
IconButton(onClick = onDelete, modifier = Modifier.testTag("delete_remote_${remote.id.value}")) {
|
IconButton(onClick = onDelete, modifier = Modifier.testTag("delete_remote_${remote.id.value}")) {
|
||||||
Text("🗑️", fontSize = 16.sp)
|
Text(stringResource(R.string.infrared_delete))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-5
@@ -60,6 +60,9 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.flagship.abox.manager.designsystem.ABoxSurface
|
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.AcFanSpeed
|
||||||
import com.flagship.abox.manager.domain.infrared.AcMode
|
import com.flagship.abox.manager.domain.infrared.AcMode
|
||||||
import com.flagship.abox.manager.domain.infrared.AcStateSnapshot
|
import com.flagship.abox.manager.domain.infrared.AcStateSnapshot
|
||||||
@@ -175,9 +178,7 @@ internal fun RemotePanelScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
TextButton(onClick = onBack) {
|
ABoxIconButton(ABoxIconKey.BACK, "返回", onBack)
|
||||||
Text(stringResource(R.string.infrared_back))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
// 非空调面板显示学习模式开关
|
// 非空调面板显示学习模式开关
|
||||||
@@ -185,7 +186,7 @@ internal fun RemotePanelScreen(
|
|||||||
FilterChip(
|
FilterChip(
|
||||||
selected = state.isLearnMode,
|
selected = state.isLearnMode,
|
||||||
onClick = onToggleLearnMode,
|
onClick = onToggleLearnMode,
|
||||||
label = { Text(if (state.isLearnMode) "🔴 录制中" else "⚪ 学习模式") },
|
label = { Text(if (state.isLearnMode) "录制中" else "学习模式") },
|
||||||
colors = FilterChipDefaults.filterChipColors(
|
colors = FilterChipDefaults.filterChipColors(
|
||||||
selectedContainerColor = Color(0xFFFF5252),
|
selectedContainerColor = Color(0xFFFF5252),
|
||||||
selectedLabelColor = Color.White,
|
selectedLabelColor = Color.White,
|
||||||
@@ -222,7 +223,7 @@ internal fun RemotePanelScreen(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
Text("🔴", fontSize = 16.sp)
|
ABoxIcon(ABoxIconKey.PENDING, "录制中", modifier = Modifier.size(20.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "学习模式已开启:点击任意按键,然后将物理实体遥控器对准转发器按下即可录码",
|
text = "学习模式已开启:点击任意按键,然后将物理实体遥控器对准转发器按下即可录码",
|
||||||
color = Color(0xFFC62828),
|
color = Color(0xFFC62828),
|
||||||
|
|||||||
Reference in New Issue
Block a user