package com.example.smarthome.ui import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import com.example.smarthome.R import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.border import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import android.view.SoundEffectConstants import kotlinx.coroutines.launch import kotlinx.coroutines.delay import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.geometry.Offset import androidx.compose.foundation.layout.offset import androidx.compose.ui.draw.rotate import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.animateFloat import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.tween import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode @Composable fun MainScaffold( selectedRoom: Int, onRoomSelect: (Int) -> Unit, selectedNavItem: Int = 0, onNavItemSelect: (Int) -> Unit = {}, rooms: List = listOf("总览", "客厅", "厨房", "卧室", "影音室", "游戏房"), onAddRoom: (String) -> Unit = {}, onDeleteRoom: (Int) -> Unit = {} ) { Row(modifier = Modifier.fillMaxSize()) { SideNavRail( selectedNavItem = selectedNavItem, onNavItemSelect = onNavItemSelect ) // 根据选中的导航项显示不同的内容 when (selectedNavItem) { 0 -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom) 5 -> SettingsContent() else -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom) } } } @Composable fun DashboardContent( selectedRoom: Int, onRoomSelect: (Int) -> Unit, rooms: List = listOf("总览", "客厅", "厨房", "卧室", "影音室", "游戏房"), onAddRoom: (String) -> Unit = {}, onDeleteRoom: (Int) -> Unit = {} ) { var showAddRoomDialog by remember { mutableStateOf(false) } var editMode by remember { mutableStateOf(false) } Box( modifier = Modifier .fillMaxSize() .pointerInput(editMode) { if (editMode) { detectTapGestures( onTap = { editMode = false } ) } } ) { Column(modifier = Modifier.fillMaxSize().padding(16.dp)) { TopBar() RoomTabs( selectedRoom = selectedRoom, onRoomSelect = onRoomSelect, rooms = rooms, onAddRoomClick = { showAddRoomDialog = true }, onDeleteRoom = onDeleteRoom, editMode = editMode, onEditModeChange = { editMode = it } ) // 根据选中的房间显示不同的内容 RoomContent(selectedRoom = selectedRoom, roomName = rooms.getOrNull(selectedRoom) ?: "总览") } if (showAddRoomDialog) { AddRoomDialog( onDismiss = { showAddRoomDialog = false }, onConfirm = { roomName -> onAddRoom(roomName) showAddRoomDialog = false } ) } } } @Composable fun RoomContent(selectedRoom: Int, roomName: String) { // 根据房间索引显示不同的内容 when (selectedRoom) { 0 -> OverviewRoomContent() // 总览 else -> SpecificRoomContent(selectedRoom, roomName) // 具体房间 } } @Composable fun OverviewRoomContent() { Column(modifier = Modifier.fillMaxSize()) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { AirConditionerCard(modifier = Modifier.weight(1f), roomName = "全屋") UsageStatusChart(modifier = Modifier.weight(1f), roomName = "全屋") } LightRow(modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), roomName = "全屋") ModeButtonsRow(onModeSelected = { }) Spacer(modifier = Modifier.height(24.dp)) // 显示所有房间的设备摘要 Text( text = "所有设备", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.padding(bottom = 12.dp) ) AllDevicesOverview(modifier = Modifier.fillMaxSize()) } } @Composable fun SpecificRoomContent(selectedRoom: Int, roomName: String) { Column(modifier = Modifier.fillMaxSize()) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) { AirConditionerCard(modifier = Modifier.weight(1f), roomName = roomName) UsageStatusChart(modifier = Modifier.weight(1f), roomName = roomName) } LightRow(modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), roomName = roomName) Spacer(modifier = Modifier.height(24.dp)) Text( text = "$roomName 设备", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.padding(bottom = 12.dp) ) MyDevicesGrid(selectedRoom = selectedRoom, modifier = Modifier.fillMaxSize()) } } @Composable fun AllDevicesOverview(modifier: Modifier = Modifier) { val allDevices = listOf( Device("客厅空调", "24°C", R.drawable.ic_ac, true), Device("客厅灯光", "80%", R.drawable.ic_light, true), Device("厨房灯光", "60%", R.drawable.ic_light, true), Device("卧室空调", "22°C", R.drawable.ic_ac, false), Device("影音室电视", "运行中", R.drawable.ic_media, true), Device("游戏房主机", "待机中", R.drawable.ic_media, false) ) LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 140.dp), modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(allDevices) { d -> DeviceCard(d) } } } @Composable fun HeaderHero() { TopBar() } @Composable fun StatusChips() { SideNavRail() } @Composable fun Chip(text: String) { Box(modifier = Modifier.clip(RoundedCornerShape(20.dp)).background(Color(0x331A1A1A)).padding(horizontal = 12.dp, vertical = 8.dp)) { Text(text = text, color = Color.White) } } @Composable fun QuickActions() { Row(modifier = Modifier.fillMaxWidth().padding(24.dp), horizontalArrangement = Arrangement.spacedBy(12.dp)) { QuickActionCard( title = "回家模式", colors = Brush.linearGradient(listOf(Color(0xFFFFD54F), Color(0xFFFF8F00))), modifier = Modifier.weight(1f), onClick = { /* TODO: 触发回家场景 */ } ) QuickActionCard( title = "出门模式", colors = Brush.linearGradient(listOf(Color(0xFFFF8A65), Color(0xFFFF7043))), modifier = Modifier.weight(1f), onClick = { /* TODO: 触发出门场景 */ } ) QuickActionCard( title = "玩乐模式", colors = Brush.linearGradient(listOf(Color(0xFF6A3DFF), Color(0xFF3A0CA3))), modifier = Modifier.weight(1f), onClick = { /* TODO: 触发玩乐场景 */ } ) } } @Composable fun QuickActionCard(title: String, colors: Brush, modifier: Modifier = Modifier, onClick: () -> Unit) { val shape = RoundedCornerShape(24.dp) var pressed by remember { mutableStateOf(false) } val scale by androidx.compose.animation.core.animateFloatAsState( targetValue = if (pressed) 0.97f else 1f, animationSpec = androidx.compose.animation.core.spring(), label = "quick_action_scale" ) Box( modifier = modifier .height(88.dp) .shadow(12.dp, shape, false) .clip(shape) .background(colors) .scale(scale) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { pressed = !pressed onClick() } .padding(16.dp), contentAlignment = Alignment.BottomStart ) { Text(text = title, fontWeight = FontWeight.Medium, color = Color.White) } } @Composable fun RoomTabs( selectedRoom: Int, onRoomSelect: (Int) -> Unit, rooms: List = listOf("总览", "客厅", "厨房", "卧室", "影音室", "游戏房"), onAddRoomClick: () -> Unit = {}, onDeleteRoom: (Int) -> Unit = {}, editMode: Boolean = false, onEditModeChange: (Boolean) -> Unit = {} ) { Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { // 可滚动的房间标签区域 Row( modifier = Modifier .weight(1f) .horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { rooms.forEachIndexed { index, name -> RoomTab( name = name, selected = index == selectedRoom, editMode = editMode, canDelete = rooms.size > 1, onClick = { if (!editMode) { onRoomSelect(index) } }, onLongClick = { onEditModeChange(true) }, onDelete = { onDeleteRoom(index) onEditModeChange(false) } ) } } // 固定的添加按钮 GradientButton("+ 添加", onClick = onAddRoomClick) } } @Composable fun RoomTab( name: String, selected: Boolean, editMode: Boolean, canDelete: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, onDelete: () -> Unit ) { val scope = rememberCoroutineScope() // 改进的晃动动画 - 使用无限循环的旋转动画 val infiniteTransition = rememberInfiniteTransition(label = "shake") val rotation by infiniteTransition.animateFloat( initialValue = -3f, targetValue = 3f, animationSpec = infiniteRepeatable( animation = tween(150, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "rotation" ) // 缩放动画 val scale by androidx.compose.animation.core.animateFloatAsState( targetValue = if (editMode && canDelete) 0.95f else 1f, animationSpec = androidx.compose.animation.core.spring( dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy, stiffness = androidx.compose.animation.core.Spring.StiffnessLow ), label = "scale" ) Box( modifier = Modifier .scale(scale) .then( if (editMode && canDelete) { Modifier.rotate(rotation) } else { Modifier } ) .pointerInput(Unit) { detectTapGestures( onTap = { onClick() }, onLongPress = { if (canDelete) { onLongClick() } } ) } ) { val base = Modifier.clip(RoundedCornerShape(20.dp)) val mod = if (selected) { base.background(brush = Brush.linearGradient(listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF)))) } else { base.background(color = Color(0x22121212)) } Box( modifier = mod.padding(horizontal = 14.dp, vertical = 10.dp) ) { Text( text = name, color = if (selected) Color.Black else Color(0xFFB0B0B0), maxLines = 1 ) } // 删除按钮 if (editMode && canDelete) { Box( modifier = Modifier .size(22.dp) .offset(x = (-4).dp, y = (-4).dp) .align(Alignment.TopEnd) .shadow(4.dp, RoundedCornerShape(11.dp)) .clip(RoundedCornerShape(11.dp)) .background(Color(0xFFFF5252)) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { onDelete() }, contentAlignment = Alignment.Center ) { Text( text = "×", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold ) } } } } @Composable fun DeviceGrid() { val devices = listOf( Device("空调", "室内 25℃", R.drawable.ic_ac, true), Device("智能开关", "待机中", R.drawable.ic_light, false), Device("吊灯", "6500K", R.drawable.ic_light, true), Device("扫地机", "清扫中", R.drawable.ic_media, true), Device("空气净化", "运行中", R.drawable.ic_security, true), Device("饮水机", "待机中", R.drawable.ic_media, false) ) LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 140.dp), modifier = Modifier.fillMaxSize().padding(24.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { items(devices) { d -> DeviceCard(d) } } } data class Device(val name: String, val sub: String, val icon: Int, val on: Boolean) @Composable fun DeviceCard(d: Device) { var active by remember { mutableStateOf(d.on) } Box(modifier = Modifier.height(120.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).shadow(12.dp, RoundedCornerShape(24.dp), true).padding(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) { Image(painter = painterResource(id = d.icon), contentDescription = null, modifier = Modifier.size(40.dp)) Column { Text(text = d.name, fontWeight = FontWeight.SemiBold) Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { Box(modifier = Modifier.size(8.dp).clip(RoundedCornerShape(4.dp)).background(if (active) Color(0xFF00E676) else Color(0xFF9AA0A6))) Text(text = d.sub, color = Color(0xFF9AA0A6)) } } } Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Text(text = if (active) "运行中" else "待机中", color = if (active) Color(0xFF00E676) else Color(0xFF9AA0A6)) Toggle(active) { active = it } } } } } @Composable fun Toggle(on: Boolean, change: (Boolean) -> Unit) { Box(modifier = Modifier.size(48.dp, 28.dp).clip(RoundedCornerShape(18.dp)).background(if (on) Color(0xFF3A0CA3) else Color(0x33222222)).clickable { change(!on) }.padding(4.dp)) { Box(modifier = Modifier.size(20.dp).clip(RoundedCornerShape(10.dp)).background(Color.White).align(if (on) Alignment.CenterEnd else Alignment.CenterStart)) } } @Composable fun BottomNav() { Row(modifier = Modifier.fillMaxWidth().padding(16.dp).clip(RoundedCornerShape(28.dp)).background(Color(0x33121212)).padding(8.dp), horizontalArrangement = Arrangement.SpaceEvenly) { NavItem("首页", true) NavItem("设备", false) NavItem("我的", false) } } @Composable fun NavItem(title: String, selected: Boolean) { Box(modifier = Modifier.clip(RoundedCornerShape(20.dp)).background(if (selected) Color(0x55121212) else Color.Transparent).padding(horizontal = 18.dp, vertical = 10.dp)) { Text(text = title, color = if (selected) Color.White else Color(0xFFB0B0B0)) } } @Composable fun SideNavRail(selectedNavItem: Int = 0, onNavItemSelect: (Int) -> Unit = {}) { Column(modifier = Modifier.width(120.dp).fillMaxHeight().padding(12.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) { Spacer(modifier = Modifier.height(48.dp)) NavRailItem("控制台", R.drawable.ic_dashboard, selectedNavItem == 0, onClick = { onNavItemSelect(0) }) NavRailItem("最近", R.drawable.ic_recent, selectedNavItem == 1, onClick = { onNavItemSelect(1) }) NavRailItem("收藏", R.drawable.ic_bookmark, selectedNavItem == 2, onClick = { onNavItemSelect(2) }) NavRailItem("下载", R.drawable.ic_download, selectedNavItem == 3, onClick = { onNavItemSelect(3) }) NavRailItem("支持", R.drawable.ic_support, selectedNavItem == 4, onClick = { onNavItemSelect(4) }) NavRailItem("设置", R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) }) } } @Composable fun NavRailItem(text: String, iconRes: Int, selected: Boolean, onClick: () -> Unit = {}) { val scale by androidx.compose.animation.core.animateFloatAsState( targetValue = if (selected) 1.05f else 1f, animationSpec = androidx.compose.animation.core.spring(), label = "nav_scale" ) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier .scale(scale) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { onClick() } .padding(vertical = 4.dp) ) { Box( modifier = Modifier .size(48.dp) .shadow(if (selected) 8.dp else 0.dp, RoundedCornerShape(14.dp)) .clip(RoundedCornerShape(14.dp)) .background( if (selected) { Brush.linearGradient( listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF)) ) } else { Brush.linearGradient( listOf(Color(0x22FFFFFF), Color(0x22FFFFFF)) ) } ), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = iconRes), contentDescription = text, modifier = Modifier.size(24.dp), colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( if (selected) Color.Black else Color(0xFFB0B0B0) ) ) } Text( text = text, color = if (selected) Color.White else Color(0xFFB0B0B0), fontSize = 13.sp, fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal, maxLines = 1 ) } } @Composable fun TopBar() { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Text(text = "控制台", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White) Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { Box(modifier = Modifier.width(280.dp).height(40.dp).clip(RoundedCornerShape(20.dp)).background(Color(0x22121212)).padding(horizontal = 16.dp), contentAlignment = Alignment.CenterStart) { Text(text = "搜索关键词", color = Color(0xFF9AA0A6)) } GradientButton("+ 添加设备") } } } @Composable fun GradientButton(text: String, onClick: () -> Unit = {}) { Box(modifier = Modifier.height(36.dp).clip(RoundedCornerShape(20.dp)).background(Brush.linearGradient(listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF)))).clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { onClick() }.padding(horizontal = 16.dp), contentAlignment = Alignment.Center) { Text(text = text, color = Color.Black, fontWeight = FontWeight.Medium) } } @Composable fun MyDevicesGrid(selectedRoom: Int, modifier: Modifier = Modifier) { val devices = when (selectedRoom) { 1 -> listOf( // 客厅 Device("智能电视", "运行中", R.drawable.ic_media, true), Device("音响", "待机中", R.drawable.ic_media, false), Device("空调", "24°C", R.drawable.ic_ac, true), Device("主灯", "80%", R.drawable.ic_light, true), Device("窗帘", "已打开", R.drawable.ic_curtain, true), Device("插座", "运行中", R.drawable.ic_light, true) ) 2 -> listOf( // 厨房 Device("冰箱", "运行中", R.drawable.ic_ac, true), Device("微波炉", "待机中", R.drawable.ic_media, false), Device("油烟机", "待机中", R.drawable.ic_media, false), Device("灯光", "60%", R.drawable.ic_light, true) ) 3 -> listOf( // 卧室 Device("空调", "22°C", R.drawable.ic_ac, true), Device("床头灯", "40%", R.drawable.ic_light, true), Device("窗帘", "已关闭", R.drawable.ic_curtain, false), Device("加湿器", "运行中", R.drawable.ic_media, true), Device("空气净化器", "运行中", R.drawable.ic_security, true) ) 4 -> listOf( // 影音室 Device("投影仪", "运行中", R.drawable.ic_media, true), Device("功放", "运行中", R.drawable.ic_media, true), Device("音响系统", "运行中", R.drawable.ic_media, true), Device("灯光", "20%", R.drawable.ic_light, true), Device("窗帘", "已关闭", R.drawable.ic_curtain, false), Device("空调", "23°C", R.drawable.ic_ac, true) ) 5 -> listOf( // 游戏房 Device("游戏主机", "运行中", R.drawable.ic_media, true), Device("显示器", "运行中", R.drawable.ic_media, true), Device("RGB灯带", "运行中", R.drawable.ic_light, true), Device("空调", "20°C", R.drawable.ic_ac, true), Device("路由器", "运行中", R.drawable.ic_security, true) ) else -> emptyList() } LazyVerticalGrid( columns = GridCells.Adaptive(minSize = 140.dp), modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(devices) { d -> DeviceCard(d) } } } @Composable fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间") { // 根据房间生成不同的数据 val values = remember(roomName) { List(10) { (10..35).random().toFloat() } } val totalPower = remember(roomName) { (15.0 + Math.random() * 30).toString().take(5) } val totalHours = remember(roomName) { (10 + (Math.random() * 40).toInt()) } Box(modifier = modifier.height(220.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).padding(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Column { Text(text = "使用状态", fontWeight = FontWeight.SemiBold, color = Color.White) Text(text = roomName, fontSize = 12.sp, color = Color(0xFF9AA0A6)) } Row(horizontalArrangement = Arrangement.spacedBy(24.dp)) { Column { Text(text = "${totalPower}kWh", color = Color.White) Text(text = "总消耗", color = Color(0xFF9AA0A6), fontSize = 12.sp) } Column { Text(text = "${totalHours}h", color = Color.White) Text(text = "总时长", color = Color(0xFF9AA0A6), fontSize = 12.sp) } } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.Bottom) { values.forEach { v -> Box(modifier = Modifier.weight(1f).height(v.dp).clip(RoundedCornerShape(6.dp)).background(Color(0xFF2A2A2A))) } } } } } @Composable fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间") { var temp by remember { mutableStateOf(24f) } var isOn by remember { mutableStateOf(true) } Box(modifier = modifier.height(220.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).padding(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Column { Text(text = "空调", fontWeight = FontWeight.SemiBold, color = Color.White, fontSize = 18.sp) Text(text = roomName, fontSize = 12.sp, color = Color(0xFF9AA0A6)) } Switch(checked = isOn, onCheckedChange = { isOn = it }) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( text = "${temp.toInt()}°C", style = MaterialTheme.typography.headlineLarge, color = Color.White, fontWeight = FontWeight.Bold ) Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { // 降温按钮 Box( modifier = Modifier .size(40.dp) .clip(RoundedCornerShape(12.dp)) .background(Color(0x33FFFFFF)) .clickable(enabled = isOn && temp > 16f) { if (temp > 16f) temp -= 1f }, contentAlignment = Alignment.Center ) { Text(text = "−", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Bold) } // 升温按钮 Box( modifier = Modifier .size(40.dp) .clip(RoundedCornerShape(12.dp)) .background(Color(0x33FFFFFF)) .clickable(enabled = isOn && temp < 32f) { if (temp < 32f) temp += 1f }, contentAlignment = Alignment.Center ) { Text(text = "+", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Bold) } } } // 自定义滑动条 CustomSlider( value = temp, onValueChange = { temp = it }, valueRange = 16f..32f, enabled = isOn ) // 温度标记 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text(text = "16°C", fontSize = 12.sp, color = Color(0xFF9AA0A6)) Text(text = "24°C", fontSize = 12.sp, color = Color(0xFF9AA0A6)) Text(text = "32°C", fontSize = 12.sp, color = Color(0xFF9AA0A6)) } } } } @OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class) @Composable fun CustomSlider( value: Float, onValueChange: (Float) -> Unit, valueRange: ClosedFloatingPointRange, enabled: Boolean = true ) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = Modifier .fillMaxWidth() .height(32.dp), contentAlignment = Alignment.Center ) { Slider( value = value, onValueChange = onValueChange, valueRange = valueRange, enabled = enabled, interactionSource = interactionSource, colors = androidx.compose.material3.SliderDefaults.colors( thumbColor = Color.White, activeTrackColor = Brush.linearGradient( listOf(Color(0xFF00D1FF), Color(0xFFA9F0FF)) ).let { Color(0xFF00D1FF) }, inactiveTrackColor = Color(0x33FFFFFF), disabledThumbColor = Color(0x66FFFFFF), disabledActiveTrackColor = Color(0x33FFFFFF), disabledInactiveTrackColor = Color(0x22FFFFFF) ), modifier = Modifier .fillMaxWidth() .height(24.dp), thumb = { Box( modifier = Modifier .size(20.dp) .shadow(4.dp, RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp)) .background(Color.White) ) }, track = { sliderState -> val fraction = (sliderState.value - valueRange.start) / (valueRange.endInclusive - valueRange.start) Box( modifier = Modifier .fillMaxWidth() .height(6.dp) .clip(RoundedCornerShape(3.dp)) ) { // 未激活轨道 Box( modifier = Modifier .fillMaxSize() .background(if (enabled) Color(0x33FFFFFF) else Color(0x22FFFFFF)) ) // 激活轨道 Box( modifier = Modifier .fillMaxWidth(fraction) .fillMaxHeight() .background( if (enabled) { Brush.linearGradient( listOf(Color(0xFF00D1FF), Color(0xFFA9F0FF)) ) } else { Brush.linearGradient( listOf(Color(0x33FFFFFF), Color(0x33FFFFFF)) ) } ) ) } } ) } } @Composable fun LightList(modifier: Modifier = Modifier) { val lights = listOf( "灯光 1" to 0.6f, "灯光 2" to 0.8f, "灯光 3" to 0.45f, "灯光 4" to 0.6f, "灯光 5" to 0.6f ) Column(modifier = modifier.fillMaxHeight(), verticalArrangement = Arrangement.spacedBy(12.dp)) { Text(text = "灯光", fontWeight = FontWeight.SemiBold) lights.forEach { (name, percent) -> Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clip(RoundedCornerShape(16.dp)).background(Color(0x26121212)).padding(12.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212))) Column { Text(text = name); Text(text = "${(percent * 100).toInt()}%", color = Color(0xFF9AA0A6)) } } DotsProgress(percent) } } } } @Composable fun DotsProgress(percent: Float, onPercentChange: (Float) -> Unit = {}, enabled: Boolean = true) { val total = 12 Row( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.pointerInput(enabled) { if (enabled) { detectTapGestures { offset -> // 计算点击的是第几个点 val dotWidth = 6.dp.toPx() val spacing = 4.dp.toPx() val totalWidth = (dotWidth + spacing) * total - spacing val clickedIndex = ((offset.x / totalWidth) * total).toInt().coerceIn(0, total - 1) // 更新百分比 val newPercent = (clickedIndex + 1).toFloat() / total onPercentChange(newPercent) } } } ) { repeat(total) { i -> val filled = i < (percent * total).toInt() Box( modifier = Modifier .size(8.dp) .clip(RoundedCornerShape(4.dp)) .background(if (filled) Color(0xFFA9F0FF) else Color(0xFF3A3A3A)) .clickable(enabled = enabled) { // 点击某个点直接设置到该亮度 val newPercent = (i + 1).toFloat() / total onPercentChange(newPercent) } ) } } } enum class Mode { HOME, AWAY, FUN } @Composable fun ModeButtonsRow(onModeSelected: (Mode) -> Unit) { BoxWithConstraints(modifier = Modifier.fillMaxWidth()) { val density = LocalDensity.current val buttonHeight = when { maxWidth >= 1024.dp -> with(density) { 500f.toDp() } maxWidth >= 600.dp -> with(density) { 460f.toDp() } else -> with(density) { 160f.toDp() } } var selected by remember { mutableStateOf(null) } val isVertical = maxWidth < 480.dp val containerModifier = Modifier.fillMaxWidth() val arrangement = Arrangement.spacedBy(16.dp) val items = listOf( Triple(Mode.HOME, Brush.linearGradient(listOf(Color(0xFF00D1FF), Color(0xFF00C9A7))), "回家"), Triple(Mode.AWAY, Brush.linearGradient(listOf(Color(0xFFFF8A65), Color(0xFFFF7043))), "出门"), Triple(Mode.FUN, Brush.linearGradient(listOf(Color(0xFF6A3DFF), Color(0xFF3A0CA3))), "玩乐"), ) if (isVertical) { Column(modifier = containerModifier, verticalArrangement = arrangement) { items.forEach { (mode, brush, label) -> ModeButton( text = label, modifier = Modifier.fillMaxWidth(), height = buttonHeight, brush = brush, selected = selected == mode, onClick = { selected = mode onModeSelected(mode) } ) } } } else { Row(modifier = containerModifier, horizontalArrangement = arrangement) { items.forEach { (mode, brush, label) -> ModeButton( text = label, modifier = Modifier.weight(1f), height = buttonHeight, brush = brush, selected = selected == mode, onClick = { selected = mode onModeSelected(mode) } ) } } } } } @Composable fun ModeButton( text: String, modifier: Modifier, height: androidx.compose.ui.unit.Dp, brush: Brush, selected: Boolean, onClick: () -> Unit ) { val shape = RoundedCornerShape(24.dp) val interaction = remember { MutableInteractionSource() } var pressed by remember { mutableStateOf(false) } val scale by androidx.compose.animation.core.animateFloatAsState( targetValue = if (pressed) 0.96f else 1f, animationSpec = androidx.compose.animation.core.spring( dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy, stiffness = androidx.compose.animation.core.Spring.StiffnessLow ), label = "scale" ) val elevation by androidx.compose.animation.core.animateDpAsState( targetValue = if (selected) 16.dp else if (pressed) 4.dp else 8.dp, animationSpec = androidx.compose.animation.core.tween(300), label = "elevation" ) val view = LocalView.current LaunchedEffect(interaction) { interaction.interactions.collect { i -> when (i) { is PressInteraction.Press -> pressed = true is PressInteraction.Release, is PressInteraction.Cancel -> pressed = false } } } Box( modifier = modifier .height(height) .scale(scale) .shadow(elevation, shape) .background( if (selected) { brush } else { Brush.linearGradient( listOf( Color(0x40FFFFFF), Color(0x20FFFFFF) ) ) }, shape = shape ) .border( width = if (selected) 2.dp else 1.dp, brush = if (selected) { Brush.linearGradient( listOf( Color(0x99FFFFFF), Color(0x66FFFFFF) ) ) } else { Brush.linearGradient( listOf( Color(0x33FFFFFF), Color(0x11FFFFFF) ) ) }, shape = shape ) .clickable(indication = null, interactionSource = interaction) { view.playSoundEffect(SoundEffectConstants.CLICK) onClick() } .padding(24.dp), contentAlignment = Alignment.Center ) { Text( text = text, color = if (selected) Color.White else Color(0xFFB0B0B0), fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, fontSize = if (selected) 22.sp else 20.sp, style = MaterialTheme.typography.titleLarge ) } } @Composable fun LightRow(modifier: Modifier = Modifier, roomName: String = "房间") { // 根据房间生成不同的灯光数据 val lights = remember(roomName) { listOf( "$roomName 主灯" to (0.5f + Math.random().toFloat() * 0.4f), "$roomName 辅灯" to (0.5f + Math.random().toFloat() * 0.4f) ) } Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(16.dp)) { lights.forEach { (name, percent) -> LightCard(name = name, percent = percent, modifier = Modifier.weight(1f)) } } } @Composable fun LightCard(name: String, percent: Float, modifier: Modifier = Modifier) { var brightness by remember { mutableStateOf(percent) } Box(modifier = modifier.height(100.dp).clip(RoundedCornerShape(16.dp)).background(Color(0x26121212)).padding(12.dp)) { Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { // 灯光图标 - 带发光效果 Box( modifier = Modifier .size(40.dp) .shadow( elevation = if (brightness > 0.3f) (brightness * 12).dp else 0.dp, shape = RoundedCornerShape(12.dp), spotColor = Color(0xFFFFD54F).copy(alpha = brightness * 0.6f) ) .clip(RoundedCornerShape(12.dp)) .background( if (brightness > 0.1f) { Brush.radialGradient( colors = listOf( Color(0xFFFFE082).copy(alpha = brightness), Color(0xFFFFD54F).copy(alpha = brightness * 0.9f), Color(0xFFFF8F00).copy(alpha = brightness * 0.7f) ), radius = 60f ) } else { Brush.linearGradient( listOf( Color(0xFF3A3A3A), Color(0xFF2A2A2A) ) ) } ), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = R.drawable.ic_light), contentDescription = "灯光", modifier = Modifier.size(24.dp), colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( if (brightness > 0.1f) { Color.White.copy(alpha = 0.95f) } else { Color(0xFF666666) } ) ) } Column { Text(text = name, color = Color.White, fontWeight = FontWeight.Medium) Text( text = "${(brightness * 100).toInt()}%", color = if (brightness > 0.1f) Color(0xFFFFD54F) else Color(0xFF9AA0A6), fontSize = 13.sp, fontWeight = if (brightness > 0.5f) FontWeight.Medium else FontWeight.Normal ) } } DotsProgress( percent = brightness, onPercentChange = { brightness = it }, enabled = true ) } } } @Composable fun AddRoomDialog( onDismiss: () -> Unit, onConfirm: (String) -> Unit ) { var roomName by remember { mutableStateOf("") } Box( modifier = Modifier .fillMaxSize() .background(Color(0x80000000)) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { onDismiss() }, contentAlignment = Alignment.Center ) { Box( modifier = Modifier .width(400.dp) .clip(RoundedCornerShape(24.dp)) .background(Color(0xFF1E1E1E)) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { /* 阻止点击穿透 */ } .padding(24.dp) ) { Column( verticalArrangement = Arrangement.spacedBy(20.dp) ) { Text( text = "添加房间", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = Color.White ) androidx.compose.material3.TextField( value = roomName, onValueChange = { roomName = it }, placeholder = { Text("请输入房间名称", color = Color(0xFF9AA0A6)) }, modifier = Modifier.fillMaxWidth(), colors = androidx.compose.material3.TextFieldDefaults.colors( focusedContainerColor = Color(0x22FFFFFF), unfocusedContainerColor = Color(0x22FFFFFF), focusedTextColor = Color.White, unfocusedTextColor = Color.White, cursorColor = Color(0xFFA9F0FF), focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent ), shape = RoundedCornerShape(12.dp), singleLine = true ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { Box( modifier = Modifier .weight(1f) .height(48.dp) .clip(RoundedCornerShape(12.dp)) .background(Color(0x22FFFFFF)) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { onDismiss() }, contentAlignment = Alignment.Center ) { Text( text = "取消", color = Color(0xFFB0B0B0), fontWeight = FontWeight.Medium ) } Box( modifier = Modifier .weight(1f) .height(48.dp) .clip(RoundedCornerShape(12.dp)) .background( Brush.linearGradient( listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF)) ) ) .clickable( indication = null, interactionSource = remember { MutableInteractionSource() }, enabled = roomName.isNotBlank() ) { if (roomName.isNotBlank()) { onConfirm(roomName.trim()) } } .alpha(if (roomName.isNotBlank()) 1f else 0.5f), contentAlignment = Alignment.Center ) { Text( text = "确定", color = Color.Black, fontWeight = FontWeight.Bold ) } } } } } }