2025-11-26 02:30:03 +00:00
|
|
|
|
package com.example.smarthome.ui
|
|
|
|
|
|
|
|
|
|
|
|
import androidx.compose.foundation.Image
|
|
|
|
|
|
import androidx.compose.foundation.background
|
|
|
|
|
|
import androidx.compose.foundation.clickable
|
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
|
|
import androidx.compose.foundation.layout.width
|
|
|
|
|
|
import androidx.compose.foundation.lazy.grid.GridCells
|
|
|
|
|
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
|
|
|
|
import androidx.compose.foundation.lazy.grid.items
|
|
|
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
|
|
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
|
|
|
|
import androidx.compose.foundation.interaction.PressInteraction
|
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
|
import androidx.compose.material3.Switch
|
|
|
|
|
|
import androidx.compose.material3.Slider
|
|
|
|
|
|
import androidx.compose.material3.Text
|
|
|
|
|
|
import androidx.compose.runtime.Composable
|
|
|
|
|
|
import androidx.compose.runtime.getValue
|
|
|
|
|
|
import androidx.compose.runtime.mutableStateOf
|
|
|
|
|
|
import androidx.compose.runtime.remember
|
|
|
|
|
|
import androidx.compose.runtime.setValue
|
|
|
|
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
|
|
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
|
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
|
import androidx.compose.ui.draw.clip
|
|
|
|
|
|
import androidx.compose.ui.draw.shadow
|
|
|
|
|
|
import androidx.compose.ui.draw.scale
|
|
|
|
|
|
import androidx.compose.ui.draw.alpha
|
|
|
|
|
|
import androidx.compose.ui.graphics.Brush
|
|
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
|
|
|
import androidx.compose.ui.res.painterResource
|
|
|
|
|
|
import com.example.smarthome.R
|
|
|
|
|
|
import androidx.compose.ui.text.font.FontWeight
|
|
|
|
|
|
import androidx.compose.ui.text.style.TextAlign
|
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
|
import androidx.compose.ui.unit.sp
|
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxHeight
|
|
|
|
|
|
import androidx.compose.foundation.border
|
|
|
|
|
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
|
|
|
|
import androidx.compose.ui.platform.LocalDensity
|
|
|
|
|
|
import androidx.compose.ui.platform.LocalView
|
|
|
|
|
|
import android.view.SoundEffectConstants
|
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
|
|
import androidx.compose.foundation.horizontalScroll
|
|
|
|
|
|
import androidx.compose.foundation.rememberScrollState
|
|
|
|
|
|
import androidx.compose.foundation.gestures.detectTapGestures
|
|
|
|
|
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
|
|
|
|
import androidx.compose.ui.geometry.Offset
|
|
|
|
|
|
import androidx.compose.foundation.layout.offset
|
|
|
|
|
|
import androidx.compose.ui.draw.rotate
|
|
|
|
|
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
|
|
|
|
|
import androidx.compose.animation.core.animateFloat
|
|
|
|
|
|
import androidx.compose.animation.core.infiniteRepeatable
|
|
|
|
|
|
import androidx.compose.animation.core.tween
|
|
|
|
|
|
import androidx.compose.animation.core.LinearEasing
|
|
|
|
|
|
import androidx.compose.animation.core.RepeatMode
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun MainScaffold(
|
|
|
|
|
|
selectedRoom: Int,
|
|
|
|
|
|
onRoomSelect: (Int) -> Unit,
|
|
|
|
|
|
selectedNavItem: Int = 0,
|
|
|
|
|
|
onNavItemSelect: (Int) -> Unit = {},
|
|
|
|
|
|
rooms: List<String> = listOf("总览", "客厅", "厨房", "卧室", "影音室", "游戏房"),
|
|
|
|
|
|
onAddRoom: (String) -> Unit = {},
|
|
|
|
|
|
onDeleteRoom: (Int) -> Unit = {}
|
|
|
|
|
|
) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Row(modifier = Modifier.fillMaxSize()) {
|
2025-11-26 02:30:03 +00:00
|
|
|
|
SideNavRail(
|
|
|
|
|
|
selectedNavItem = selectedNavItem,
|
|
|
|
|
|
onNavItemSelect = onNavItemSelect
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 根据选中的导航项显示不同的内容
|
|
|
|
|
|
when (selectedNavItem) {
|
|
|
|
|
|
0 -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom)
|
|
|
|
|
|
5 -> SettingsContent()
|
|
|
|
|
|
else -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun DashboardContent(
|
|
|
|
|
|
selectedRoom: Int,
|
|
|
|
|
|
onRoomSelect: (Int) -> Unit,
|
|
|
|
|
|
rooms: List<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(16.dp)) {
|
|
|
|
|
|
TopBar()
|
|
|
|
|
|
RoomTabs(
|
|
|
|
|
|
selectedRoom = selectedRoom,
|
|
|
|
|
|
onRoomSelect = onRoomSelect,
|
|
|
|
|
|
rooms = rooms,
|
|
|
|
|
|
onAddRoomClick = { showAddRoomDialog = true },
|
|
|
|
|
|
onDeleteRoom = onDeleteRoom,
|
|
|
|
|
|
editMode = editMode,
|
|
|
|
|
|
onEditModeChange = { editMode = it }
|
|
|
|
|
|
)
|
2025-11-26 03:23:00 +00:00
|
|
|
|
// 根据选中的房间显示不同的内容
|
|
|
|
|
|
RoomContent(selectedRoom = selectedRoom, roomName = rooms.getOrNull(selectedRoom) ?: "总览")
|
2025-11-26 02:30:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (showAddRoomDialog) {
|
|
|
|
|
|
AddRoomDialog(
|
|
|
|
|
|
onDismiss = { showAddRoomDialog = false },
|
|
|
|
|
|
onConfirm = { roomName ->
|
|
|
|
|
|
onAddRoom(roomName)
|
|
|
|
|
|
showAddRoomDialog = false
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 03:23:00 +00:00
|
|
|
|
@Composable
|
|
|
|
|
|
fun RoomContent(selectedRoom: Int, roomName: String) {
|
|
|
|
|
|
// 根据房间索引显示不同的内容
|
|
|
|
|
|
when (selectedRoom) {
|
|
|
|
|
|
0 -> OverviewRoomContent() // 总览
|
|
|
|
|
|
else -> SpecificRoomContent(selectedRoom, roomName) // 具体房间
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun OverviewRoomContent() {
|
|
|
|
|
|
Column(modifier = Modifier.fillMaxSize()) {
|
|
|
|
|
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
|
|
|
|
AirConditionerCard(modifier = Modifier.weight(1f), roomName = "全屋")
|
|
|
|
|
|
UsageStatusChart(modifier = Modifier.weight(1f), roomName = "全屋")
|
|
|
|
|
|
}
|
|
|
|
|
|
LightRow(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), roomName = "全屋")
|
|
|
|
|
|
ModeButtonsRow(onModeSelected = { })
|
|
|
|
|
|
|
|
|
|
|
|
// 显示所有房间的设备摘要
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = "所有设备",
|
|
|
|
|
|
fontSize = 18.sp,
|
|
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
|
|
color = Color.White,
|
|
|
|
|
|
modifier = Modifier.padding(vertical = 12.dp)
|
|
|
|
|
|
)
|
|
|
|
|
|
AllDevicesOverview(modifier = Modifier.fillMaxSize())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun SpecificRoomContent(selectedRoom: Int, roomName: String) {
|
|
|
|
|
|
Column(modifier = Modifier.fillMaxSize()) {
|
|
|
|
|
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
|
|
|
|
|
AirConditionerCard(modifier = Modifier.weight(1f), roomName = roomName)
|
|
|
|
|
|
UsageStatusChart(modifier = Modifier.weight(1f), roomName = roomName)
|
|
|
|
|
|
}
|
|
|
|
|
|
LightRow(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), roomName = roomName)
|
|
|
|
|
|
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = "$roomName 设备",
|
|
|
|
|
|
fontSize = 18.sp,
|
|
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
|
|
color = Color.White,
|
|
|
|
|
|
modifier = Modifier.padding(vertical = 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) }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-26 02:30:03 +00:00
|
|
|
|
@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)
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 固定的添加按钮
|
|
|
|
|
|
GradientButton("+ 添加", onClick = onAddRoomClick)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun RoomTab(
|
|
|
|
|
|
name: String,
|
|
|
|
|
|
selected: Boolean,
|
|
|
|
|
|
editMode: Boolean,
|
|
|
|
|
|
canDelete: Boolean,
|
|
|
|
|
|
onClick: () -> Unit,
|
|
|
|
|
|
onLongClick: () -> Unit,
|
|
|
|
|
|
onDelete: () -> Unit
|
|
|
|
|
|
) {
|
|
|
|
|
|
val scope = rememberCoroutineScope()
|
|
|
|
|
|
|
|
|
|
|
|
// 改进的晃动动画 - 使用无限循环的旋转动画
|
|
|
|
|
|
val infiniteTransition = rememberInfiniteTransition(label = "shake")
|
|
|
|
|
|
val rotation by infiniteTransition.animateFloat(
|
|
|
|
|
|
initialValue = -3f,
|
|
|
|
|
|
targetValue = 3f,
|
|
|
|
|
|
animationSpec = infiniteRepeatable(
|
|
|
|
|
|
animation = tween(150, easing = LinearEasing),
|
|
|
|
|
|
repeatMode = RepeatMode.Reverse
|
|
|
|
|
|
),
|
|
|
|
|
|
label = "rotation"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 缩放动画
|
|
|
|
|
|
val scale by androidx.compose.animation.core.animateFloatAsState(
|
|
|
|
|
|
targetValue = if (editMode && canDelete) 0.95f else 1f,
|
|
|
|
|
|
animationSpec = androidx.compose.animation.core.spring(
|
|
|
|
|
|
dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy,
|
|
|
|
|
|
stiffness = androidx.compose.animation.core.Spring.StiffnessLow
|
|
|
|
|
|
),
|
|
|
|
|
|
label = "scale"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.scale(scale)
|
|
|
|
|
|
.then(
|
|
|
|
|
|
if (editMode && canDelete) {
|
|
|
|
|
|
Modifier.rotate(rotation)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Modifier
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
.pointerInput(Unit) {
|
|
|
|
|
|
detectTapGestures(
|
|
|
|
|
|
onTap = { onClick() },
|
|
|
|
|
|
onLongPress = {
|
|
|
|
|
|
if (canDelete) {
|
|
|
|
|
|
onLongClick()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
) {
|
|
|
|
|
|
val base = Modifier.clip(RoundedCornerShape(20.dp))
|
|
|
|
|
|
val mod = if (selected) {
|
|
|
|
|
|
base.background(brush = Brush.linearGradient(listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
base.background(color = Color(0x22121212))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = mod.padding(horizontal = 14.dp, vertical = 10.dp)
|
|
|
|
|
|
) {
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = name,
|
|
|
|
|
|
color = if (selected) Color.Black else Color(0xFFB0B0B0),
|
|
|
|
|
|
maxLines = 1
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除按钮
|
|
|
|
|
|
if (editMode && canDelete) {
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.size(22.dp)
|
|
|
|
|
|
.offset(x = (-4).dp, y = (-4).dp)
|
|
|
|
|
|
.align(Alignment.TopEnd)
|
|
|
|
|
|
.shadow(4.dp, RoundedCornerShape(11.dp))
|
|
|
|
|
|
.clip(RoundedCornerShape(11.dp))
|
|
|
|
|
|
.background(Color(0xFFFF5252))
|
|
|
|
|
|
.clickable(
|
|
|
|
|
|
indication = null,
|
|
|
|
|
|
interactionSource = remember { MutableInteractionSource() }
|
|
|
|
|
|
) { onDelete() },
|
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
|
) {
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = "×",
|
|
|
|
|
|
color = Color.White,
|
|
|
|
|
|
fontSize = 18.sp,
|
|
|
|
|
|
fontWeight = FontWeight.Bold
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun DeviceGrid() {
|
|
|
|
|
|
val devices = listOf(
|
|
|
|
|
|
Device("空调", "室内 25℃", R.drawable.ic_ac, true),
|
|
|
|
|
|
Device("智能开关", "待机中", R.drawable.ic_light, false),
|
|
|
|
|
|
Device("吊灯", "6500K", R.drawable.ic_light, true),
|
|
|
|
|
|
Device("扫地机", "清扫中", R.drawable.ic_media, true),
|
|
|
|
|
|
Device("空气净化", "运行中", R.drawable.ic_security, true),
|
|
|
|
|
|
Device("饮水机", "待机中", R.drawable.ic_media, false)
|
|
|
|
|
|
)
|
|
|
|
|
|
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 140.dp), modifier = Modifier.fillMaxSize().padding(24.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
|
|
|
|
items(devices) { d -> DeviceCard(d) }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data class Device(val name: String, val sub: String, val icon: Int, val on: Boolean)
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun DeviceCard(d: Device) {
|
|
|
|
|
|
var active by remember { mutableStateOf(d.on) }
|
|
|
|
|
|
Box(modifier = Modifier.height(120.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).shadow(12.dp, RoundedCornerShape(24.dp), true).padding(16.dp)) {
|
|
|
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
|
|
|
|
Image(painter = painterResource(id = d.icon), contentDescription = null, modifier = Modifier.size(40.dp))
|
|
|
|
|
|
Column {
|
|
|
|
|
|
Text(text = d.name, fontWeight = FontWeight.SemiBold)
|
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
|
|
|
|
|
Box(modifier = Modifier.size(8.dp).clip(RoundedCornerShape(4.dp)).background(if (active) Color(0xFF00E676) else Color(0xFF9AA0A6)))
|
|
|
|
|
|
Text(text = d.sub, color = Color(0xFF9AA0A6))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
|
Text(text = if (active) "运行中" else "待机中", color = if (active) Color(0xFF00E676) else Color(0xFF9AA0A6))
|
|
|
|
|
|
Toggle(active) { active = it }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun Toggle(on: Boolean, change: (Boolean) -> Unit) {
|
|
|
|
|
|
Box(modifier = Modifier.size(48.dp, 28.dp).clip(RoundedCornerShape(18.dp)).background(if (on) Color(0xFF3A0CA3) else Color(0x33222222)).clickable { change(!on) }.padding(4.dp)) {
|
|
|
|
|
|
Box(modifier = Modifier.size(20.dp).clip(RoundedCornerShape(10.dp)).background(Color.White).align(if (on) Alignment.CenterEnd else Alignment.CenterStart))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun BottomNav() {
|
|
|
|
|
|
Row(modifier = Modifier.fillMaxWidth().padding(16.dp).clip(RoundedCornerShape(28.dp)).background(Color(0x33121212)).padding(8.dp), horizontalArrangement = Arrangement.SpaceEvenly) {
|
|
|
|
|
|
NavItem("首页", true)
|
|
|
|
|
|
NavItem("设备", false)
|
|
|
|
|
|
NavItem("我的", false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun NavItem(title: String, selected: Boolean) {
|
|
|
|
|
|
Box(modifier = Modifier.clip(RoundedCornerShape(20.dp)).background(if (selected) Color(0x55121212) else Color.Transparent).padding(horizontal = 18.dp, vertical = 10.dp)) {
|
|
|
|
|
|
Text(text = title, color = if (selected) Color.White else Color(0xFFB0B0B0))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun SideNavRail(selectedNavItem: Int = 0, onNavItemSelect: (Int) -> Unit = {}) {
|
|
|
|
|
|
Column(modifier = Modifier.width(120.dp).fillMaxHeight().padding(12.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Spacer(modifier = Modifier.height(48.dp))
|
2025-11-26 02:30:03 +00:00
|
|
|
|
NavRailItem("控制台", R.drawable.ic_dashboard, selectedNavItem == 0, onClick = { onNavItemSelect(0) })
|
|
|
|
|
|
NavRailItem("最近", R.drawable.ic_recent, selectedNavItem == 1, onClick = { onNavItemSelect(1) })
|
|
|
|
|
|
NavRailItem("收藏", R.drawable.ic_bookmark, selectedNavItem == 2, onClick = { onNavItemSelect(2) })
|
|
|
|
|
|
NavRailItem("下载", R.drawable.ic_download, selectedNavItem == 3, onClick = { onNavItemSelect(3) })
|
|
|
|
|
|
NavRailItem("支持", R.drawable.ic_support, selectedNavItem == 4, onClick = { onNavItemSelect(4) })
|
|
|
|
|
|
NavRailItem("设置", R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun NavRailItem(text: String, iconRes: Int, selected: Boolean, onClick: () -> Unit = {}) {
|
|
|
|
|
|
val scale by androidx.compose.animation.core.animateFloatAsState(
|
|
|
|
|
|
targetValue = if (selected) 1.05f else 1f,
|
|
|
|
|
|
animationSpec = androidx.compose.animation.core.spring(),
|
|
|
|
|
|
label = "nav_scale"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
Column(
|
|
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.scale(scale)
|
|
|
|
|
|
.clickable(
|
|
|
|
|
|
indication = null,
|
|
|
|
|
|
interactionSource = remember { MutableInteractionSource() }
|
|
|
|
|
|
) { onClick() }
|
|
|
|
|
|
.padding(vertical = 4.dp)
|
|
|
|
|
|
) {
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.size(48.dp)
|
|
|
|
|
|
.shadow(if (selected) 8.dp else 0.dp, RoundedCornerShape(14.dp))
|
|
|
|
|
|
.clip(RoundedCornerShape(14.dp))
|
|
|
|
|
|
.background(
|
|
|
|
|
|
if (selected) {
|
|
|
|
|
|
Brush.linearGradient(
|
|
|
|
|
|
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Brush.linearGradient(
|
|
|
|
|
|
listOf(Color(0x22FFFFFF), Color(0x22FFFFFF))
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
),
|
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
|
) {
|
|
|
|
|
|
Image(
|
|
|
|
|
|
painter = painterResource(id = iconRes),
|
|
|
|
|
|
contentDescription = text,
|
|
|
|
|
|
modifier = Modifier.size(24.dp),
|
|
|
|
|
|
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
|
|
|
|
|
|
if (selected) Color.Black else Color(0xFFB0B0B0)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = text,
|
|
|
|
|
|
color = if (selected) Color.White else Color(0xFFB0B0B0),
|
|
|
|
|
|
fontSize = 13.sp,
|
|
|
|
|
|
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
|
|
|
|
|
|
maxLines = 1
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun TopBar() {
|
|
|
|
|
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Text(text = "控制台", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White)
|
2025-11-26 02:30:03 +00:00
|
|
|
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
|
Box(modifier = Modifier.width(280.dp).height(40.dp).clip(RoundedCornerShape(20.dp)).background(Color(0x22121212)).padding(horizontal = 16.dp), contentAlignment = Alignment.CenterStart) {
|
|
|
|
|
|
Text(text = "搜索关键词", color = Color(0xFF9AA0A6))
|
|
|
|
|
|
}
|
|
|
|
|
|
GradientButton("+ 添加设备")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun GradientButton(text: String, onClick: () -> Unit = {}) {
|
|
|
|
|
|
Box(modifier = Modifier.height(36.dp).clip(RoundedCornerShape(20.dp)).background(Brush.linearGradient(listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF)))).clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { onClick() }.padding(horizontal = 16.dp), contentAlignment = Alignment.Center) {
|
|
|
|
|
|
Text(text = text, color = Color.Black, fontWeight = FontWeight.Medium)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun MyDevicesGrid(selectedRoom: Int, modifier: Modifier = Modifier) {
|
|
|
|
|
|
val devices = when (selectedRoom) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
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)
|
2025-11-26 02:30:03 +00:00
|
|
|
|
)
|
|
|
|
|
|
else -> emptyList()
|
|
|
|
|
|
}
|
2025-11-26 03:23:00 +00:00
|
|
|
|
LazyVerticalGrid(
|
|
|
|
|
|
columns = GridCells.Adaptive(minSize = 140.dp),
|
|
|
|
|
|
modifier = modifier,
|
|
|
|
|
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
|
) {
|
2025-11-26 02:30:03 +00:00
|
|
|
|
items(devices) { d -> DeviceCard(d) }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
2025-11-26 03:23:00 +00:00
|
|
|
|
fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间") {
|
|
|
|
|
|
// 根据房间生成不同的数据
|
|
|
|
|
|
val values = remember(roomName) {
|
|
|
|
|
|
List(10) { (10..35).random().toFloat() }
|
|
|
|
|
|
}
|
|
|
|
|
|
val totalPower = remember(roomName) { (15.0 + Math.random() * 30).toString().take(5) }
|
|
|
|
|
|
val totalHours = remember(roomName) { (10 + (Math.random() * 40).toInt()) }
|
|
|
|
|
|
|
2025-11-26 02:30:03 +00:00
|
|
|
|
Box(modifier = modifier.height(220.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).padding(16.dp)) {
|
|
|
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Column {
|
|
|
|
|
|
Text(text = "使用状态", fontWeight = FontWeight.SemiBold, color = Color.White)
|
|
|
|
|
|
Text(text = roomName, fontSize = 12.sp, color = Color(0xFF9AA0A6))
|
|
|
|
|
|
}
|
2025-11-26 02:30:03 +00:00
|
|
|
|
Row(horizontalArrangement = Arrangement.spacedBy(24.dp)) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Column {
|
|
|
|
|
|
Text(text = "${totalPower}kWh", color = Color.White)
|
|
|
|
|
|
Text(text = "总消耗", color = Color(0xFF9AA0A6), fontSize = 12.sp)
|
|
|
|
|
|
}
|
|
|
|
|
|
Column {
|
|
|
|
|
|
Text(text = "${totalHours}h", color = Color.White)
|
|
|
|
|
|
Text(text = "总时长", color = Color(0xFF9AA0A6), fontSize = 12.sp)
|
|
|
|
|
|
}
|
2025-11-26 02:30:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.Bottom) {
|
|
|
|
|
|
values.forEach { v ->
|
|
|
|
|
|
Box(modifier = Modifier.weight(1f).height(v.dp).clip(RoundedCornerShape(6.dp)).background(Color(0xFF2A2A2A)))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
2025-11-26 03:23:00 +00:00
|
|
|
|
fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间") {
|
2025-11-26 02:30:03 +00:00
|
|
|
|
var temp by remember { mutableStateOf(24f) }
|
2025-11-26 03:23:00 +00:00
|
|
|
|
var isOn by remember { mutableStateOf(true) }
|
|
|
|
|
|
|
2025-11-26 02:30:03 +00:00
|
|
|
|
Box(modifier = modifier.height(220.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).padding(16.dp)) {
|
|
|
|
|
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Column {
|
|
|
|
|
|
Text(text = "空调", fontWeight = FontWeight.SemiBold, color = Color.White)
|
|
|
|
|
|
Text(text = roomName, fontSize = 12.sp, color = Color(0xFF9AA0A6))
|
|
|
|
|
|
}
|
|
|
|
|
|
Switch(checked = isOn, onCheckedChange = { isOn = it })
|
2025-11-26 02:30:03 +00:00
|
|
|
|
}
|
2025-11-26 03:23:00 +00:00
|
|
|
|
Text(text = "${temp.toInt()}°C", style = MaterialTheme.typography.headlineMedium, color = Color.White)
|
|
|
|
|
|
Slider(
|
|
|
|
|
|
value = temp,
|
|
|
|
|
|
onValueChange = { temp = it },
|
|
|
|
|
|
valueRange = 16f..32f,
|
|
|
|
|
|
enabled = isOn
|
|
|
|
|
|
)
|
2025-11-26 02:30:03 +00:00
|
|
|
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
|
|
|
|
Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212)))
|
|
|
|
|
|
Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212)))
|
|
|
|
|
|
Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212)))
|
|
|
|
|
|
Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212)))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun LightList(modifier: Modifier = Modifier) {
|
|
|
|
|
|
val lights = listOf(
|
|
|
|
|
|
"灯光 1" to 0.6f,
|
|
|
|
|
|
"灯光 2" to 0.8f,
|
|
|
|
|
|
"灯光 3" to 0.45f,
|
|
|
|
|
|
"灯光 4" to 0.6f,
|
|
|
|
|
|
"灯光 5" to 0.6f
|
|
|
|
|
|
)
|
|
|
|
|
|
Column(modifier = modifier.fillMaxHeight(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
|
|
|
|
Text(text = "灯光", fontWeight = FontWeight.SemiBold)
|
|
|
|
|
|
lights.forEach { (name, percent) ->
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clip(RoundedCornerShape(16.dp)).background(Color(0x26121212)).padding(12.dp)) {
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
|
Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212)))
|
|
|
|
|
|
Column { Text(text = name); Text(text = "${(percent * 100).toInt()}%", color = Color(0xFF9AA0A6)) }
|
|
|
|
|
|
}
|
|
|
|
|
|
DotsProgress(percent)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun DotsProgress(percent: Float) {
|
|
|
|
|
|
val total = 12
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
|
repeat(total) { i ->
|
|
|
|
|
|
val filled = i < (percent * total).toInt()
|
|
|
|
|
|
Box(modifier = Modifier.size(6.dp).clip(RoundedCornerShape(3.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) }
|
|
|
|
|
|
|
|
|
|
|
|
val scale by androidx.compose.animation.core.animateFloatAsState(
|
|
|
|
|
|
targetValue = if (pressed) 0.96f else 1f,
|
|
|
|
|
|
animationSpec = androidx.compose.animation.core.spring(
|
|
|
|
|
|
dampingRatio = androidx.compose.animation.core.Spring.DampingRatioMediumBouncy,
|
|
|
|
|
|
stiffness = androidx.compose.animation.core.Spring.StiffnessLow
|
|
|
|
|
|
),
|
|
|
|
|
|
label = "scale"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
val elevation by androidx.compose.animation.core.animateDpAsState(
|
|
|
|
|
|
targetValue = if (selected) 16.dp else if (pressed) 4.dp else 8.dp,
|
|
|
|
|
|
animationSpec = androidx.compose.animation.core.tween(300),
|
|
|
|
|
|
label = "elevation"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
val view = LocalView.current
|
|
|
|
|
|
|
|
|
|
|
|
LaunchedEffect(interaction) {
|
|
|
|
|
|
interaction.interactions.collect { i ->
|
|
|
|
|
|
when (i) {
|
|
|
|
|
|
is PressInteraction.Press -> pressed = true
|
|
|
|
|
|
is PressInteraction.Release, is PressInteraction.Cancel -> pressed = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = modifier
|
|
|
|
|
|
.height(height)
|
|
|
|
|
|
.scale(scale)
|
|
|
|
|
|
.shadow(elevation, shape)
|
|
|
|
|
|
.background(
|
|
|
|
|
|
if (selected) {
|
|
|
|
|
|
brush
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Brush.linearGradient(
|
|
|
|
|
|
listOf(
|
|
|
|
|
|
Color(0x40FFFFFF),
|
|
|
|
|
|
Color(0x20FFFFFF)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
shape = shape
|
|
|
|
|
|
)
|
|
|
|
|
|
.border(
|
|
|
|
|
|
width = if (selected) 2.dp else 1.dp,
|
|
|
|
|
|
brush = if (selected) {
|
|
|
|
|
|
Brush.linearGradient(
|
|
|
|
|
|
listOf(
|
|
|
|
|
|
Color(0x99FFFFFF),
|
|
|
|
|
|
Color(0x66FFFFFF)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Brush.linearGradient(
|
|
|
|
|
|
listOf(
|
|
|
|
|
|
Color(0x33FFFFFF),
|
|
|
|
|
|
Color(0x11FFFFFF)
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
shape = shape
|
|
|
|
|
|
)
|
|
|
|
|
|
.clickable(indication = null, interactionSource = interaction) {
|
|
|
|
|
|
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
|
|
|
|
onClick()
|
|
|
|
|
|
}
|
|
|
|
|
|
.padding(24.dp),
|
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
|
) {
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = text,
|
|
|
|
|
|
color = if (selected) Color.White else Color(0xFFB0B0B0),
|
|
|
|
|
|
fontWeight = if (selected) FontWeight.Bold else FontWeight.Medium,
|
|
|
|
|
|
fontSize = if (selected) 22.sp else 20.sp,
|
|
|
|
|
|
style = MaterialTheme.typography.titleLarge
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
2025-11-26 03:23:00 +00:00
|
|
|
|
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)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-11-26 02:30:03 +00:00
|
|
|
|
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) {
|
|
|
|
|
|
Box(modifier = modifier.height(100.dp).clip(RoundedCornerShape(16.dp)).background(Color(0x26121212)).padding(12.dp)) {
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
|
|
|
|
|
Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
|
Box(modifier = Modifier.size(36.dp).clip(RoundedCornerShape(10.dp)).background(Color(0x22121212)))
|
|
|
|
|
|
Column { Text(text = name); Text(text = "${(percent * 100).toInt()}%", color = Color(0xFF9AA0A6)) }
|
|
|
|
|
|
}
|
|
|
|
|
|
DotsProgress(percent)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
|
fun AddRoomDialog(
|
|
|
|
|
|
onDismiss: () -> Unit,
|
|
|
|
|
|
onConfirm: (String) -> Unit
|
|
|
|
|
|
) {
|
|
|
|
|
|
var roomName by remember { mutableStateOf("") }
|
|
|
|
|
|
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.fillMaxSize()
|
|
|
|
|
|
.background(Color(0x80000000))
|
|
|
|
|
|
.clickable(
|
|
|
|
|
|
indication = null,
|
|
|
|
|
|
interactionSource = remember { MutableInteractionSource() }
|
|
|
|
|
|
) { onDismiss() },
|
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
|
) {
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.width(400.dp)
|
|
|
|
|
|
.clip(RoundedCornerShape(24.dp))
|
|
|
|
|
|
.background(Color(0xFF1E1E1E))
|
|
|
|
|
|
.clickable(
|
|
|
|
|
|
indication = null,
|
|
|
|
|
|
interactionSource = remember { MutableInteractionSource() }
|
|
|
|
|
|
) { /* 阻止点击穿透 */ }
|
|
|
|
|
|
.padding(24.dp)
|
|
|
|
|
|
) {
|
|
|
|
|
|
Column(
|
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(20.dp)
|
|
|
|
|
|
) {
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = "添加房间",
|
|
|
|
|
|
fontSize = 20.sp,
|
|
|
|
|
|
fontWeight = FontWeight.Bold,
|
|
|
|
|
|
color = Color.White
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
androidx.compose.material3.TextField(
|
|
|
|
|
|
value = roomName,
|
|
|
|
|
|
onValueChange = { roomName = it },
|
|
|
|
|
|
placeholder = { Text("请输入房间名称", color = Color(0xFF9AA0A6)) },
|
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
|
colors = androidx.compose.material3.TextFieldDefaults.colors(
|
|
|
|
|
|
focusedContainerColor = Color(0x22FFFFFF),
|
|
|
|
|
|
unfocusedContainerColor = Color(0x22FFFFFF),
|
|
|
|
|
|
focusedTextColor = Color.White,
|
|
|
|
|
|
unfocusedTextColor = Color.White,
|
|
|
|
|
|
cursorColor = Color(0xFFA9F0FF),
|
|
|
|
|
|
focusedIndicatorColor = Color.Transparent,
|
|
|
|
|
|
unfocusedIndicatorColor = Color.Transparent
|
|
|
|
|
|
),
|
|
|
|
|
|
shape = RoundedCornerShape(12.dp),
|
|
|
|
|
|
singleLine = true
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
Row(
|
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
|
|
|
|
) {
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.weight(1f)
|
|
|
|
|
|
.height(48.dp)
|
|
|
|
|
|
.clip(RoundedCornerShape(12.dp))
|
|
|
|
|
|
.background(Color(0x22FFFFFF))
|
|
|
|
|
|
.clickable(
|
|
|
|
|
|
indication = null,
|
|
|
|
|
|
interactionSource = remember { MutableInteractionSource() }
|
|
|
|
|
|
) { onDismiss() },
|
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
|
) {
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = "取消",
|
|
|
|
|
|
color = Color(0xFFB0B0B0),
|
|
|
|
|
|
fontWeight = FontWeight.Medium
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Box(
|
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
|
.weight(1f)
|
|
|
|
|
|
.height(48.dp)
|
|
|
|
|
|
.clip(RoundedCornerShape(12.dp))
|
|
|
|
|
|
.background(
|
|
|
|
|
|
Brush.linearGradient(
|
|
|
|
|
|
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
.clickable(
|
|
|
|
|
|
indication = null,
|
|
|
|
|
|
interactionSource = remember { MutableInteractionSource() },
|
|
|
|
|
|
enabled = roomName.isNotBlank()
|
|
|
|
|
|
) {
|
|
|
|
|
|
if (roomName.isNotBlank()) {
|
|
|
|
|
|
onConfirm(roomName.trim())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.alpha(if (roomName.isNotBlank()) 1f else 0.5f),
|
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
|
) {
|
|
|
|
|
|
Text(
|
|
|
|
|
|
text = "确定",
|
|
|
|
|
|
color = Color.Black,
|
|
|
|
|
|
fontWeight = FontWeight.Bold
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|