3091 lines
124 KiB
Kotlin
3091 lines
124 KiB
Kotlin
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 com.example.smarthome.data.LanguageManager
|
||
import com.example.smarthome.data.tr
|
||
import kotlinx.coroutines.launch
|
||
import kotlinx.coroutines.delay
|
||
import androidx.compose.runtime.LaunchedEffect
|
||
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.verticalScroll
|
||
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
|
||
import androidx.compose.ui.layout.ContentScale
|
||
|
||
@Composable
|
||
fun MainScaffold(
|
||
selectedRoom: Int,
|
||
onRoomSelect: (Int) -> Unit,
|
||
selectedNavItem: Int = 0,
|
||
onNavItemSelect: (Int) -> Unit = {},
|
||
rooms: List<String> = listOf("总览", "客厅", "厨房", "卧室", "影音室", "游戏房"),
|
||
onAddRoom: (String) -> Unit = {},
|
||
onDeleteRoom: (Int) -> Unit = {},
|
||
onRenameRoom: (Int, String) -> Unit = { _, _ -> }
|
||
) {
|
||
Box(modifier = Modifier.fillMaxSize()) {
|
||
// 内容区域 - 全屏显示
|
||
Box(
|
||
modifier = Modifier.fillMaxSize()
|
||
) {
|
||
// 根据选中的导航项显示不同的内容
|
||
// 底部导航: 0-Dashboard, 1-Scene, 2-Automation, 3-Security, 4-Settings
|
||
when (selectedNavItem) {
|
||
0 -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom, onRenameRoom)
|
||
1 -> SceneScreen()
|
||
2 -> AutomationScreen()
|
||
3 -> SecurityScreen()
|
||
4 -> SettingsContent()
|
||
else -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom, onRenameRoom)
|
||
}
|
||
}
|
||
|
||
// 底部悬浮导航栏 - 置于内容之上
|
||
FloatingBottomNav(
|
||
selectedNavItem = selectedNavItem,
|
||
onNavItemSelect = onNavItemSelect,
|
||
modifier = Modifier
|
||
.align(Alignment.BottomCenter)
|
||
.padding(bottom = 24.dp)
|
||
)
|
||
}
|
||
}
|
||
|
||
@Composable
|
||
fun DashboardContent(
|
||
selectedRoom: Int,
|
||
onRoomSelect: (Int) -> Unit,
|
||
rooms: List<String> = listOf("总览", "客厅", "厨房", "卧室", "影音室", "游戏房"),
|
||
onAddRoom: (String) -> Unit = {},
|
||
onDeleteRoom: (Int) -> Unit = {},
|
||
onRenameRoom: (Int, String) -> Unit = { _, _ -> }
|
||
) {
|
||
var showAddRoomDialog by remember { mutableStateOf(false) }
|
||
|
||
// 总览和客厅使用默认壁纸
|
||
val roomName = rooms.getOrNull(selectedRoom) ?: "总览"
|
||
val hasWallpaper = selectedRoom == 0 || roomName == "客厅"
|
||
|
||
Box(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
) {
|
||
// 房间壁纸背景(总览和客厅)- 放在最底层
|
||
if (hasWallpaper) {
|
||
Image(
|
||
painter = painterResource(id = R.drawable.room_wallpaper_default),
|
||
contentDescription = null,
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.clip(RoundedCornerShape(24.dp)),
|
||
contentScale = ContentScale.Crop,
|
||
alpha = 0.25f
|
||
)
|
||
}
|
||
|
||
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 16.dp)) {
|
||
TopBar()
|
||
RoomTabs(
|
||
selectedRoom = selectedRoom,
|
||
onRoomSelect = onRoomSelect,
|
||
rooms = rooms,
|
||
onAddRoomClick = { showAddRoomDialog = true },
|
||
onDeleteRoom = onDeleteRoom,
|
||
onRenameRoom = onRenameRoom
|
||
)
|
||
// 根据选中的房间显示不同的内容
|
||
RoomContent(selectedRoom = selectedRoom, roomName = roomName)
|
||
}
|
||
|
||
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()
|
||
.verticalScroll(rememberScrollState())
|
||
) {
|
||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||
AirConditionerCard(modifier = Modifier.weight(1f), roomName = tr("room_all"))
|
||
UsageStatusChart(modifier = Modifier.weight(1f), roomName = tr("room_all"))
|
||
}
|
||
LightRow(modifier = Modifier.fillMaxWidth().padding(top = 16.dp, bottom = 16.dp), roomName = tr("room_all"))
|
||
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.fillMaxWidth())
|
||
}
|
||
}
|
||
|
||
@Composable
|
||
fun SpecificRoomContent(selectedRoom: Int, roomName: String) {
|
||
Column(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.verticalScroll(rememberScrollState())
|
||
) {
|
||
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.fillMaxWidth())
|
||
}
|
||
}
|
||
|
||
@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)
|
||
)
|
||
Column(
|
||
modifier = modifier,
|
||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||
) {
|
||
allDevices.forEach { 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 = {},
|
||
onRenameRoom: (Int, String) -> Unit = { _, _ -> }
|
||
) {
|
||
// 编辑房间对话框状态
|
||
var showEditRoomsDialog by remember { mutableStateOf(false) }
|
||
|
||
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,
|
||
onClick = { onRoomSelect(index) }
|
||
)
|
||
}
|
||
}
|
||
|
||
// 编辑按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.size(44.dp)
|
||
.clip(RoundedCornerShape(12.dp))
|
||
.background(Color(0xFF2A2A3E))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color(0xFF3A3A4E),
|
||
shape = RoundedCornerShape(12.dp)
|
||
)
|
||
.clickable { showEditRoomsDialog = true },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✏️", fontSize = 18.sp)
|
||
}
|
||
|
||
// 添加按钮
|
||
GradientButton("+ 添加", onClick = onAddRoomClick)
|
||
}
|
||
|
||
// 编辑房间对话框
|
||
if (showEditRoomsDialog) {
|
||
EditRoomsDialog(
|
||
rooms = rooms,
|
||
onDismiss = { showEditRoomsDialog = false },
|
||
onRenameRoom = onRenameRoom,
|
||
onDeleteRoom = onDeleteRoom
|
||
)
|
||
}
|
||
}
|
||
|
||
@Composable
|
||
fun RoomTab(
|
||
name: String,
|
||
selected: Boolean,
|
||
onClick: () -> Unit
|
||
) {
|
||
val shape = RoundedCornerShape(20.dp)
|
||
|
||
Box(
|
||
modifier = Modifier
|
||
.clip(shape)
|
||
.then(
|
||
if (selected) {
|
||
Modifier.background(brush = Brush.linearGradient(listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))))
|
||
} else {
|
||
Modifier
|
||
.background(Color(0xFF1E2530).copy(alpha = 0.8f))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color.White.copy(alpha = 0.15f),
|
||
shape = shape
|
||
)
|
||
}
|
||
)
|
||
.clickable { onClick() }
|
||
.padding(horizontal = 14.dp, vertical = 10.dp)
|
||
) {
|
||
Text(
|
||
text = name,
|
||
color = if (selected) Color.Black else Color(0xFFB0B0B0),
|
||
maxLines = 1
|
||
)
|
||
}
|
||
}
|
||
|
||
@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, roomName: String = "") {
|
||
var active by remember { mutableStateOf(d.on) }
|
||
val accentColor = if (active) Color(0xFFFFB74D) else Color(0xFF5A5A6E)
|
||
|
||
Row(
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.clip(RoundedCornerShape(16.dp))
|
||
.background(Color(0xFF1E2530).copy(alpha = 0.85f))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color.White.copy(alpha = 0.15f),
|
||
shape = RoundedCornerShape(16.dp)
|
||
)
|
||
.padding(12.dp),
|
||
verticalAlignment = Alignment.CenterVertically,
|
||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||
) {
|
||
// 左侧图标
|
||
Box(
|
||
modifier = Modifier
|
||
.size(48.dp)
|
||
.clip(RoundedCornerShape(12.dp))
|
||
.background(accentColor.copy(alpha = if (active) 0.3f else 0.15f)),
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Image(
|
||
painter = painterResource(id = d.icon),
|
||
contentDescription = null,
|
||
modifier = Modifier.size(28.dp),
|
||
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
||
if (active) accentColor else Color(0xFF9AA0A6)
|
||
)
|
||
)
|
||
}
|
||
|
||
// 中间文字
|
||
Column(modifier = Modifier.weight(1f)) {
|
||
Text(
|
||
text = if (roomName.isNotEmpty()) "$roomName ${d.name}" else d.name,
|
||
fontWeight = FontWeight.SemiBold,
|
||
color = Color.White,
|
||
fontSize = 15.sp
|
||
)
|
||
Text(
|
||
text = d.sub,
|
||
color = if (active) Color(0xFF00E676) else Color(0xFF9AA0A6),
|
||
fontSize = 13.sp
|
||
)
|
||
}
|
||
|
||
// 右侧控制 - 根据设备类型显示不同控制方式
|
||
val isLight = d.icon == R.drawable.ic_light || d.name.contains("灯")
|
||
val isAC = d.icon == R.drawable.ic_ac || d.name.contains("空调")
|
||
val isCurtain = d.icon == R.drawable.ic_curtain || d.name.contains("窗帘")
|
||
|
||
when {
|
||
isLight -> {
|
||
// 灯光:亮度调节点
|
||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||
val brightnessLevel = d.sub.replace("%", "").toIntOrNull()?.let { it / 20 } ?: 4
|
||
repeat(6) { index ->
|
||
Box(
|
||
modifier = Modifier
|
||
.size(10.dp)
|
||
.clip(RoundedCornerShape(5.dp))
|
||
.background(
|
||
if (active && index < brightnessLevel) Color(0xFF00D1FF)
|
||
else Color(0xFF00D1FF).copy(alpha = 0.3f)
|
||
)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
isAC -> {
|
||
// 空调:显示温度
|
||
Text(
|
||
text = d.sub,
|
||
color = if (active) Color(0xFF00D1FF) else Color(0xFF9AA0A6),
|
||
fontSize = 16.sp,
|
||
fontWeight = FontWeight.Bold
|
||
)
|
||
}
|
||
isCurtain -> {
|
||
// 窗帘:开关状态图标
|
||
Text(
|
||
text = if (active) "▼" else "▲",
|
||
color = if (active) Color(0xFF00E676) else Color(0xFF9AA0A6),
|
||
fontSize = 18.sp
|
||
)
|
||
}
|
||
else -> {
|
||
// 其他设备:开关按钮
|
||
Switch(
|
||
checked = active,
|
||
onCheckedChange = { active = it },
|
||
colors = SwitchDefaults.colors(
|
||
checkedThumbColor = Color.White,
|
||
checkedTrackColor = Color(0xFF00D1FF),
|
||
uncheckedThumbColor = Color.Gray,
|
||
uncheckedTrackColor = Color(0xFF3A3A4E)
|
||
),
|
||
modifier = Modifier.scale(0.8f)
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@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 FloatingBottomNav(
|
||
selectedNavItem: Int = 0,
|
||
onNavItemSelect: (Int) -> Unit = {},
|
||
modifier: Modifier = Modifier
|
||
) {
|
||
// 监听语言变化以触发重组
|
||
val currentLang by LanguageManager.currentLanguage.collectAsState()
|
||
|
||
val navItems = listOf(
|
||
R.drawable.ic_dashboard,
|
||
R.drawable.ic_scene,
|
||
R.drawable.ic_automation,
|
||
R.drawable.ic_security,
|
||
R.drawable.ic_settings
|
||
)
|
||
|
||
val itemWidth = 52.dp
|
||
val itemSpacing = 4.dp
|
||
val totalItemWidth = itemWidth + itemSpacing
|
||
|
||
// 是否正在拖动
|
||
var isDragging by remember { mutableStateOf(false) }
|
||
// 拖动时的临时选中项
|
||
var dragIndex by remember { mutableStateOf(selectedNavItem) }
|
||
|
||
// 当外部 selectedNavItem 变化时,同步更新 dragIndex
|
||
LaunchedEffect(selectedNavItem) {
|
||
if (!isDragging) {
|
||
dragIndex = selectedNavItem
|
||
}
|
||
}
|
||
|
||
// 选中框的动画偏移量 - 跟随 dragIndex(拖动时)或 selectedNavItem(点击时)
|
||
val currentIndex = if (isDragging) dragIndex else selectedNavItem
|
||
val animatedOffset by animateFloatAsState(
|
||
targetValue = currentIndex * (itemWidth.value + itemSpacing.value),
|
||
animationSpec = spring(
|
||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||
stiffness = Spring.StiffnessHigh
|
||
),
|
||
label = "liquid_glass_offset"
|
||
)
|
||
|
||
|
||
val density = LocalDensity.current
|
||
|
||
// 菜单栏高度(胶囊形)
|
||
val navBarHeight = 68.dp
|
||
val navBarPadding = 10.dp
|
||
val innerHeight = navBarHeight - navBarPadding * 2 // 48dp
|
||
|
||
// 选中指示器尺寸 - 按住时放大到贴合菜单栏边缘
|
||
val indicatorSize by animateFloatAsState(
|
||
targetValue = if (isDragging) (navBarHeight.value - 4f) else innerHeight.value,
|
||
animationSpec = spring(
|
||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||
stiffness = Spring.StiffnessMedium
|
||
),
|
||
label = "indicator_size"
|
||
)
|
||
|
||
Box(
|
||
modifier = modifier
|
||
.height(navBarHeight)
|
||
.shadow(24.dp, RoundedCornerShape(50))
|
||
.clip(RoundedCornerShape(50))
|
||
.background(
|
||
Brush.linearGradient(
|
||
colors = listOf(
|
||
Color(0xFF1A1A2E).copy(alpha = 0.95f),
|
||
Color(0xFF16213E).copy(alpha = 0.95f)
|
||
)
|
||
)
|
||
)
|
||
.border(
|
||
width = 1.dp,
|
||
brush = Brush.linearGradient(
|
||
colors = listOf(
|
||
Color.White.copy(alpha = 0.1f),
|
||
Color.White.copy(alpha = 0.05f)
|
||
)
|
||
),
|
||
shape = RoundedCornerShape(50)
|
||
)
|
||
.padding(horizontal = 10.dp, vertical = 10.dp)
|
||
.pointerInput(Unit) {
|
||
detectHorizontalDragGestures(
|
||
onDragStart = { offset ->
|
||
isDragging = true
|
||
val index = (offset.x / (itemWidth.toPx() + itemSpacing.toPx())).toInt()
|
||
.coerceIn(0, navItems.size - 1)
|
||
dragIndex = index
|
||
},
|
||
onDragEnd = {
|
||
isDragging = false
|
||
onNavItemSelect(dragIndex)
|
||
},
|
||
onDragCancel = {
|
||
isDragging = false
|
||
dragIndex = selectedNavItem
|
||
},
|
||
onHorizontalDrag = { change, _ ->
|
||
change.consume()
|
||
val index = (change.position.x / (itemWidth.toPx() + itemSpacing.toPx())).toInt()
|
||
.coerceIn(0, navItems.size - 1)
|
||
if (index != dragIndex) {
|
||
dragIndex = index
|
||
}
|
||
}
|
||
)
|
||
}
|
||
) {
|
||
Box(
|
||
contentAlignment = Alignment.CenterStart
|
||
) {
|
||
// 选中指示器 - 按住时放大贴合菜单栏边缘
|
||
val horizontalOffset = (itemWidth.value - indicatorSize) / 2f
|
||
|
||
Box(
|
||
modifier = Modifier
|
||
.align(Alignment.CenterStart)
|
||
.offset(x = animatedOffset.dp + horizontalOffset.dp)
|
||
.size(indicatorSize.dp)
|
||
.clip(RoundedCornerShape(50))
|
||
.background(Color.White.copy(alpha = if (isDragging) 0.2f else 0.12f))
|
||
.border(
|
||
width = if (isDragging) 2.dp else 1.5.dp,
|
||
color = Color.White.copy(alpha = if (isDragging) 0.6f else 0.25f),
|
||
shape = RoundedCornerShape(50)
|
||
)
|
||
)
|
||
|
||
// 图标层
|
||
Row(
|
||
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
|
||
verticalAlignment = Alignment.CenterVertically
|
||
) {
|
||
navItems.forEachIndexed { index, iconRes ->
|
||
val isCurrentSelected = if (isDragging) index == dragIndex else index == selectedNavItem
|
||
|
||
Box(
|
||
modifier = Modifier
|
||
.size(itemWidth)
|
||
.clip(RoundedCornerShape(16.dp))
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) {
|
||
if (!isDragging) {
|
||
onNavItemSelect(index)
|
||
}
|
||
},
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Image(
|
||
painter = painterResource(id = iconRes),
|
||
contentDescription = null,
|
||
modifier = Modifier.size(24.dp),
|
||
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
||
if (isCurrentSelected) 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
|
||
) {
|
||
// 上半部分:导航项
|
||
// 监听语言变化以触发重组
|
||
val currentLang by LanguageManager.currentLanguage.collectAsState()
|
||
|
||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||
Spacer(modifier = Modifier.height(48.dp))
|
||
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) })
|
||
}
|
||
|
||
// 底部:模式切换按钮(暂时注释)
|
||
/*
|
||
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
|
||
.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),
|
||
fontSize = 12.sp,
|
||
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
|
||
maxLines = 1,
|
||
textAlign = TextAlign.Center
|
||
)
|
||
}
|
||
}
|
||
|
||
@Composable
|
||
fun TopBar() {
|
||
val context = androidx.compose.ui.platform.LocalContext.current
|
||
var weatherInfo by remember { mutableStateOf(WeatherService.getSimulatedWeather()) }
|
||
var isLoading by remember { mutableStateOf(true) }
|
||
|
||
// 获取用户信息
|
||
val userInfo by com.example.smarthome.data.UserManager.userInfo.collectAsState()
|
||
|
||
LaunchedEffect(Unit) {
|
||
try {
|
||
weatherInfo = WeatherService.getWeather(context)
|
||
} catch (e: Exception) {
|
||
// 使用模拟数据
|
||
}
|
||
isLoading = false
|
||
}
|
||
|
||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
||
Text(text = tr("dashboard_title"), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White)
|
||
|
||
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
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
@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()
|
||
}
|
||
Column(
|
||
modifier = modifier,
|
||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||
) {
|
||
devices.forEach { d -> DeviceCard(d) }
|
||
}
|
||
}
|
||
|
||
// 各房间的固定能耗数据 (用电量kWh, 运行时长h, 设备数, 趋势, 趋势百分比, 费用, 峰值功率W)
|
||
private val roomEnergyData = mapOf(
|
||
"客厅" to RoomEnergy(3.2, 8, 6, "↑", 8, 1.8, 320),
|
||
"厨房" to RoomEnergy(2.1, 4, 4, "↓", 12, 1.2, 1800),
|
||
"卧室" to RoomEnergy(1.5, 6, 5, "↓", 5, 0.9, 180),
|
||
"影音室" to RoomEnergy(2.8, 5, 6, "↑", 15, 1.6, 450),
|
||
"游戏房" to RoomEnergy(3.5, 7, 5, "↑", 18, 2.0, 380)
|
||
)
|
||
|
||
private data class RoomEnergy(
|
||
val power: Double, // 今日用电量 kWh
|
||
val hours: Int, // 今日运行时长 h
|
||
val devices: Int, // 活跃设备数
|
||
val trend: String, // 趋势方向
|
||
val trendPercent: Int, // 趋势百分比
|
||
val cost: Double, // 今日费用 元
|
||
val peakWatt: Int // 峰值功率 W
|
||
)
|
||
|
||
@Composable
|
||
fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间") {
|
||
// 判断是否为总览
|
||
val isOverview = roomName == tr("room_all") || roomName == "总览" || roomName == "全部"
|
||
|
||
// 根据房间获取数据,总览时汇总所有房间
|
||
val roomData = if (isOverview) {
|
||
// 汇总所有房间数据
|
||
val allRooms = roomEnergyData.values.toList()
|
||
RoomEnergy(
|
||
power = allRooms.sumOf { it.power },
|
||
hours = allRooms.maxOf { it.hours }, // 取最长运行时间(今日已过时间)
|
||
devices = allRooms.sumOf { it.devices },
|
||
trend = if (allRooms.count { it.trend == "↑" } > allRooms.size / 2) "↑" else "↓",
|
||
trendPercent = allRooms.map { it.trendPercent }.average().toInt(),
|
||
cost = allRooms.sumOf { it.cost },
|
||
peakWatt = allRooms.maxOf { it.peakWatt } // 取最大峰值
|
||
)
|
||
} else {
|
||
roomEnergyData[roomName] ?: RoomEnergy(2.0, 5, 4, "↓", 8, 1.2, 200)
|
||
}
|
||
|
||
val values = remember(roomName) {
|
||
if (isOverview) {
|
||
// 总览:生成更高的数值
|
||
List(12) { (40..90).random().toFloat() }
|
||
} else {
|
||
List(12) { (15..45).random().toFloat() }
|
||
}
|
||
}
|
||
val maxValue = remember(values) { values.maxOrNull() ?: 45f }
|
||
val totalPower = String.format("%.1f", roomData.power)
|
||
val totalHours = roomData.hours
|
||
val activeDevices = roomData.devices
|
||
val trend = roomData.trend
|
||
val trendPercent = roomData.trendPercent
|
||
val costToday = String.format("%.1f", roomData.cost)
|
||
val peakWatt = roomData.peakWatt
|
||
|
||
// 主题色
|
||
val accentColor = Color(0xFF00D1FF)
|
||
|
||
Box(
|
||
modifier = modifier
|
||
.height(260.dp)
|
||
.clip(RoundedCornerShape(24.dp))
|
||
.background(Color(0xFF1E2530).copy(alpha = 0.85f))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color.White.copy(alpha = 0.15f),
|
||
shape = RoundedCornerShape(24.dp)
|
||
)
|
||
.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 = "$peakWatt",
|
||
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 tr("ac_mode_cool"),
|
||
"☀️" to tr("ac_mode_heat"),
|
||
"💧" to tr("ac_mode_dry"),
|
||
"🌀" to tr("ac_mode_fan")
|
||
)
|
||
|
||
// 风速旋转速度(根据档位变化)
|
||
val fanRotationDuration = when (fanSpeed) {
|
||
0 -> 2000 // 自动
|
||
1 -> 3000 // 低速
|
||
2 -> 1500 // 中速
|
||
else -> 800 // 高速
|
||
}
|
||
|
||
// 图标动画
|
||
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 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) // 送风 - 灰色
|
||
}
|
||
|
||
// 当前模式的emoji
|
||
val currentEmoji = modeIcons[selectedMode].first
|
||
|
||
Box(
|
||
modifier = modifier
|
||
.height(260.dp)
|
||
.clip(RoundedCornerShape(24.dp))
|
||
.background(Color(0xFF1E2530).copy(alpha = 0.85f))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color.White.copy(alpha = 0.15f),
|
||
shape = RoundedCornerShape(24.dp)
|
||
)
|
||
.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 = 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)
|
||
)
|
||
)
|
||
}
|
||
|
||
// 温度显示和调节
|
||
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)
|
||
)
|
||
}
|
||
|
||
// 风速按钮 + 温度调节按钮
|
||
Row(
|
||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||
verticalAlignment = Alignment.CenterVertically
|
||
) {
|
||
// 风速按钮(点击循环切换)
|
||
Column(
|
||
horizontalAlignment = Alignment.CenterHorizontally,
|
||
modifier = Modifier
|
||
.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)
|
||
) {
|
||
// 风扇图标(根据风速显示不同图片,带旋转动画)
|
||
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
|
||
)
|
||
}
|
||
|
||
// 温度调节按钮
|
||
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)
|
||
.heightIn(min = 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 }
|
||
.padding(vertical = 4.dp),
|
||
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),
|
||
maxLines = 1
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@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) },
|
||
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 buttonHeight = 100.dp
|
||
|
||
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) }
|
||
|
||
// 根据文本确定图标
|
||
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(0xFF1E2530).copy(alpha = 0.85f))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color.White.copy(alpha = 0.15f),
|
||
shape = shape
|
||
)
|
||
}
|
||
)
|
||
.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(72.dp)
|
||
.clip(RoundedCornerShape(16.dp))
|
||
.background(Color(0xFF1E2530).copy(alpha = 0.85f))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color.White.copy(alpha = 0.15f),
|
||
shape = RoundedCornerShape(16.dp)
|
||
)
|
||
.padding(horizontal = 12.dp, vertical = 8.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("") }
|
||
var selectedIcon by remember { mutableStateOf(0) }
|
||
|
||
// 预设房间图标
|
||
val roomIcons = listOf("🛋️", "🍳", "🛏️", "🎬", "🎮", "📚", "🛁", "🌿")
|
||
val roomSuggestions = listOf("书房", "阳台", "浴室", "储物间", "健身房", "儿童房")
|
||
|
||
// 背景遮罩(点击关闭)
|
||
Box(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
// 对话框主体
|
||
Box(
|
||
modifier = Modifier
|
||
.width(420.dp)
|
||
.clip(RoundedCornerShape(28.dp))
|
||
.background(
|
||
Brush.verticalGradient(
|
||
listOf(
|
||
Color(0xFF2A2A3E),
|
||
Color(0xFF1A1A2E)
|
||
)
|
||
)
|
||
)
|
||
.border(
|
||
width = 1.dp,
|
||
brush = Brush.linearGradient(
|
||
listOf(Color(0xFF00D1FF).copy(alpha = 0.3f), Color(0xFFB89CFF).copy(alpha = 0.3f))
|
||
),
|
||
shape = RoundedCornerShape(28.dp)
|
||
)
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) { /* 阻止点击穿透 */ }
|
||
.padding(28.dp)
|
||
) {
|
||
Column(
|
||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||
) {
|
||
// 标题区域
|
||
Row(
|
||
modifier = Modifier.fillMaxWidth(),
|
||
horizontalArrangement = Arrangement.SpaceBetween,
|
||
verticalAlignment = Alignment.CenterVertically
|
||
) {
|
||
Row(
|
||
verticalAlignment = Alignment.CenterVertically,
|
||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||
) {
|
||
// 图标
|
||
Box(
|
||
modifier = Modifier
|
||
.size(44.dp)
|
||
.clip(RoundedCornerShape(12.dp))
|
||
.background(
|
||
Brush.linearGradient(
|
||
listOf(Color(0xFF00D1FF).copy(alpha = 0.3f), Color(0xFFB89CFF).copy(alpha = 0.3f))
|
||
)
|
||
),
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "🏠", fontSize = 22.sp)
|
||
}
|
||
Column {
|
||
Text(
|
||
text = "添加新房间",
|
||
fontSize = 20.sp,
|
||
fontWeight = FontWeight.Bold,
|
||
color = Color.White
|
||
)
|
||
Text(
|
||
text = "创建一个新的智能空间",
|
||
fontSize = 12.sp,
|
||
color = Color(0xFF9AA0A6)
|
||
)
|
||
}
|
||
}
|
||
|
||
// 关闭按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.size(32.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0x33FFFFFF))
|
||
.clickable { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✕", color = Color(0xFF9AA0A6), fontSize = 14.sp)
|
||
}
|
||
}
|
||
|
||
// 房间名称输入
|
||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||
Text(
|
||
text = "房间名称",
|
||
fontSize = 14.sp,
|
||
fontWeight = FontWeight.Medium,
|
||
color = Color(0xFFB0B0B0)
|
||
)
|
||
androidx.compose.material3.TextField(
|
||
value = roomName,
|
||
onValueChange = { roomName = it },
|
||
placeholder = { Text("例如:书房、阳台...", color = Color(0xFF6A6A7A)) },
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.height(56.dp),
|
||
colors = androidx.compose.material3.TextFieldDefaults.colors(
|
||
focusedContainerColor = Color(0xFF1A1A2E),
|
||
unfocusedContainerColor = Color(0xFF1A1A2E),
|
||
focusedTextColor = Color.White,
|
||
unfocusedTextColor = Color.White,
|
||
cursorColor = Color(0xFF00D1FF),
|
||
focusedIndicatorColor = Color.Transparent,
|
||
unfocusedIndicatorColor = Color.Transparent
|
||
),
|
||
shape = RoundedCornerShape(14.dp),
|
||
singleLine = true,
|
||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 16.sp)
|
||
)
|
||
}
|
||
|
||
// 快速选择建议
|
||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||
Text(
|
||
text = "快速选择",
|
||
fontSize = 14.sp,
|
||
fontWeight = FontWeight.Medium,
|
||
color = Color(0xFFB0B0B0)
|
||
)
|
||
Row(
|
||
modifier = Modifier.fillMaxWidth(),
|
||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||
) {
|
||
roomSuggestions.take(3).forEach { suggestion ->
|
||
Box(
|
||
modifier = Modifier
|
||
.clip(RoundedCornerShape(20.dp))
|
||
.background(
|
||
if (roomName == suggestion) Color(0xFF00D1FF).copy(alpha = 0.3f)
|
||
else Color(0xFF1A1A2E)
|
||
)
|
||
.border(
|
||
width = 1.dp,
|
||
color = if (roomName == suggestion) Color(0xFF00D1FF) else Color(0xFF3A3A4E),
|
||
shape = RoundedCornerShape(20.dp)
|
||
)
|
||
.clickable { roomName = suggestion }
|
||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||
) {
|
||
Text(
|
||
text = suggestion,
|
||
color = if (roomName == suggestion) Color.White else Color(0xFF9AA0A6),
|
||
fontSize = 13.sp
|
||
)
|
||
}
|
||
}
|
||
}
|
||
Row(
|
||
modifier = Modifier.fillMaxWidth(),
|
||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||
) {
|
||
roomSuggestions.drop(3).forEach { suggestion ->
|
||
Box(
|
||
modifier = Modifier
|
||
.clip(RoundedCornerShape(20.dp))
|
||
.background(
|
||
if (roomName == suggestion) Color(0xFF00D1FF).copy(alpha = 0.3f)
|
||
else Color(0xFF1A1A2E)
|
||
)
|
||
.border(
|
||
width = 1.dp,
|
||
color = if (roomName == suggestion) Color(0xFF00D1FF) else Color(0xFF3A3A4E),
|
||
shape = RoundedCornerShape(20.dp)
|
||
)
|
||
.clickable { roomName = suggestion }
|
||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||
) {
|
||
Text(
|
||
text = suggestion,
|
||
color = if (roomName == suggestion) Color.White else Color(0xFF9AA0A6),
|
||
fontSize = 13.sp
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 选择图标
|
||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||
Text(
|
||
text = "选择图标",
|
||
fontSize = 14.sp,
|
||
fontWeight = FontWeight.Medium,
|
||
color = Color(0xFFB0B0B0)
|
||
)
|
||
Row(
|
||
modifier = Modifier.fillMaxWidth(),
|
||
horizontalArrangement = Arrangement.SpaceEvenly
|
||
) {
|
||
roomIcons.forEachIndexed { index, icon ->
|
||
Box(
|
||
modifier = Modifier
|
||
.size(44.dp)
|
||
.clip(RoundedCornerShape(12.dp))
|
||
.then(
|
||
if (selectedIcon == index)
|
||
Modifier.background(Brush.linearGradient(listOf(Color(0xFF00D1FF), Color(0xFFB89CFF))))
|
||
else Modifier.background(Color(0xFF1A1A2E))
|
||
)
|
||
.border(
|
||
width = if (selectedIcon == index) 0.dp else 1.dp,
|
||
color = Color(0xFF3A3A4E),
|
||
shape = RoundedCornerShape(12.dp)
|
||
)
|
||
.clickable { selectedIcon = index },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = icon, fontSize = 20.sp)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 按钮区域
|
||
Row(
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.padding(top = 8.dp),
|
||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||
) {
|
||
// 取消按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.weight(1f)
|
||
.height(52.dp)
|
||
.clip(RoundedCornerShape(14.dp))
|
||
.background(Color(0xFF1A1A2E))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color(0xFF3A3A4E),
|
||
shape = RoundedCornerShape(14.dp)
|
||
)
|
||
.clickable { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(
|
||
text = "取消",
|
||
color = Color(0xFFB0B0B0),
|
||
fontWeight = FontWeight.Medium,
|
||
fontSize = 15.sp
|
||
)
|
||
}
|
||
|
||
// 确定按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.weight(1f)
|
||
.height(52.dp)
|
||
.clip(RoundedCornerShape(14.dp))
|
||
.background(
|
||
if (roomName.isNotBlank())
|
||
Brush.linearGradient(listOf(Color(0xFF00D1FF), Color(0xFFB89CFF)))
|
||
else
|
||
Brush.linearGradient(listOf(Color(0xFF3A3A4E), Color(0xFF3A3A4E)))
|
||
)
|
||
.clickable(enabled = roomName.isNotBlank()) {
|
||
if (roomName.isNotBlank()) {
|
||
onConfirm(roomName.trim())
|
||
}
|
||
},
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Row(
|
||
verticalAlignment = Alignment.CenterVertically,
|
||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||
) {
|
||
Text(
|
||
text = "✓",
|
||
color = if (roomName.isNotBlank()) Color.White else Color(0xFF6A6A7A),
|
||
fontSize = 16.sp
|
||
)
|
||
Text(
|
||
text = "添加房间",
|
||
color = if (roomName.isNotBlank()) Color.White else Color(0xFF6A6A7A),
|
||
fontWeight = FontWeight.Bold,
|
||
fontSize = 15.sp
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@Composable
|
||
fun RenameRoomDialog(
|
||
currentName: String,
|
||
onDismiss: () -> Unit,
|
||
onConfirm: (String) -> Unit
|
||
) {
|
||
var roomName by remember { mutableStateOf(currentName) }
|
||
|
||
// 背景遮罩(点击关闭)
|
||
Box(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
// 对话框主体
|
||
Box(
|
||
modifier = Modifier
|
||
.width(380.dp)
|
||
.clip(RoundedCornerShape(28.dp))
|
||
.background(
|
||
Brush.verticalGradient(
|
||
listOf(
|
||
Color(0xFF2A2A3E),
|
||
Color(0xFF1A1A2E)
|
||
)
|
||
)
|
||
)
|
||
.border(
|
||
width = 1.dp,
|
||
brush = Brush.linearGradient(
|
||
listOf(Color(0xFF00D1FF).copy(alpha = 0.3f), Color(0xFFB89CFF).copy(alpha = 0.3f))
|
||
),
|
||
shape = RoundedCornerShape(28.dp)
|
||
)
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) { /* 阻止点击穿透 */ }
|
||
.padding(28.dp)
|
||
) {
|
||
Column(
|
||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||
) {
|
||
// 标题区域
|
||
Row(
|
||
modifier = Modifier.fillMaxWidth(),
|
||
horizontalArrangement = Arrangement.SpaceBetween,
|
||
verticalAlignment = Alignment.CenterVertically
|
||
) {
|
||
Row(
|
||
verticalAlignment = Alignment.CenterVertically,
|
||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||
) {
|
||
Box(
|
||
modifier = Modifier
|
||
.size(44.dp)
|
||
.clip(RoundedCornerShape(12.dp))
|
||
.background(
|
||
Brush.linearGradient(
|
||
listOf(Color(0xFF00D1FF).copy(alpha = 0.3f), Color(0xFFB89CFF).copy(alpha = 0.3f))
|
||
)
|
||
),
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✏️", fontSize = 22.sp)
|
||
}
|
||
Column {
|
||
Text(
|
||
text = "重命名房间",
|
||
fontSize = 20.sp,
|
||
fontWeight = FontWeight.Bold,
|
||
color = Color.White
|
||
)
|
||
Text(
|
||
text = "修改房间名称",
|
||
fontSize = 12.sp,
|
||
color = Color(0xFF9AA0A6)
|
||
)
|
||
}
|
||
}
|
||
|
||
Box(
|
||
modifier = Modifier
|
||
.size(32.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0x33FFFFFF))
|
||
.clickable { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✕", color = Color(0xFF9AA0A6), fontSize = 14.sp)
|
||
}
|
||
}
|
||
|
||
// 房间名称输入
|
||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||
Text(
|
||
text = "房间名称",
|
||
fontSize = 14.sp,
|
||
fontWeight = FontWeight.Medium,
|
||
color = Color(0xFFB0B0B0)
|
||
)
|
||
TextField(
|
||
value = roomName,
|
||
onValueChange = { roomName = it },
|
||
placeholder = { Text("输入新名称...", color = Color(0xFF6A6A7A)) },
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.height(56.dp),
|
||
colors = TextFieldDefaults.colors(
|
||
focusedContainerColor = Color(0xFF1A1A2E),
|
||
unfocusedContainerColor = Color(0xFF1A1A2E),
|
||
focusedTextColor = Color.White,
|
||
unfocusedTextColor = Color.White,
|
||
cursorColor = Color(0xFF00D1FF),
|
||
focusedIndicatorColor = Color.Transparent,
|
||
unfocusedIndicatorColor = Color.Transparent
|
||
),
|
||
shape = RoundedCornerShape(14.dp),
|
||
singleLine = true,
|
||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 16.sp)
|
||
)
|
||
}
|
||
|
||
// 按钮区域
|
||
Row(
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.padding(top = 8.dp),
|
||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||
) {
|
||
Box(
|
||
modifier = Modifier
|
||
.weight(1f)
|
||
.height(52.dp)
|
||
.clip(RoundedCornerShape(14.dp))
|
||
.background(Color(0xFF1A1A2E))
|
||
.border(
|
||
width = 1.dp,
|
||
color = Color(0xFF3A3A4E),
|
||
shape = RoundedCornerShape(14.dp)
|
||
)
|
||
.clickable { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(
|
||
text = "取消",
|
||
color = Color(0xFFB0B0B0),
|
||
fontWeight = FontWeight.Medium,
|
||
fontSize = 15.sp
|
||
)
|
||
}
|
||
|
||
val canConfirm = roomName.isNotBlank() && roomName.trim() != currentName
|
||
Box(
|
||
modifier = Modifier
|
||
.weight(1f)
|
||
.height(52.dp)
|
||
.clip(RoundedCornerShape(14.dp))
|
||
.background(
|
||
if (canConfirm)
|
||
Brush.linearGradient(listOf(Color(0xFF00D1FF), Color(0xFFB89CFF)))
|
||
else
|
||
Brush.linearGradient(listOf(Color(0xFF3A3A4E), Color(0xFF3A3A4E)))
|
||
)
|
||
.clickable(enabled = canConfirm) {
|
||
onConfirm(roomName.trim())
|
||
},
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Row(
|
||
verticalAlignment = Alignment.CenterVertically,
|
||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||
) {
|
||
Text(
|
||
text = "✓",
|
||
color = if (canConfirm) Color.White else Color(0xFF6A6A7A),
|
||
fontSize = 16.sp
|
||
)
|
||
Text(
|
||
text = "确认修改",
|
||
color = if (canConfirm) Color.White else Color(0xFF6A6A7A),
|
||
fontWeight = FontWeight.Bold,
|
||
fontSize = 15.sp
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@Composable
|
||
fun EditRoomsDialog(
|
||
rooms: List<String>,
|
||
onDismiss: () -> Unit,
|
||
onRenameRoom: (Int, String) -> Unit,
|
||
onDeleteRoom: (Int) -> Unit
|
||
) {
|
||
// 当前编辑的房间索引
|
||
var editingIndex by remember { mutableStateOf(-1) }
|
||
var editingName by remember { mutableStateOf("") }
|
||
|
||
// 背景遮罩
|
||
Box(
|
||
modifier = Modifier
|
||
.fillMaxSize()
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
// 对话框主体
|
||
Box(
|
||
modifier = Modifier
|
||
.width(420.dp)
|
||
.clip(RoundedCornerShape(28.dp))
|
||
.background(
|
||
Brush.verticalGradient(
|
||
listOf(
|
||
Color(0xFF2A2A3E),
|
||
Color(0xFF1A1A2E)
|
||
)
|
||
)
|
||
)
|
||
.border(
|
||
width = 1.dp,
|
||
brush = Brush.linearGradient(
|
||
listOf(Color(0xFF00D1FF).copy(alpha = 0.3f), Color(0xFFB89CFF).copy(alpha = 0.3f))
|
||
),
|
||
shape = RoundedCornerShape(28.dp)
|
||
)
|
||
.clickable(
|
||
indication = null,
|
||
interactionSource = remember { MutableInteractionSource() }
|
||
) { /* 阻止点击穿透 */ }
|
||
.padding(28.dp)
|
||
) {
|
||
Column(
|
||
verticalArrangement = Arrangement.spacedBy(20.dp)
|
||
) {
|
||
// 标题区域
|
||
Row(
|
||
modifier = Modifier.fillMaxWidth(),
|
||
horizontalArrangement = Arrangement.SpaceBetween,
|
||
verticalAlignment = Alignment.CenterVertically
|
||
) {
|
||
Row(
|
||
verticalAlignment = Alignment.CenterVertically,
|
||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||
) {
|
||
Box(
|
||
modifier = Modifier
|
||
.size(44.dp)
|
||
.clip(RoundedCornerShape(12.dp))
|
||
.background(
|
||
Brush.linearGradient(
|
||
listOf(Color(0xFF00D1FF).copy(alpha = 0.3f), Color(0xFFB89CFF).copy(alpha = 0.3f))
|
||
)
|
||
),
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "🏠", fontSize = 22.sp)
|
||
}
|
||
Column {
|
||
Text(
|
||
text = "编辑房间",
|
||
fontSize = 20.sp,
|
||
fontWeight = FontWeight.Bold,
|
||
color = Color.White
|
||
)
|
||
Text(
|
||
text = "管理你的房间列表",
|
||
fontSize = 12.sp,
|
||
color = Color(0xFF9AA0A6)
|
||
)
|
||
}
|
||
}
|
||
|
||
Box(
|
||
modifier = Modifier
|
||
.size(32.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0x33FFFFFF))
|
||
.clickable { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✕", color = Color(0xFF9AA0A6), fontSize = 14.sp)
|
||
}
|
||
}
|
||
|
||
// 房间列表
|
||
Column(
|
||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||
modifier = Modifier.heightIn(max = 300.dp).verticalScroll(rememberScrollState())
|
||
) {
|
||
rooms.forEachIndexed { index, name ->
|
||
Row(
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.clip(RoundedCornerShape(14.dp))
|
||
.background(Color(0xFF1A1A2E))
|
||
.padding(12.dp),
|
||
horizontalArrangement = Arrangement.SpaceBetween,
|
||
verticalAlignment = Alignment.CenterVertically
|
||
) {
|
||
if (editingIndex == index) {
|
||
// 编辑模式
|
||
TextField(
|
||
value = editingName,
|
||
onValueChange = { editingName = it },
|
||
modifier = Modifier
|
||
.weight(1f)
|
||
.height(48.dp),
|
||
colors = TextFieldDefaults.colors(
|
||
focusedContainerColor = Color(0xFF2A2A3E),
|
||
unfocusedContainerColor = Color(0xFF2A2A3E),
|
||
focusedTextColor = Color.White,
|
||
unfocusedTextColor = Color.White,
|
||
cursorColor = Color(0xFF00D1FF),
|
||
focusedIndicatorColor = Color.Transparent,
|
||
unfocusedIndicatorColor = Color.Transparent
|
||
),
|
||
shape = RoundedCornerShape(10.dp),
|
||
singleLine = true,
|
||
textStyle = androidx.compose.ui.text.TextStyle(fontSize = 15.sp)
|
||
)
|
||
|
||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||
// 确认按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.size(36.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0xFF4CAF50))
|
||
.clickable {
|
||
if (editingName.isNotBlank() && editingName != name) {
|
||
onRenameRoom(index, editingName.trim())
|
||
}
|
||
editingIndex = -1
|
||
},
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✓", color = Color.White, fontSize = 16.sp)
|
||
}
|
||
// 取消按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.size(36.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0xFF3A3A4E))
|
||
.clickable { editingIndex = -1 },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✕", color = Color(0xFFB0B0B0), fontSize = 16.sp)
|
||
}
|
||
}
|
||
} else {
|
||
// 显示模式
|
||
Text(
|
||
text = name,
|
||
color = Color.White,
|
||
fontSize = 15.sp,
|
||
modifier = Modifier.weight(1f)
|
||
)
|
||
|
||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||
// 编辑按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.size(36.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0xFF3A3A4E))
|
||
.clickable {
|
||
editingIndex = index
|
||
editingName = name
|
||
},
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "✏️", fontSize = 16.sp)
|
||
}
|
||
// 删除按钮
|
||
if (rooms.size > 1) {
|
||
Box(
|
||
modifier = Modifier
|
||
.size(36.dp)
|
||
.clip(RoundedCornerShape(8.dp))
|
||
.background(Color(0xFFFF5252).copy(alpha = 0.2f))
|
||
.clickable { onDeleteRoom(index) },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(text = "🗑️", fontSize = 16.sp)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 完成按钮
|
||
Box(
|
||
modifier = Modifier
|
||
.fillMaxWidth()
|
||
.height(52.dp)
|
||
.clip(RoundedCornerShape(14.dp))
|
||
.background(
|
||
Brush.linearGradient(listOf(Color(0xFF00D1FF), Color(0xFFB89CFF)))
|
||
)
|
||
.clickable { onDismiss() },
|
||
contentAlignment = Alignment.Center
|
||
) {
|
||
Text(
|
||
text = "完成",
|
||
color = Color.White,
|
||
fontWeight = FontWeight.Bold,
|
||
fontSize = 15.sp
|
||
)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|