111/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt

2188 lines
86 KiB
Kotlin
Raw Normal View History

package com.example.smarthome.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
2025-11-27 04:32:01 +00:00
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
2025-11-27 04:32:01 +00:00
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
2025-11-27 04:32:01 +00:00
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
2025-11-27 04:32:01 +00:00
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
2025-11-27 04:32:01 +00:00
import com.example.smarthome.R
import com.example.smarthome.data.WeatherInfo
import com.example.smarthome.data.WeatherService
2025-11-27 08:16:34 +00:00
import com.example.smarthome.data.LanguageManager
import com.example.smarthome.data.tr
2025-11-27 04:32:01 +00:00
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
2025-11-27 06:15:31 +00:00
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<String> = 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<String> = 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)) {
2025-11-27 08:16:34 +00:00
AirConditionerCard(modifier = Modifier.weight(1f), roomName = tr("room_all"))
UsageStatusChart(modifier = Modifier.weight(1f), roomName = tr("room_all"))
}
2025-11-27 08:16:34 +00:00
LightRow(modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), roomName = tr("room_all"))
ModeButtonsRow(onModeSelected = { })
2025-11-26 03:43:39 +00:00
Spacer(modifier = Modifier.height(24.dp))
// 显示所有房间的设备摘要
Text(
text = "所有设备",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
2025-11-26 03:43:39 +00:00
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)
}
2025-11-26 03:43:39 +00:00
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,
2025-11-26 03:43:39 +00:00
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<String> = 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)
}
)
}
}
// 固定的添加按钮
2025-11-27 08:00:44 +00:00
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 {
2025-11-27 02:56:42 +00:00
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) }
2025-11-27 02:56:42 +00:00
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() {
2025-11-27 02:56:42 +00:00
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 = {}) {
2025-11-27 08:00:44 +00:00
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
) {
// 上半部分:导航项
2025-11-27 08:16:34 +00:00
// 监听语言变化以触发重组
val currentLang by LanguageManager.currentLanguage.collectAsState()
2025-11-27 08:00:44 +00:00
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Spacer(modifier = Modifier.height(48.dp))
2025-11-27 08:16:34 +00:00
NavRailItem(tr("nav_dashboard"), R.drawable.ic_dashboard, selectedNavItem == 0, onClick = { onNavItemSelect(0) })
NavRailItem(tr("nav_scene"), R.drawable.ic_scene, selectedNavItem == 1, onClick = { onNavItemSelect(1) })
NavRailItem(tr("nav_automation"), R.drawable.ic_automation, selectedNavItem == 2, onClick = { onNavItemSelect(2) })
NavRailItem(tr("nav_statistics"), R.drawable.ic_statistics, selectedNavItem == 3, onClick = { onNavItemSelect(3) })
NavRailItem(tr("nav_security"), R.drawable.ic_security, selectedNavItem == 4, onClick = { onNavItemSelect(4) })
NavRailItem(tr("nav_settings"), R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) })
2025-11-27 08:00:44 +00:00
}
// 底部:模式切换按钮(暂时注释)
/*
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
)
}
}
}
2025-11-27 08:00:44 +00:00
*/
@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
2025-11-27 08:16:34 +00:00
.width(80.dp) // 固定宽度,防止文字长度不一致导致图标不对齐
.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),
2025-11-27 08:16:34 +00:00
fontSize = 12.sp,
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
2025-11-27 08:16:34 +00:00
maxLines = 1,
textAlign = TextAlign.Center
)
}
}
@Composable
fun TopBar() {
2025-11-27 04:32:01 +00:00
val context = androidx.compose.ui.platform.LocalContext.current
2025-11-27 02:56:42 +00:00
var weatherInfo by remember { mutableStateOf(WeatherService.getSimulatedWeather()) }
var isLoading by remember { mutableStateOf(true) }
2025-11-28 05:21:09 +00:00
// 获取用户信息
val userInfo by com.example.smarthome.data.UserManager.userInfo.collectAsState()
2025-11-27 02:56:42 +00:00
LaunchedEffect(Unit) {
try {
2025-11-27 04:32:01 +00:00
weatherInfo = WeatherService.getWeather(context)
2025-11-27 02:56:42 +00:00
} catch (e: Exception) {
// 使用模拟数据
}
isLoading = false
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
2025-11-27 08:16:34 +00:00
Text(text = tr("dashboard_title"), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White)
2025-11-28 05:21:09 +00:00
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
WeatherCard(weatherInfo = weatherInfo, isLoading = isLoading)
// 用户头像
UserAvatar(
nickname = userInfo.nickname,
avatarUrl = userInfo.avatarUrl,
onClick = { /* 点击头像可以打开用户菜单 */ }
)
}
}
}
@Composable
fun UserAvatar(
nickname: String,
avatarUrl: String,
onClick: () -> Unit
) {
val context = androidx.compose.ui.platform.LocalContext.current
Box(
modifier = Modifier
.size(40.dp)
.shadow(8.dp, androidx.compose.foundation.shape.CircleShape)
.clip(androidx.compose.foundation.shape.CircleShape)
.background(
Brush.linearGradient(
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
)
)
.border(
width = 2.dp,
brush = Brush.linearGradient(
listOf(Color.White.copy(alpha = 0.5f), Color.White.copy(alpha = 0.2f))
),
shape = androidx.compose.foundation.shape.CircleShape
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onClick() },
contentAlignment = Alignment.Center
) {
if (avatarUrl.isNotEmpty()) {
// 如果有头像URL可以用Coil加载网络图片
// 这里暂时用首字母代替
Text(
text = nickname.firstOrNull()?.uppercase() ?: "U",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
} else {
// 显示昵称首字母
Text(
text = nickname.firstOrNull()?.uppercase() ?: "U",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
}
}
2025-11-27 02:56:42 +00:00
@Composable
fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) {
2025-11-27 04:32:01 +00:00
// 动画:创建无限循环的偏移量
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"
)
2025-11-27 04:45:22 +00:00
// 根据天气状况决定背景颜色
2025-11-27 04:32:01 +00:00
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 {
2025-11-27 04:45:22 +00:00
// 晴天:深橙色流动
2025-11-27 04:32:01 +00:00
weatherInfo.weather.contains("") ->
listOf(
2025-11-27 04:45:22 +00:00
Color(0xFF8B3000).copy(alpha = 0.65f),
Color(0xFF9A4500).copy(alpha = 0.65f),
Color(0xFF8B3000).copy(alpha = 0.65f)
2025-11-27 04:32:01 +00:00
)
// 雨天:深紫深蓝流动
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)
)
2025-11-27 04:45:22 +00:00
2025-11-27 02:56:42 +00:00
Box(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
2025-11-27 04:32:01 +00:00
.background(backgroundBrush) // 使用动态渐变背景
2025-11-27 02:56:42 +00:00
) {
2025-11-27 04:32:01 +00:00
// 天气特效层(在背景之上,内容之下)- 使用固定大小
WeatherEffectLayer(
weather = weatherInfo.weather,
modifier = Modifier.size(width = 300.dp, height = 60.dp)
)
2025-11-27 02:56:42 +00:00
Row(
2025-11-27 04:32:01 +00:00
modifier = Modifier.padding(horizontal = 20.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.spacedBy(20.dp),
2025-11-27 02:56:42 +00:00
verticalAlignment = Alignment.CenterVertically
) {
// 温度和天气
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = if (isLoading) "--" else "${weatherInfo.temperature}°C",
2025-11-27 04:32:01 +00:00
fontSize = 22.sp,
2025-11-27 02:56:42 +00:00
fontWeight = FontWeight.Bold,
2025-11-27 04:32:01 +00:00
color = Color.White,
style = androidx.compose.ui.text.TextStyle(
shadow = Shadow(color = Color.Black.copy(alpha = 0.3f), blurRadius = 4f)
)
2025-11-27 02:56:42 +00:00
)
Text(
text = if (isLoading) "加载中" else weatherInfo.weather,
2025-11-27 04:32:01 +00:00
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)
)
2025-11-27 02:56:42 +00:00
)
}
// 分隔线
Box(
modifier = Modifier
.width(1.dp)
2025-11-27 04:32:01 +00:00
.height(36.dp)
.background(Color.White.copy(alpha = 0.3f))
2025-11-27 02:56:42 +00:00
)
// 湿度
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = if (isLoading) "--" else "${weatherInfo.humidity}%",
2025-11-27 04:32:01 +00:00
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)
)
2025-11-27 02:56:42 +00:00
)
Text(
text = "湿度",
2025-11-27 04:32:01 +00:00
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)
)
2025-11-27 02:56:42 +00:00
)
}
// 分隔线
Box(
modifier = Modifier
.width(1.dp)
2025-11-27 04:32:01 +00:00
.height(36.dp)
.background(Color.White.copy(alpha = 0.3f))
2025-11-27 02:56:42 +00:00
)
// 空气质量
Column(horizontalAlignment = Alignment.CenterHorizontally) {
val aqiColor = when {
2025-11-27 04:32:01 +00:00
weatherInfo.aqi <= 50 -> Color(0xFF4CAF50)
2025-11-27 02:56:42 +00:00
weatherInfo.aqi <= 100 -> Color(0xFFFFEB3B)
weatherInfo.aqi <= 150 -> Color(0xFFFF9800)
else -> Color(0xFFFF5252)
}
Text(
text = if (isLoading) "--" else weatherInfo.airQuality,
2025-11-27 04:32:01 +00:00
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)
)
2025-11-27 02:56:42 +00:00
)
Text(
text = if (isLoading) "AQI" else "AQI ${weatherInfo.aqi}",
2025-11-27 04:32:01 +00:00
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)
)
2025-11-27 02:56:42 +00:00
)
}
// 城市
Text(
text = weatherInfo.city,
2025-11-27 04:32:01 +00:00
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)
)
2025-11-27 02:56:42 +00:00
)
}
}
}
@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) {
2025-11-27 02:56:42 +00:00
List(12) { (15..45).random().toFloat() }
}
2025-11-27 02:56:42 +00:00
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()) }
2025-11-27 02:56:42 +00:00
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)
2025-11-27 02:56:42 +00:00
Box(
modifier = modifier
.height(260.dp)
2025-11-27 02:56:42 +00:00
.clip(RoundedCornerShape(24.dp))
.background(
Brush.verticalGradient(
2025-11-27 02:56:42 +00:00
colors = listOf(
accentColor.copy(alpha = 0.15f),
Color(0xFF1A1A2E).copy(alpha = 0.9f)
2025-11-27 02:56:42 +00:00
)
)
)
.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)
)
}
2025-11-27 02:56:42 +00:00
}
// 趋势指示器
Box(
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
2025-11-27 02:56:42 +00:00
.background(
if (trend == "") Color(0x33FF5252) else Color(0x3300E676)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
2025-11-27 02:56:42 +00:00
) {
Text(
text = "$trend${trendPercent}%",
color = if (trend == "") Color(0xFFFF5252) else Color(0xFF00E676),
fontSize = 12.sp,
fontWeight = FontWeight.Bold
)
2025-11-27 02:56:42 +00:00
}
}
2025-11-27 02:56:42 +00:00
// 主数据显示
2025-11-27 02:56:42 +00:00
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom
2025-11-27 02:56:42 +00:00
) {
// 今日用电
Column {
Row(verticalAlignment = Alignment.Top) {
2025-11-27 02:56:42 +00:00
Text(
text = totalPower,
fontSize = 42.sp,
color = Color.White,
fontWeight = FontWeight.Light
2025-11-27 02:56:42 +00:00
)
Text(
text = "kWh",
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.6f),
modifier = Modifier.padding(top = 10.dp, start = 4.dp)
2025-11-27 02:56:42 +00:00
)
}
Text(
text = "今日用电",
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
2025-11-27 02:56:42 +00:00
)
}
2025-11-27 02:56:42 +00:00
// 预计费用
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
) {
2025-11-27 02:56:42 +00:00
// 运行时长
Column(horizontalAlignment = Alignment.CenterHorizontally) {
2025-11-27 02:56:42 +00:00
Text(
text = "$totalHours",
color = Color(0xFFB89CFF),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
2025-11-27 02:56:42 +00:00
)
Text(text = "小时", fontSize = 10.sp, color = Color(0xFF9AA0A6))
2025-11-27 02:56:42 +00:00
}
// 分隔线
Box(
modifier = Modifier
.width(1.dp)
.height(30.dp)
.background(Color(0xFF3A3A4E))
)
2025-11-27 02:56:42 +00:00
// 活跃设备
Column(horizontalAlignment = Alignment.CenterHorizontally) {
2025-11-27 02:56:42 +00:00
Text(
text = "$activeDevices",
color = Color(0xFF00E676),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
2025-11-27 02:56:42 +00:00
)
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))
}
}
2025-11-27 02:56:42 +00:00
// 图表区域
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
horizontalArrangement = Arrangement.spacedBy(4.dp),
2025-11-27 02:56:42 +00:00
verticalAlignment = Alignment.Bottom
) {
values.forEachIndexed { index, v ->
val normalizedHeight = (v / maxValue) * 50
2025-11-27 02:56:42 +00:00
val isHighlight = index == values.size - 1
Box(
modifier = Modifier
.weight(1f)
.height(normalizedHeight.dp)
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))
2025-11-27 02:56:42 +00:00
.background(
if (isHighlight) {
Brush.verticalGradient(
colors = listOf(accentColor, accentColor.copy(alpha = 0.5f))
2025-11-27 02:56:42 +00:00
)
} else {
Brush.verticalGradient(
colors = listOf(Color(0xFF4A4A5A), Color(0xFF3A3A4A))
2025-11-27 02:56:42 +00:00
)
}
)
)
}
}
}
}
}
}
@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:高
2025-11-27 08:00:44 +00:00
// 模式emoji图标
val modeIcons = listOf(
2025-11-27 08:16:34 +00:00
"❄️" to tr("ac_mode_cool"),
"☀️" to tr("ac_mode_heat"),
"💧" to tr("ac_mode_dry"),
"🌀" to tr("ac_mode_fan")
2025-11-27 08:00:44 +00:00
)
2025-11-28 05:21:09 +00:00
// 风速旋转速度(根据档位变化)
val fanRotationDuration = when (fanSpeed) {
0 -> 2000 // 自动
1 -> 3000 // 低速
2 -> 1500 // 中速
else -> 800 // 高速
}
2025-11-27 08:00:44 +00:00
// 图标动画
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"
)
2025-11-28 05:21:09 +00:00
// 风扇旋转动画
val fanRotation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = fanRotationDuration, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "fan_rotation"
)
// 风速标签
val fanSpeedLabel = when (fanSpeed) {
0 -> tr("ac_fan_auto")
1 -> tr("ac_fan_low")
2 -> tr("ac_fan_medium")
else -> tr("ac_fan_high")
}
// 根据模式决定主题色
val accentColor = when (selectedMode) {
0 -> Color(0xFF4FC3F7) // 制冷 - 蓝色
1 -> Color(0xFFFF8A65) // 制热 - 橙色
2 -> Color(0xFF81C784) // 除湿 - 绿色
else -> Color(0xFFB0BEC5) // 送风 - 灰色
}
2025-11-27 08:00:44 +00:00
// 当前模式的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)) {
2025-11-27 08:00:44 +00:00
// 空调图标 - 根据模式应用不同动画
Box(
modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(10.dp))
.background(accentColor.copy(alpha = if (isOn) 0.3f else 0.1f)),
contentAlignment = Alignment.Center
) {
2025-11-27 08:00:44 +00:00
// 根据模式选择动画:除湿用上下移动,其他用旋转
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 {
2025-11-27 08:16:34 +00:00
Text(text = tr("ac_title"), 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)
)
)
}
2025-11-26 03:43:39 +00:00
// 温度显示和调节
2025-11-26 03:43:39 +00:00
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)
)
}
2025-11-26 03:43:39 +00:00
2025-11-28 05:21:09 +00:00
// 风速按钮 + 温度调节按钮
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 风速按钮(点击循环切换)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
2025-11-28 05:21:09 +00:00
.clip(RoundedCornerShape(12.dp))
.background(if (isOn) accentColor.copy(alpha = 0.2f) else Color(0xFF3A3A4E))
.clickable(enabled = isOn) { fanSpeed = (fanSpeed + 1) % 4 }
.padding(horizontal = 12.dp, vertical = 8.dp)
) {
2025-11-28 05:21:09 +00:00
// 风扇图标(根据风速显示不同图片,带旋转动画)
val fanIconRes = when (fanSpeed) {
1 -> R.drawable.fan_low // 低速 - 3叶片
2 -> R.drawable.fan_medium // 中速 - 4叶片
3 -> R.drawable.fan_high // 高速 - 5叶片
else -> R.drawable.fan_medium // 自动 - 默认4叶片
}
Image(
painter = painterResource(id = fanIconRes),
contentDescription = "Fan Speed",
modifier = Modifier
.size(24.dp)
.then(if (isOn) Modifier.rotate(fanRotation) else Modifier)
)
Text(
text = fanSpeedLabel,
fontSize = 10.sp,
color = if (isOn) Color.White else Color.Gray,
maxLines = 1
)
}
2025-11-28 05:21:09 +00:00
// 温度调节按钮
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)
}
2025-11-26 03:43:39 +00:00
}
}
}
// 模式选择
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
2025-11-27 08:00:44 +00:00
modeIcons.forEachIndexed { index, (emoji, name) ->
val isSelected = selectedMode == index
2025-11-27 08:00:44 +00:00
val modeColor = when (index) {
0 -> Color(0xFF4FC3F7)
1 -> Color(0xFFFF8A65)
2 -> Color(0xFF81C784)
else -> Color(0xFFB0BEC5)
}
2025-11-26 03:43:39 +00:00
Box(
modifier = Modifier
.weight(1f)
2025-11-28 05:21:09 +00:00
.heightIn(min = 44.dp)
2025-11-26 03:43:39 +00:00
.clip(RoundedCornerShape(12.dp))
.background(
2025-11-27 08:00:44 +00:00
if (isSelected && isOn) modeColor.copy(alpha = 0.4f)
else Color(0xFF2A2A3E).copy(alpha = 0.6f)
)
2025-11-28 05:21:09 +00:00
.clickable(enabled = isOn) { selectedMode = index }
.padding(vertical = 4.dp),
2025-11-26 03:43:39 +00:00
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
2025-11-27 08:00:44 +00:00
Text(text = emoji, fontSize = 14.sp)
Text(
text = name,
fontSize = 10.sp,
2025-11-28 05:21:09 +00:00
color = if (isSelected && isOn) Color.White else Color(0xFF9AA0A6),
maxLines = 1
)
}
2025-11-26 03:43:39 +00:00
}
}
}
}
}
}
2025-11-26 03:43:39 +00:00
@OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
@Composable
fun CustomSlider(
value: Float,
onValueChange: (Float) -> Unit,
valueRange: ClosedFloatingPointRange<Float>,
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) },
2025-11-27 02:56:42 +00:00
inactiveTrackColor = Color(0x55FFFFFF),
2025-11-26 03:43:39 +00:00
disabledThumbColor = Color(0x66FFFFFF),
2025-11-27 02:56:42 +00:00
disabledActiveTrackColor = Color(0x55FFFFFF),
disabledInactiveTrackColor = Color(0x55FFFFFF)
2025-11-26 03:43:39 +00:00
),
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()
2025-11-27 02:56:42 +00:00
.background(if (enabled) Color(0x55FFFFFF) else Color(0x55FFFFFF))
2025-11-26 03:43:39 +00:00
)
// 激活轨道
Box(
modifier = Modifier
.fillMaxWidth(fraction)
.fillMaxHeight()
.background(
if (enabled) {
Brush.linearGradient(
listOf(Color(0xFF00D1FF), Color(0xFFA9F0FF))
)
} else {
Brush.linearGradient(
2025-11-27 02:56:42 +00:00
listOf(Color(0x55FFFFFF), Color(0x55FFFFFF))
2025-11-26 03:43:39 +00:00
)
}
)
)
}
}
)
}
}
@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) ->
2025-11-27 02:56:42 +00:00
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) {
2025-11-27 02:56:42 +00:00
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
2025-11-26 03:43:39 +00:00
fun DotsProgress(percent: Float, onPercentChange: (Float) -> Unit = {}, enabled: Boolean = true) {
val total = 12
2025-11-27 06:15:31 +00:00
2025-11-26 03:43:39 +00:00
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
2025-11-27 06:15:31 +00:00
modifier = Modifier
.pointerInput(enabled) {
if (enabled) {
val dotWidth = 8.dp.toPx()
2025-11-26 03:43:39 +00:00
val spacing = 4.dp.toPx()
val totalWidth = (dotWidth + spacing) * total - spacing
2025-11-27 06:15:31 +00:00
// 支持拖动手势
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)
}
2025-11-26 03:43:39 +00:00
}
}
) {
repeat(total) { i ->
val filled = i < (percent * total).toInt()
2025-11-26 03:43:39 +00:00
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<Mode?>(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) }
2025-11-27 02:56:42 +00:00
// 根据文本确定图标
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"
)
2025-11-27 02:56:42 +00:00
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)
2025-11-27 02:56:42 +00:00
.clip(shape)
.then(
if (selected) {
2025-11-27 02:56:42 +00:00
Modifier
.shadow(elevation, shape, clip = false)
.background(brush, shape)
.border(
width = 2.dp,
brush = Brush.linearGradient(
listOf(
Color(0x99FFFFFF),
Color(0x66FFFFFF)
)
),
shape = shape
)
} else {
2025-11-27 02:56:42 +00:00
Modifier.background(Color(0x50121212))
}
)
.clickable(indication = null, interactionSource = interaction) {
view.playSoundEffect(SoundEffectConstants.CLICK)
onClick()
}
.padding(24.dp),
contentAlignment = Alignment.Center
) {
2025-11-27 02:56:42 +00:00
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) {
2025-11-26 03:43:39 +00:00
var brightness by remember { mutableStateOf(percent) }
2025-11-27 02:56:42 +00:00
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) {
2025-11-26 03:43:39 +00:00
// 灯光图标 - 带发光效果
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
)
}
}
2025-11-26 03:43:39 +00:00
DotsProgress(
percent = brightness,
onPercentChange = { brightness = it },
enabled = true
)
}
}
}
2025-11-27 04:32:01 +00:00
@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"
)
2025-11-27 04:45:22 +00:00
// 雨雪下落动画 - 使用更长的周期避免明显停顿
2025-11-27 04:32:01 +00:00
val dropOffset by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
2025-11-27 04:45:22 +00:00
animation = tween(8000, easing = LinearEasing),
repeatMode = RepeatMode.Restart
2025-11-27 04:32:01 +00:00
),
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("") -> {
2025-11-27 04:45:22 +00:00
val dropCount = 20
val dropLength = 10.dp.toPx()
2025-11-27 04:32:01 +00:00
for (i in 0 until dropCount) {
2025-11-27 04:45:22 +00:00
// 使用不同的起始位置和速度让雨滴错开
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
2025-11-27 04:32:01 +00:00
drawLine(
2025-11-27 04:45:22 +00:00
color = Color.White.copy(alpha = 0.35f),
2025-11-27 04:32:01 +00:00
start = Offset(x, currentY),
2025-11-27 04:45:22 +00:00
end = Offset(x, currentY + dropLength), // 垂直下落
2025-11-27 04:32:01 +00:00
strokeWidth = 1.5f.dp.toPx()
)
}
}
// 雪天:绘制飘落的雪花(圆点)
weather.contains("") -> {
2025-11-27 04:45:22 +00:00
val snowCount = 18
2025-11-27 04:32:01 +00:00
for (i in 0 until snowCount) {
2025-11-27 04:45:22 +00:00
// 使用不同的起始位置和速度让雪花错开
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
2025-11-27 04:32:01 +00:00
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(
2025-11-27 02:56:42 +00:00
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))
2025-11-27 02:56:42 +00:00
.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
)
}
}
}
}
}
}