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.* 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.* import androidx.compose.runtime.* import androidx.compose.animation.core.* 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.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.res.painterResource 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.border import com.example.smarthome.R import com.example.smarthome.data.WeatherInfo import com.example.smarthome.data.WeatherService import kotlinx.coroutines.launch import kotlinx.coroutines.delay import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import android.view.SoundEffectConstants import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.foundation.layout.offset import androidx.compose.ui.draw.rotate @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 ) // 内容区域 - 减少左侧间距 Box(modifier = Modifier.fillMaxSize()) { // 根据选中的导航项显示不同的内容 when (selectedNavItem) { 0 -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom) 1 -> SceneScreen() 2 -> AutomationScreen() 3 -> StatisticsScreen() 4 -> SecurityScreen() 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(start = 0.dp, end = 16.dp, top = 16.dp, bottom = 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(0x50121212)) } 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(0x50121212)) .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(0x50121212)).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 = {}) { val context = androidx.compose.ui.platform.LocalContext.current val selectedBg by com.example.smarthome.data.BackgroundManager.selectedBackground.collectAsState() val isDarkMode = com.example.smarthome.data.BackgroundManager.backgrounds.getOrNull(selectedBg)?.isDark ?: false Column( modifier = Modifier .width(100.dp) .fillMaxHeight() .padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp), verticalArrangement = Arrangement.SpaceBetween ) { // 上半部分:导航项 Column(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_scene, selectedNavItem == 1, onClick = { onNavItemSelect(1) }) NavRailItem("自动化", R.drawable.ic_automation, selectedNavItem == 2, onClick = { onNavItemSelect(2) }) NavRailItem("统计", R.drawable.ic_statistics, selectedNavItem == 3, onClick = { onNavItemSelect(3) }) NavRailItem("安全", R.drawable.ic_security, selectedNavItem == 4, onClick = { onNavItemSelect(4) }) NavRailItem("设置", R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) }) } // 底部:模式切换按钮(暂时注释) /* ThemeModeToggle( isDarkMode = isDarkMode, onToggle = { if (isDarkMode) { com.example.smarthome.data.BackgroundManager.setBackground(context, 0) // 丝绸白 } else { com.example.smarthome.data.BackgroundManager.setBackground(context, 2) // 紫金绸 } } ) */ } } /* @Composable fun ThemeModeToggle(isDarkMode: Boolean, onToggle: () -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .clickable( indication = null, interactionSource = remember { MutableInteractionSource() } ) { onToggle() } .padding(vertical = 4.dp) ) { Box( modifier = Modifier .size(48.dp) .clip(RoundedCornerShape(14.dp)) .background( if (isDarkMode) { Brush.linearGradient( listOf(Color(0xFF2C2C54), Color(0xFF1A1A2E)) ) } else { Brush.linearGradient( listOf(Color(0xFFFFE082), Color(0xFFFFD54F)) ) } ), contentAlignment = Alignment.Center ) { Text( text = if (isDarkMode) "🌙" else "☀️", fontSize = 20.sp ) } } } */ @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() { val context = androidx.compose.ui.platform.LocalContext.current var weatherInfo by remember { mutableStateOf(WeatherService.getSimulatedWeather()) } var isLoading by remember { mutableStateOf(true) } LaunchedEffect(Unit) { try { weatherInfo = WeatherService.getWeather(context) } catch (e: Exception) { // 使用模拟数据 } isLoading = false } Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Text(text = "控制台", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White) WeatherCard(weatherInfo = weatherInfo, isLoading = isLoading) } } @Composable fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) { // 动画:创建无限循环的偏移量 val infiniteTransition = rememberInfiniteTransition(label = "weather") val offsetAnim by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1500f, // 增加偏移量,让流动范围更大 animationSpec = infiniteRepeatable( animation = tween(durationMillis = 15000, easing = LinearEasing), // 加快一点速度 repeatMode = RepeatMode.Reverse ), label = "gradient" ) // 根据天气状况决定背景颜色 val colors = if (isLoading) { listOf( Color(0xFF2C3E50).copy(alpha = 0.6f), Color(0xFF3498DB).copy(alpha = 0.6f), Color(0xFF2C3E50).copy(alpha = 0.6f) ) } else { when { // 晴天:深橙色流动 weatherInfo.weather.contains("晴") -> listOf( Color(0xFF8B3000).copy(alpha = 0.65f), Color(0xFF9A4500).copy(alpha = 0.65f), Color(0xFF8B3000).copy(alpha = 0.65f) ) // 雨天:深紫深蓝流动 weatherInfo.weather.contains("雨") -> listOf( Color(0xFF2C3E50).copy(alpha = 0.6f), Color(0xFF4CA1AF).copy(alpha = 0.6f), Color(0xFF2C3E50).copy(alpha = 0.6f) ) // 阴天/多云:灰蓝流动 weatherInfo.weather.contains("云") || weatherInfo.weather.contains("阴") -> listOf( Color(0xFF606c88).copy(alpha = 0.6f), Color(0xFF3f4c6b).copy(alpha = 0.6f), Color(0xFF606c88).copy(alpha = 0.6f) ) // 雪天:冰蓝流动 weatherInfo.weather.contains("雪") -> listOf( Color(0xFF5a7fa4).copy(alpha = 0.6f), Color(0xFF8ad0d4).copy(alpha = 0.6f), Color(0xFF5a7fa4).copy(alpha = 0.6f) ) // 默认:蓝青流动 else -> listOf( Color(0xFF1090B0).copy(alpha = 0.6f), Color(0xFF000046).copy(alpha = 0.6f), Color(0xFF1090B0).copy(alpha = 0.6f) ) } } val backgroundBrush = Brush.linearGradient( colors = colors, start = Offset(offsetAnim, offsetAnim), end = Offset(offsetAnim + 500f, offsetAnim + 500f) ) Box( modifier = Modifier .clip(RoundedCornerShape(16.dp)) .background(backgroundBrush) // 使用动态渐变背景 ) { // 天气特效层(在背景之上,内容之下)- 使用固定大小 WeatherEffectLayer( weather = weatherInfo.weather, modifier = Modifier.size(width = 300.dp, height = 60.dp) ) Row( modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp), horizontalArrangement = Arrangement.spacedBy(20.dp), verticalAlignment = Alignment.CenterVertically ) { // 温度和天气 Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = if (isLoading) "--" else "${weatherInfo.temperature}°C", fontSize = 22.sp, fontWeight = FontWeight.Bold, color = Color.White, style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) Text( text = if (isLoading) "加载中" else weatherInfo.weather, fontSize = 14.sp, fontWeight = FontWeight.Medium, color = Color.White, style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) } // 分隔线 Box( modifier = Modifier .width(1.dp) .height(36.dp) .background(Color.White.copy(alpha = 0.3f)) ) // 湿度 Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = if (isLoading) "--" else "${weatherInfo.humidity}%", fontSize = 18.sp, fontWeight = FontWeight.SemiBold, color = Color.White, style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) Text( text = "湿度", fontSize = 13.sp, color = Color.White.copy(alpha = 0.8f), style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) } // 分隔线 Box( modifier = Modifier .width(1.dp) .height(36.dp) .background(Color.White.copy(alpha = 0.3f)) ) // 空气质量 Column(horizontalAlignment = Alignment.CenterHorizontally) { val aqiColor = when { weatherInfo.aqi <= 50 -> Color(0xFF4CAF50) weatherInfo.aqi <= 100 -> Color(0xFFFFEB3B) weatherInfo.aqi <= 150 -> Color(0xFFFF9800) else -> Color(0xFFFF5252) } Text( text = if (isLoading) "--" else weatherInfo.airQuality, fontSize = 18.sp, fontWeight = FontWeight.SemiBold, color = aqiColor, style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) Text( text = if (isLoading) "AQI" else "AQI ${weatherInfo.aqi}", fontSize = 13.sp, color = Color.White.copy(alpha = 0.8f), style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) } // 城市 Text( text = weatherInfo.city, fontSize = 15.sp, fontWeight = FontWeight.Medium, color = Color.White, style = androidx.compose.ui.text.TextStyle( shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f) ) ) } } } @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(12) { (15..45).random().toFloat() } } val maxValue = remember(values) { values.maxOrNull() ?: 45f } val totalPower = remember(roomName) { (15.0 + Math.random() * 30).toString().take(4) } val totalHours = remember(roomName) { (10 + (Math.random() * 40).toInt()) } val activeDevices = remember(roomName) { (3 + (Math.random() * 8).toInt()) } val trend = remember(roomName) { if (Math.random() > 0.5) "↑" else "↓" } val trendPercent = remember(roomName) { (5 + (Math.random() * 15).toInt()) } val costToday = remember(roomName) { String.format("%.1f", 2 + Math.random() * 8) } // 主题色 val accentColor = Color(0xFF00D1FF) Box( modifier = modifier .height(260.dp) .clip(RoundedCornerShape(24.dp)) .background( Brush.verticalGradient( colors = listOf( accentColor.copy(alpha = 0.15f), Color(0xFF1A1A2E).copy(alpha = 0.9f) ) ) ) .padding(16.dp) ) { Column( verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxSize() ) { // 标题区域 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { // 图标 Box( modifier = Modifier .size(36.dp) .clip(RoundedCornerShape(10.dp)) .background(accentColor.copy(alpha = 0.2f)), contentAlignment = Alignment.Center ) { Text(text = "⚡", fontSize = 18.sp) } Column { Text( text = "能耗统计", fontWeight = FontWeight.SemiBold, color = Color.White, fontSize = 16.sp ) Text( text = roomName, fontSize = 11.sp, color = Color(0xFF9AA0A6) ) } } // 趋势指示器 Box( modifier = Modifier .clip(RoundedCornerShape(8.dp)) .background( if (trend == "↑") Color(0x33FF5252) else Color(0x3300E676) ) .padding(horizontal = 8.dp, vertical = 4.dp) ) { Text( text = "$trend${trendPercent}%", color = if (trend == "↑") Color(0xFFFF5252) else Color(0xFF00E676), fontSize = 12.sp, fontWeight = FontWeight.Bold ) } } // 主数据显示 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.Bottom ) { // 今日用电 Column { Row(verticalAlignment = Alignment.Top) { Text( text = totalPower, fontSize = 42.sp, color = Color.White, fontWeight = FontWeight.Light ) Text( text = "kWh", fontSize = 14.sp, color = Color.White.copy(alpha = 0.6f), modifier = Modifier.padding(top = 10.dp, start = 4.dp) ) } Text( text = "今日用电", fontSize = 12.sp, color = Color(0xFF9AA0A6) ) } // 预计费用 Column(horizontalAlignment = Alignment.End) { Text( text = "¥$costToday", fontSize = 20.sp, color = accentColor, fontWeight = FontWeight.Bold ) Text( text = "预计费用", fontSize = 11.sp, color = Color(0xFF9AA0A6) ) } } // 详细数据行 Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(Color(0xFF2A2A3E).copy(alpha = 0.5f)) .padding(12.dp), horizontalArrangement = Arrangement.SpaceEvenly ) { // 运行时长 Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = "$totalHours", color = Color(0xFFB89CFF), fontSize = 18.sp, fontWeight = FontWeight.Bold ) Text(text = "小时", fontSize = 10.sp, color = Color(0xFF9AA0A6)) } // 分隔线 Box( modifier = Modifier .width(1.dp) .height(30.dp) .background(Color(0xFF3A3A4E)) ) // 活跃设备 Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = "$activeDevices", color = Color(0xFF00E676), fontSize = 18.sp, fontWeight = FontWeight.Bold ) Text(text = "设备", fontSize = 10.sp, color = Color(0xFF9AA0A6)) } // 分隔线 Box( modifier = Modifier .width(1.dp) .height(30.dp) .background(Color(0xFF3A3A4E)) ) // 峰值功率 Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( text = "${(800 + (Math.random() * 400).toInt())}", color = Color(0xFFFFB74D), fontSize = 18.sp, fontWeight = FontWeight.Bold ) Text(text = "W峰值", fontSize = 10.sp, color = Color(0xFF9AA0A6)) } } // 图表区域 Box( modifier = Modifier .fillMaxWidth() .weight(1f) ) { Row( modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.Bottom ) { values.forEachIndexed { index, v -> val normalizedHeight = (v / maxValue) * 50 val isHighlight = index == values.size - 1 Box( modifier = Modifier .weight(1f) .height(normalizedHeight.dp) .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)) .background( if (isHighlight) { Brush.verticalGradient( colors = listOf(accentColor, accentColor.copy(alpha = 0.5f)) ) } else { Brush.verticalGradient( colors = listOf(Color(0xFF4A4A5A), Color(0xFF3A3A4A)) ) } ) ) } } } } } } @Composable fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间") { var temp by remember { mutableStateOf(24f) } var isOn by remember { mutableStateOf(true) } var selectedMode by remember { mutableStateOf(0) } // 0:制冷 1:制热 2:除湿 3:送风 var fanSpeed by remember { mutableStateOf(1) } // 0:自动 1:低 2:中 3:高 // 模式emoji图标 val modeIcons = listOf( "❄️" to "制冷", "☀️" to "制热", "💧" to "除湿", "🌀" to "送风" ) val fanSpeeds = listOf("自动", "低", "中", "高") // 图标动画 val infiniteTransition = rememberInfiniteTransition(label = "ac_icon") // 旋转动画(制冷、制热、送风) val iconRotation by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( animation = tween(durationMillis = 3000, easing = LinearEasing), repeatMode = RepeatMode.Restart ), label = "icon_rotation" ) // 水滴下落动画(除湿) val dropOffset by infiniteTransition.animateFloat( initialValue = -2f, targetValue = 2f, animationSpec = infiniteRepeatable( animation = tween(durationMillis = 800, easing = androidx.compose.animation.core.EaseInOut), repeatMode = RepeatMode.Reverse ), label = "drop_offset" ) // 根据模式决定主题色 val accentColor = when (selectedMode) { 0 -> Color(0xFF4FC3F7) // 制冷 - 蓝色 1 -> Color(0xFFFF8A65) // 制热 - 橙色 2 -> Color(0xFF81C784) // 除湿 - 绿色 else -> Color(0xFFB0BEC5) // 送风 - 灰色 } // 当前模式的emoji val currentEmoji = modeIcons[selectedMode].first Box( modifier = modifier .height(260.dp) .clip(RoundedCornerShape(24.dp)) .background( Brush.verticalGradient( colors = if (isOn) listOf( accentColor.copy(alpha = 0.3f), Color(0xFF1A1A2E).copy(alpha = 0.9f) ) else listOf( Color(0xFF2A2A3E).copy(alpha = 0.6f), Color(0xFF1A1A2E).copy(alpha = 0.9f) ) ) ) .padding(16.dp) ) { Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { // 顶部:标题和开关 Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { // 空调图标 - 根据模式应用不同动画 Box( modifier = Modifier .size(36.dp) .clip(RoundedCornerShape(10.dp)) .background(accentColor.copy(alpha = if (isOn) 0.3f else 0.1f)), contentAlignment = Alignment.Center ) { // 根据模式选择动画:除湿用上下移动,其他用旋转 val emojiModifier = when { !isOn -> Modifier selectedMode == 2 -> Modifier.offset(y = dropOffset.dp) // 除湿:上下移动 else -> Modifier.rotate(iconRotation) // 其他:旋转 } Text( text = currentEmoji, fontSize = 18.sp, modifier = emojiModifier ) } Column { Text(text = "空调", fontWeight = FontWeight.SemiBold, color = Color.White, fontSize = 16.sp) Text(text = roomName, fontSize = 11.sp, color = Color(0xFF9AA0A6)) } } Switch( checked = isOn, onCheckedChange = { isOn = it }, colors = SwitchDefaults.colors( checkedThumbColor = Color.White, checkedTrackColor = accentColor, uncheckedThumbColor = Color.Gray, uncheckedTrackColor = Color(0xFF3A3A4E) ) ) } // 温度显示和调节 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { // 大温度显示 Row(verticalAlignment = Alignment.Top) { Text( text = "${temp.toInt()}", fontSize = 52.sp, color = if (isOn) Color.White else Color.Gray, fontWeight = FontWeight.Light ) Text( text = "°C", fontSize = 20.sp, color = if (isOn) Color.White.copy(alpha = 0.7f) else Color.Gray, modifier = Modifier.padding(top = 8.dp) ) } // 温度调节按钮 Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { // 升温按钮 Box( modifier = Modifier .size(36.dp) .clip(RoundedCornerShape(10.dp)) .background(if (isOn) accentColor.copy(alpha = 0.3f) else Color(0xFF3A3A4E)) .clickable(enabled = isOn && temp < 32f) { temp += 1f }, contentAlignment = Alignment.Center ) { Text(text = "▲", color = if (isOn) Color.White else Color.Gray, fontSize = 14.sp) } // 降温按钮 Box( modifier = Modifier .size(36.dp) .clip(RoundedCornerShape(10.dp)) .background(if (isOn) accentColor.copy(alpha = 0.3f) else Color(0xFF3A3A4E)) .clickable(enabled = isOn && temp > 16f) { temp -= 1f }, contentAlignment = Alignment.Center ) { Text(text = "▼", color = if (isOn) Color.White else Color.Gray, fontSize = 14.sp) } } } // 模式选择 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { modeIcons.forEachIndexed { index, (emoji, name) -> val isSelected = selectedMode == index val modeColor = when (index) { 0 -> Color(0xFF4FC3F7) 1 -> Color(0xFFFF8A65) 2 -> Color(0xFF81C784) else -> Color(0xFFB0BEC5) } Box( modifier = Modifier .weight(1f) .height(44.dp) .clip(RoundedCornerShape(12.dp)) .background( if (isSelected && isOn) modeColor.copy(alpha = 0.4f) else Color(0xFF2A2A3E).copy(alpha = 0.6f) ) .clickable(enabled = isOn) { selectedMode = index }, contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(text = emoji, fontSize = 14.sp) Text( text = name, fontSize = 10.sp, color = if (isSelected && isOn) Color.White else Color(0xFF9AA0A6) ) } } } } // 风速选择 Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically ) { Text(text = "风速", fontSize = 12.sp, color = Color(0xFF9AA0A6)) fanSpeeds.forEachIndexed { index, speed -> val isSelected = fanSpeed == index Box( modifier = Modifier .weight(1f) .height(28.dp) .clip(RoundedCornerShape(8.dp)) .background( if (isSelected && isOn) accentColor.copy(alpha = 0.5f) else Color(0xFF2A2A3E).copy(alpha = 0.4f) ) .clickable(enabled = isOn) { fanSpeed = index }, contentAlignment = Alignment.Center ) { Text( text = speed, fontSize = 11.sp, color = if (isSelected && isOn) Color.White else 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(0x55FFFFFF), disabledThumbColor = Color(0x66FFFFFF), disabledActiveTrackColor = Color(0x55FFFFFF), disabledInactiveTrackColor = Color(0x55FFFFFF) ), 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(0x55FFFFFF) else Color(0x55FFFFFF)) ) // 激活轨道 Box( modifier = Modifier .fillMaxWidth(fraction) .fillMaxHeight() .background( if (enabled) { Brush.linearGradient( listOf(Color(0xFF00D1FF), Color(0xFFA9F0FF)) ) } else { Brush.linearGradient( listOf(Color(0x55FFFFFF), Color(0x55FFFFFF)) ) } ) ) } } ) } } @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(0x50121212)).padding(12.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) { Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x50121212))) 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) { val dotWidth = 8.dp.toPx() val spacing = 4.dp.toPx() val totalWidth = (dotWidth + spacing) * total - spacing // 支持拖动手势 detectHorizontalDragGestures( onDragStart = { offset -> // 开始拖动时立即更新 val newPercent = (offset.x / totalWidth).coerceIn(0f, 1f) onPercentChange(newPercent) }, onDragEnd = { }, onDragCancel = { }, onHorizontalDrag = { change, _ -> change.consume() val newPercent = (change.position.x / totalWidth).coerceIn(0f, 1f) onPercentChange(newPercent) } ) } } .pointerInput(enabled) { if (enabled) { val dotWidth = 8.dp.toPx() val spacing = 4.dp.toPx() val totalWidth = (dotWidth + spacing) * total - spacing // 也支持点击 detectTapGestures { offset -> val newPercent = (offset.x / totalWidth).coerceIn(0f, 1f) 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)) ) } } } 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 iconRes = when (text) { "回家" -> R.drawable.ic_mode_home "出门" -> R.drawable.ic_mode_away "玩乐" -> R.drawable.ic_mode_fun else -> R.drawable.ic_dashboard } 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 iconScale by androidx.compose.animation.core.animateFloatAsState( targetValue = if (selected) 1.1f else 1f, animationSpec = androidx.compose.animation.core.spring( dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy ), label = "icon_scale" ) 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) .clip(shape) .then( if (selected) { Modifier .shadow(elevation, shape, clip = false) .background(brush, shape) .border( width = 2.dp, brush = Brush.linearGradient( listOf( Color(0x99FFFFFF), Color(0x66FFFFFF) ) ), shape = shape ) } else { Modifier.background(Color(0x50121212)) } ) .clickable(indication = null, interactionSource = interaction) { view.playSoundEffect(SoundEffectConstants.CLICK) onClick() } .padding(24.dp), contentAlignment = Alignment.Center ) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp) ) { // 图标容器 Box( modifier = Modifier .size(if (selected) 72.dp else 64.dp) .scale(iconScale), contentAlignment = Alignment.Center ) { // 外层光晕效果(仅选中时) if (selected) { Box( modifier = Modifier .fillMaxSize() .clip(RoundedCornerShape(20.dp)) .background( Brush.radialGradient( colors = listOf( Color.White.copy(alpha = 0.2f), Color.Transparent ), radius = 100f ) ) ) } // 图标背景 Box( modifier = Modifier .size(if (selected) 64.dp else 56.dp) .clip(RoundedCornerShape(18.dp)) .background( if (selected) { Brush.linearGradient( colors = listOf( Color.White.copy(alpha = 0.25f), Color.White.copy(alpha = 0.15f) ) ) } else { Brush.linearGradient( colors = listOf( Color(0x55FFFFFF), Color(0x55FFFFFF) ) ) } ) .then( if (selected) { Modifier.border( width = 1.dp, brush = Brush.linearGradient( colors = listOf( Color.White.copy(alpha = 0.4f), Color.White.copy(alpha = 0.1f) ) ), shape = RoundedCornerShape(18.dp) ) } else { Modifier } ), contentAlignment = Alignment.Center ) { Image( painter = painterResource(id = iconRes), contentDescription = text, modifier = Modifier.size(if (selected) 38.dp else 34.dp), colorFilter = androidx.compose.ui.graphics.ColorFilter.tint( Color.White ) ) } } // 文字 Text( text = text, color = Color.White, fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium, fontSize = if (selected) 20.sp else 18.sp, style = MaterialTheme.typography.titleMedium ) } } } @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(0x50121212)).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 WeatherEffectLayer(weather: String, modifier: Modifier = Modifier) { val infiniteTransition = rememberInfiniteTransition(label = "weather_effect") // 太阳旋转动画 val sunRotation by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( animation = tween(20000, easing = LinearEasing) ), label = "sun_rotation" ) // 雨雪下落动画 - 使用更长的周期避免明显停顿 val dropOffset by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, animationSpec = infiniteRepeatable( animation = tween(8000, easing = LinearEasing), repeatMode = RepeatMode.Restart ), label = "drop_offset" ) // 云朵飘动动画 val cloudOffset by infiniteTransition.animateFloat( initialValue = -20f, targetValue = 20f, animationSpec = infiniteRepeatable( animation = tween(5000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "cloud_offset" ) androidx.compose.foundation.Canvas(modifier = modifier) { val width = size.width val height = size.height when { // 晴天:绘制旋转的太阳 weather.contains("晴") -> { // 太阳核心 val sunCenterX = width - 25.dp.toPx() val sunCenterY = 25.dp.toPx() drawCircle( color = Color(0xFFFFD700).copy(alpha = 0.6f), radius = 18.dp.toPx(), center = Offset(sunCenterX, sunCenterY) ) // 太阳光芒 - 使用数学计算位置 val rayLength = 10.dp.toPx() val rayStart = 20.dp.toPx() for (i in 0 until 8) { val angle = Math.toRadians((sunRotation + i * 45f).toDouble()) val startX = sunCenterX + kotlin.math.cos(angle).toFloat() * rayStart val startY = sunCenterY + kotlin.math.sin(angle).toFloat() * rayStart val endX = sunCenterX + kotlin.math.cos(angle).toFloat() * (rayStart + rayLength) val endY = sunCenterY + kotlin.math.sin(angle).toFloat() * (rayStart + rayLength) drawLine( color = Color(0xFFFFD700).copy(alpha = 0.5f), start = Offset(startX, startY), end = Offset(endX, endY), strokeWidth = 2.dp.toPx() ) } } // 雨天:绘制下落的雨滴 weather.contains("雨") -> { val dropCount = 20 val dropLength = 10.dp.toPx() for (i in 0 until dropCount) { // 使用不同的起始位置和速度让雨滴错开 val xOffset = (i * 37 % 100) / 100f val x = width * xOffset val speedFactor = 3f + (i % 5) * 0.5f // 更快的速度,一个周期内多次循环 val startOffset = (i * 73 % 100) / 100f // 不同起始位置 val currentY = ((startOffset + dropOffset * speedFactor) % 1f) * height drawLine( color = Color.White.copy(alpha = 0.35f), start = Offset(x, currentY), end = Offset(x, currentY + dropLength), // 垂直下落 strokeWidth = 1.5f.dp.toPx() ) } } // 雪天:绘制飘落的雪花(圆点) weather.contains("雪") -> { val snowCount = 18 for (i in 0 until snowCount) { // 使用不同的起始位置和速度让雪花错开 val xOffset = (i * 41 % 100) / 100f val x = width * xOffset val speedFactor = 1.5f + (i % 4) * 0.3f // 雪花下落更慢 val startOffset = (i * 67 % 100) / 100f val currentY = ((startOffset + dropOffset * speedFactor) % 1f) * height drawCircle( color = Color.White.copy(alpha = 0.5f), radius = (1.5f + i % 2).dp.toPx(), center = Offset(x, currentY) ) } } // 多云/阴天:绘制云朵 weather.contains("云") || weather.contains("阴") -> { // 绘制几个叠加的圆组成云 - 缩小尺寸 val cloudColor = Color.White.copy(alpha = 0.15f) val baseX = width - 35.dp.toPx() + cloudOffset * 0.5f val baseY = 25.dp.toPx() drawCircle(color = cloudColor, radius = 15.dp.toPx(), center = Offset(baseX, baseY)) drawCircle(color = cloudColor, radius = 12.dp.toPx(), center = Offset(baseX - 18.dp.toPx(), baseY + 5.dp.toPx())) drawCircle(color = cloudColor, radius = 14.dp.toPx(), center = Offset(baseX + 15.dp.toPx(), baseY + 3.dp.toPx())) drawCircle(color = cloudColor, radius = 10.dp.toPx(), center = Offset(baseX - 8.dp.toPx(), baseY - 8.dp.toPx())) } } } } @Composable fun AddRoomDialog( onDismiss: () -> Unit, onConfirm: (String) -> Unit ) { var roomName by remember { mutableStateOf("") } Box( modifier = Modifier .fillMaxSize() .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(0x55FFFFFF), unfocusedContainerColor = Color(0x55FFFFFF), 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(0x55FFFFFF)) .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 ) } } } } } }