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

909 lines
36 KiB
Kotlin
Raw Normal View History

package com.example.smarthome.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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 = {}
) {
Row(modifier = Modifier.fillMaxSize().background(Color(0xFF121212))) {
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 }
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
AirConditionerCard(modifier = Modifier.weight(1f))
UsageStatusChart(modifier = Modifier.weight(1f))
}
LightRow(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp))
ModeButtonsRow(onModeSelected = { })
MyDevicesGrid(selectedRoom = selectedRoom, modifier = Modifier.fillMaxSize())
}
if (showAddRoomDialog) {
AddRoomDialog(
onDismiss = { showAddRoomDialog = false },
onConfirm = { roomName ->
onAddRoom(roomName)
showAddRoomDialog = false
}
)
}
}
}
@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)) {
Box(modifier = Modifier.size(48.dp).clip(RoundedCornerShape(16.dp)).background(Color(0x22121212)))
NavRailItem("控制台", R.drawable.ic_dashboard, selectedNavItem == 0, onClick = { onNavItemSelect(0) })
NavRailItem("最近", R.drawable.ic_recent, selectedNavItem == 1, onClick = { onNavItemSelect(1) })
NavRailItem("收藏", R.drawable.ic_bookmark, selectedNavItem == 2, onClick = { onNavItemSelect(2) })
NavRailItem("下载", R.drawable.ic_download, selectedNavItem == 3, onClick = { onNavItemSelect(3) })
NavRailItem("支持", R.drawable.ic_support, selectedNavItem == 4, onClick = { onNavItemSelect(4) })
NavRailItem("设置", R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) })
}
}
@Composable
fun NavRailItem(text: String, iconRes: Int, selected: Boolean, onClick: () -> Unit = {}) {
val scale by androidx.compose.animation.core.animateFloatAsState(
targetValue = if (selected) 1.05f else 1f,
animationSpec = androidx.compose.animation.core.spring(),
label = "nav_scale"
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier
.scale(scale)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onClick() }
.padding(vertical = 4.dp)
) {
Box(
modifier = Modifier
.size(48.dp)
.shadow(if (selected) 8.dp else 0.dp, RoundedCornerShape(14.dp))
.clip(RoundedCornerShape(14.dp))
.background(
if (selected) {
Brush.linearGradient(
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
)
} else {
Brush.linearGradient(
listOf(Color(0x22FFFFFF), Color(0x22FFFFFF))
)
}
),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = iconRes),
contentDescription = text,
modifier = Modifier.size(24.dp),
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
if (selected) Color.Black else Color(0xFFB0B0B0)
)
)
}
Text(
text = text,
color = if (selected) Color.White else Color(0xFFB0B0B0),
fontSize = 13.sp,
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
maxLines = 1
)
}
}
@Composable
fun TopBar() {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Text(text = "控制台", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
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))
}
Box(modifier = Modifier.size(40.dp).clip(RoundedCornerShape(12.dp)).background(Color(0x22121212)))
Box(modifier = Modifier.size(40.dp).clip(RoundedCornerShape(12.dp)).background(Color(0x22121212)))
Box(modifier = Modifier.size(40.dp).clip(RoundedCornerShape(12.dp)).background(Color(0x22121212)))
GradientButton("+ 添加设备")
}
}
}
@Composable
fun GradientButton(text: String, onClick: () -> Unit = {}) {
Box(modifier = Modifier.height(36.dp).clip(RoundedCornerShape(20.dp)).background(Brush.linearGradient(listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF)))).clickable(indication = null, interactionSource = remember { MutableInteractionSource() }) { onClick() }.padding(horizontal = 16.dp), contentAlignment = Alignment.Center) {
Text(text = text, color = Color.Black, fontWeight = FontWeight.Medium)
}
}
@Composable
fun MyDevicesGrid(selectedRoom: Int, modifier: Modifier = Modifier) {
val devices = when (selectedRoom) {
1 -> listOf(
Device("智能电视", "已运行 3 小时", R.drawable.ic_media, true),
Device("音响", "已运行 3 小时", R.drawable.ic_media, false),
Device("路由器", "已运行 3 小时", R.drawable.ic_security, true),
Device("无线网络", "已运行 3 小时", R.drawable.ic_security, true),
Device("暖气", "已运行 3 小时", R.drawable.ic_media, false),
Device("插座", "已运行 3 小时", R.drawable.ic_media, true)
)
else -> emptyList()
}
LazyVerticalGrid(columns = GridCells.Adaptive(minSize = 140.dp), modifier = modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
items(devices) { d -> DeviceCard(d) }
}
}
@Composable
fun UsageStatusChart(modifier: Modifier = Modifier) {
val values = listOf(12f, 18f, 16f, 30f, 20f, 22f, 17f, 19f, 25f, 32f)
Box(modifier = modifier.height(220.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x26121212)).padding(16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(text = "使用状态", fontWeight = FontWeight.SemiBold)
Row(horizontalArrangement = Arrangement.spacedBy(24.dp)) {
Column { Text(text = "35.02kWh"); Text(text = "总消耗", color = Color(0xFF9AA0A6)) }
Column { Text(text = "32h"); Text(text = "总时长", color = Color(0xFF9AA0A6)) }
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.Bottom) {
values.forEach { v ->
Box(modifier = Modifier.weight(1f).height(v.dp).clip(RoundedCornerShape(6.dp)).background(Color(0xFF2A2A2A)))
}
}
}
}
}
@Composable
fun AirConditionerCard(modifier: Modifier = Modifier) {
var temp by remember { mutableStateOf(24f) }
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()) {
Text(text = "空调", fontWeight = FontWeight.SemiBold)
Switch(checked = true, onCheckedChange = {})
}
Text(text = "24°C", style = MaterialTheme.typography.headlineMedium)
Slider(value = temp, onValueChange = { temp = it }, valueRange = 16f..32f)
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
fun LightRow(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
).take(2)
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
)
}
}
}
}
}
}