diff --git a/app/src/main/java/com/example/smarthome/MainActivity.kt b/app/src/main/java/com/example/smarthome/MainActivity.kt
index 91c3caa..4a501c8 100644
--- a/app/src/main/java/com/example/smarthome/MainActivity.kt
+++ b/app/src/main/java/com/example/smarthome/MainActivity.kt
@@ -1,6 +1,5 @@
package com.example.smarthome
-import android.graphics.drawable.Drawable
import android.os.Bundle
import androidx.activity.ComponentActivity
import com.example.smarthome.ui.theme.SmartHomeTheme
@@ -54,35 +53,22 @@ class MainActivity : ComponentActivity() {
}
private fun hideStatusBar() {
- // 设置全屏模式,让内容延伸到系统栏区域
+ // 让内容延伸到系统栏区域
WindowCompat.setDecorFitsSystemWindows(window, false)
-
- // 设置全屏标志
- window.setFlags(
- android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN,
- android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN
- )
-
- // 设置状态栏和导航栏为透明
- window.statusBarColor = android.graphics.Color.TRANSPARENT
- window.navigationBarColor = android.graphics.Color.TRANSPARENT
-
- // 隐藏状态栏和导航栏
- window.decorView.systemUiVisibility = (
- android.view.View.SYSTEM_UI_FLAG_FULLSCREEN
- or android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- or android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE
- or android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
- or android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- or android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
- )
-
- // 支持刘海屏设备
- val lp = window.attributes
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
- lp.layoutInDisplayCutoutMode = android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+
+ // 使用现代 WindowInsetsController API 隐藏状态栏和导航栏
+ androidx.core.view.WindowInsetsControllerCompat(window, window.decorView).apply {
+ hide(androidx.core.view.WindowInsetsCompat.Type.statusBars())
+ hide(androidx.core.view.WindowInsetsCompat.Type.navigationBars())
+ systemBarsBehavior = androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
+
+ // 支持刘海屏设备
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ window.attributes = window.attributes.also {
+ it.layoutInDisplayCutoutMode = android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+ }
}
- window.attributes = lp
}
}
@@ -134,10 +120,8 @@ fun MainContent() {
rooms = roomList
}
- // 初始化背景管理器并获取当前选中的背景
- androidx.compose.runtime.LaunchedEffect(Unit) {
- BackgroundManager.init(context)
- }
+ // 初始化背景管理器并获取当前选中的背景(同步初始化,避免首帧闪烁默认背景)
+ remember { BackgroundManager.init(context) }
val selectedBgId by BackgroundManager.selectedBackground.collectAsState()
val currentBg = BackgroundManager.backgrounds.getOrNull(selectedBgId)
val backgroundRes = currentBg?.resourceId ?: R.drawable.background1
diff --git a/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt b/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt
index 4025617..c301ad3 100644
--- a/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt
+++ b/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt
@@ -10,7 +10,6 @@ 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.*
@@ -20,13 +19,10 @@ 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.draw.blur
-import androidx.compose.ui.draw.drawBehind
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.graphics.drawscope.DrawScope
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -71,8 +67,6 @@ import android.content.ContextWrapper
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.HazeTint
-import dev.chrisbanes.haze.haze
-import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.hazeEffect
@@ -93,6 +87,12 @@ fun MainScaffold(
// Haze state for blur effects
val hazeState = remember { HazeState() }
+ // 读取当前选中的背景壁纸
+ val context = androidx.compose.ui.platform.LocalContext.current
+ remember { BackgroundManager.init(context) }
+ val selectedBgId by BackgroundManager.selectedBackground.collectAsState()
+ val backgroundRes = BackgroundManager.backgrounds.getOrNull(selectedBgId)?.resourceId ?: R.drawable.background1
+
Box(modifier = Modifier.fillMaxSize()) {
// 背景作为模糊源
Box(
@@ -102,7 +102,7 @@ fun MainScaffold(
) {
// 背景图片
Image(
- painter = painterResource(id = R.drawable.background),
+ painter = painterResource(id = backgroundRes),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
@@ -181,7 +181,7 @@ fun MainScaffold(
}
}
}
- }
+}
}
@Composable
@@ -349,11 +349,97 @@ fun AllDevicesOverview(modifier: Modifier = Modifier) {
Device("影音室电视", "运行中", R.drawable.ic_media, true),
Device("游戏房主机", "待机中", R.drawable.ic_media, false)
)
+ val rows = allDevices.chunked(2)
Column(
modifier = modifier,
- verticalArrangement = Arrangement.spacedBy(8.dp)
+ verticalArrangement = Arrangement.spacedBy(12.dp)
) {
- allDevices.forEach { d -> DeviceCard(d) }
+ rows.forEach { rowDevices ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ rowDevices.forEach { d ->
+ DeviceSquareCard(d, modifier = Modifier.weight(1f))
+ }
+ if (rowDevices.size < 2) {
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun DeviceSquareCard(d: Device, modifier: Modifier = Modifier) {
+ var active by remember { mutableStateOf(d.on) }
+ val accentColor = if (active) Color(0xFF00D1FF) else Color(0xFF5A5A6E)
+ val bgAlpha = if (active) 0.75f else 0.45f
+
+ Box(
+ modifier = modifier
+ .aspectRatio(1f)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color(0xFF1A1A1A).copy(alpha = bgAlpha))
+ .border(
+ width = 1.dp,
+ color = if (active) accentColor.copy(alpha = 0.4f) else Color.White.copy(alpha = 0.12f),
+ shape = RoundedCornerShape(20.dp)
+ )
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { active = !active }
+ .padding(16.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.Top
+ ) {
+ Box(
+ modifier = Modifier
+ .size(44.dp)
+ .clip(RoundedCornerShape(12.dp))
+ .background(accentColor.copy(alpha = if (active) 0.2f else 0.1f)),
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ painter = painterResource(id = d.icon),
+ contentDescription = null,
+ modifier = Modifier.size(26.dp),
+ colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
+ if (active) accentColor else Color(0xFF6A6A7E)
+ )
+ )
+ }
+ Box(
+ modifier = Modifier
+ .size(8.dp)
+ .clip(RoundedCornerShape(4.dp))
+ .background(if (active) Color(0xFF00E676) else Color(0xFF4A4A5E))
+ )
+ }
+ Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(
+ text = d.name,
+ color = Color.White,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.SemiBold,
+ maxLines = 1
+ )
+ Text(
+ text = d.sub,
+ color = if (active) accentColor else Color(0xFF6A6A7E),
+ fontSize = 12.sp,
+ maxLines = 1
+ )
+ }
+ }
}
}
@@ -401,24 +487,16 @@ fun QuickActions() {
@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),
@@ -705,69 +783,41 @@ fun FloatingBottomNav(
R.drawable.ic_security,
R.drawable.ic_settings
)
-
+
val itemWidth = 52.dp
val itemSpacing = 4.dp
-
+
// 是否正在拖动
var isDragging by remember { mutableStateOf(false) }
// 拖动时的临时选中项
var dragIndex by remember { mutableStateOf(selectedNavItem) }
-
- // 呼吸效果:不操作时变透明
- var lastInteractionTime by remember { mutableStateOf(System.currentTimeMillis()) }
- var isIdle by remember { mutableStateOf(false) }
-
- // 检测空闲状态(3秒无操作)
- LaunchedEffect(lastInteractionTime) {
- kotlinx.coroutines.delay(3000)
- isIdle = true
- }
-
- // 菜单栏透明度动画
- val navBarAlpha by animateFloatAsState(
- targetValue = if (isIdle && !isDragging) 0.4f else 1f,
- animationSpec = tween(durationMillis = 500, easing = androidx.compose.animation.core.EaseInOut),
- label = "nav_bar_alpha"
- )
-
+
// 当外部 selectedNavItem 变化时,同步更新 dragIndex
LaunchedEffect(selectedNavItem) {
if (!isDragging) {
dragIndex = selectedNavItem
}
}
-
- // 选中框的动画偏移量 - 跟随 dragIndex(拖动时)或 selectedNavItem(点击时)
+
+ // 选中指示器偏移 - 拖动时 snap 跟随手指,松手后快速回弹
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
+ animationSpec = if (isDragging) snap() else tween(
+ durationMillis = 180,
+ easing = androidx.compose.animation.core.FastOutSlowInEasing
),
- label = "liquid_glass_offset"
+ label = "nav_indicator_offset"
)
-
+
// 菜单栏高度(胶囊形)
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)
- .alpha(navBarAlpha)
.shadow(24.dp, RoundedCornerShape(50))
.clip(RoundedCornerShape(50))
.background(
@@ -792,9 +842,6 @@ fun FloatingBottomNav(
.pointerInput(Unit) {
detectHorizontalDragGestures(
onDragStart = { offset ->
- // 重置空闲状态
- isIdle = false
- lastInteractionTime = System.currentTimeMillis()
isDragging = true
val index = (offset.x / (itemWidth.toPx() + itemSpacing.toPx())).toInt()
.coerceIn(0, navItems.size - 1)
@@ -802,12 +849,10 @@ fun FloatingBottomNav(
},
onDragEnd = {
isDragging = false
- lastInteractionTime = System.currentTimeMillis()
onNavItemSelect(dragIndex)
},
onDragCancel = {
isDragging = false
- lastInteractionTime = System.currentTimeMillis()
dragIndex = selectedNavItem
},
onHorizontalDrag = { change, _ ->
@@ -824,9 +869,10 @@ fun FloatingBottomNav(
Box(
contentAlignment = Alignment.CenterStart
) {
- // 选中指示器 - 按住时放大贴合菜单栏边缘
+ // 选中指示器
+ val indicatorSize = innerHeight.value
val horizontalOffset = (itemWidth.value - indicatorSize) / 2f
-
+
Box(
modifier = Modifier
.align(Alignment.CenterStart)
@@ -861,12 +907,7 @@ fun FloatingBottomNav(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
- if (!isDragging) {
- // 重置空闲状态
- isIdle = false
- lastInteractionTime = System.currentTimeMillis()
- onNavItemSelect(index)
- }
+ onNavItemSelect(index)
},
contentAlignment = Alignment.Center
) {
@@ -962,18 +1003,11 @@ fun ThemeModeToggle(isDarkMode: Boolean, onToggle: () -> Unit) {
@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() }
@@ -1182,20 +1216,20 @@ fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) {
color = accentColor.copy(alpha = 0.2f),
shape = RoundedCornerShape(16.dp)
)
- .padding(start = 4.dp, top = 4.dp, bottom = 4.dp, end = 20.dp),
+ .padding(start = 4.dp, top = 2.dp, bottom = 2.dp, end = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 天气图标(带阴影的渐变背景)
Box(
modifier = Modifier
- .size(48.dp)
+ .size(30.dp)
.shadow(
- elevation = 8.dp,
- shape = RoundedCornerShape(14.dp),
+ elevation = 4.dp,
+ shape = RoundedCornerShape(8.dp),
spotColor = weatherColors.second.copy(alpha = 0.3f)
)
- .clip(RoundedCornerShape(14.dp))
+ .clip(RoundedCornerShape(8.dp))
.background(
Brush.linearGradient(
colors = listOf(weatherColors.first, weatherColors.second),
@@ -1215,74 +1249,37 @@ fun WeatherCard(weatherInfo: WeatherInfo, isLoading: Boolean) {
else -> R.drawable.ic_weather_cloudy
}),
contentDescription = "天气",
- modifier = Modifier.size(24.dp),
+ modifier = Modifier.size(18.dp),
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(Color.White)
)
}
// 温度和天气状况
- Column {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(
text = if (isLoading) "--°C" else "${weatherInfo.temperature}°C",
- fontSize = 22.sp,
+ fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
- lineHeight = 22.sp
+ lineHeight = 15.sp
)
- Text(
- text = if (isLoading) "加载中" else weatherInfo.weather,
- fontSize = 11.sp,
- fontWeight = FontWeight.Medium,
- color = accentColor.copy(alpha = 0.8f)
- )
- }
-
- // 分隔线
- Box(
- modifier = Modifier
- .width(1.dp)
- .height(32.dp)
- .background(Color.White.copy(alpha = 0.1f))
- )
-
- // 湿度和AQI
- Column(
- verticalArrangement = Arrangement.spacedBy(4.dp)
- ) {
- // 湿度
Row(
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(6.dp)
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
- Image(
- painter = painterResource(id = R.drawable.ic_humidity),
- contentDescription = "湿度",
- modifier = Modifier.size(12.dp),
- colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(Color(0xFFD1D5DB))
- )
Text(
- text = if (isLoading) "--%" else "${weatherInfo.humidity}%",
+ text = if (isLoading) "加载中" else weatherInfo.weather,
fontSize = 11.sp,
- color = Color(0xFFD1D5DB)
- )
- }
-
- // AQI
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(6.dp)
- ) {
- Image(
- painter = painterResource(id = R.drawable.ic_wind),
- contentDescription = "AQI",
- modifier = Modifier.size(12.dp),
- colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(Color(0xFFD1D5DB))
- )
- Text(
- text = if (isLoading) "AQI --" else "AQI ${weatherInfo.aqi}",
- fontSize = 11.sp,
- color = Color(0xFFD1D5DB)
+ color = accentColor.copy(alpha = 0.9f)
)
+ if (!isLoading && weatherInfo.city.isNotEmpty() && weatherInfo.city != "未知") {
+ Text(text = "·", fontSize = 11.sp, color = Color.White.copy(alpha = 0.3f))
+ Text(
+ text = weatherInfo.city,
+ fontSize = 11.sp,
+ color = Color.White.copy(alpha = 0.55f)
+ )
+ }
}
}
}
@@ -1400,21 +1397,14 @@ fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间",
// 主题色
val accentColor = Color(0xFF00D1FF)
-
+
Box(
modifier = modifier
.clip(RoundedCornerShape(24.dp))
- .hazeEffect(
- state = hazeState,
- style = HazeStyle(
- blurRadius = 40.dp,
- tint = HazeTint(Color.White.copy(alpha = 0.15f)),
- noiseFactor = 0.0f
- )
- )
+ .background(Color(0xFF1A1A1A).copy(alpha = 0.55f))
.border(
width = 1.dp,
- color = Color.White.copy(alpha = 0.3f),
+ color = Color.White.copy(alpha = 0.15f),
shape = RoundedCornerShape(24.dp)
)
.padding(20.dp)
@@ -1535,7 +1525,7 @@ fun EnergyTrendChart() {
val prevX = (index - 1) * stepX
val prevY = canvasHeight - (dataPoints[index - 1] * canvasHeight)
val controlX = (prevX + x) / 2
- quadraticBezierTo(controlX, prevY, x, y)
+ quadraticTo(controlX, prevY, x, y)
}
}
@@ -1565,7 +1555,7 @@ fun EnergyTrendChart() {
val prevX = (index - 1) * stepX
val prevY = canvasHeight - (dataPoints[index - 1] * canvasHeight)
val controlX = (prevX + x) / 2
- quadraticBezierTo(controlX, prevY, x, y)
+ quadraticTo(controlX, prevY, x, y)
}
}
}
@@ -1610,21 +1600,14 @@ fun AirConditionerCard(
val pm25 = remember { mutableStateOf(35) }
val co2 = remember { mutableStateOf(420) }
- // 外层大卡片 - 使用 Haze 毛玻璃效果
+ // 外层大卡片 - 使用半透明背景替代实时模糊,降低滚动时 GPU 开销
Box(
modifier = modifier
.clip(RoundedCornerShape(24.dp))
- .hazeEffect(
- state = hazeState,
- style = HazeStyle(
- blurRadius = 40.dp,
- tint = HazeTint(Color.White.copy(alpha = 0.15f)),
- noiseFactor = 0.0f
- )
- )
+ .background(Color(0xFF1A1A1A).copy(alpha = 0.55f))
.border(
width = 1.dp,
- color = Color.White.copy(alpha = 0.3f),
+ color = Color.White.copy(alpha = 0.15f),
shape = RoundedCornerShape(24.dp)
)
.padding(20.dp)
@@ -2023,9 +2006,7 @@ fun ModeButton(
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
@@ -2034,41 +2015,17 @@ fun ModeButton(
"观影" -> R.drawable.ic_media
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 view = LocalView.current
- LaunchedEffect(interaction) {
- interaction.interactions.collect { i ->
- when (i) {
- is PressInteraction.Press -> pressed = true
- is PressInteraction.Release, is PressInteraction.Cancel -> pressed = false
- }
- }
- }
+ val view = LocalView.current
+ val interaction = remember { MutableInteractionSource() }
Box(
modifier = modifier
- .scale(scale)
.clip(shape)
.then(
if (selected) {
Modifier
- .shadow(elevation, shape, clip = false)
+ .shadow(12.dp, shape, clip = false)
.background(Color.White, shape)
.border(
width = 1.dp,
@@ -2312,130 +2269,41 @@ fun LightSlider(
@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(),
+ color = Color(0xFFFFD700).copy(alpha = 0.5f),
+ radius = 16.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) {
- // 使用不同的起始位置和速度让雨滴错开
+ weather.contains("雨") || weather.contains("雪") -> {
+ // 静态雨滴/雪花装饰点
+ val count = if (weather.contains("雪")) 8 else 12
+ repeat(count) { i ->
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
-
+ val y = 12.dp.toPx() + (i % 4) * 10.dp.toPx()
drawCircle(
- color = Color.White.copy(alpha = 0.5f),
- radius = (1.5f + i % 2).dp.toPx(),
- center = Offset(x, currentY)
+ color = Color.White.copy(alpha = 0.25f),
+ radius = (if (weather.contains("雪")) 1.5f else 1f).dp.toPx(),
+ center = Offset(x, y)
)
}
}
-
- // 多云/阴天:绘制云朵
weather.contains("云") || weather.contains("阴") -> {
- // 绘制几个叠加的圆组成云 - 缩小尺寸
- val cloudColor = Color.White.copy(alpha = 0.15f)
- val baseX = width - 35.dp.toPx() + cloudOffset * 0.5f
+ val cloudColor = Color.White.copy(alpha = 0.12f)
+ val baseX = width - 35.dp.toPx()
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()))
+ drawCircle(color = cloudColor, radius = 14.dp.toPx(), center = Offset(baseX, baseY))
+ drawCircle(color = cloudColor, radius = 11.dp.toPx(), center = Offset(baseX - 16.dp.toPx(), baseY + 4.dp.toPx()))
+ drawCircle(color = cloudColor, radius = 12.dp.toPx(), center = Offset(baseX + 13.dp.toPx(), baseY + 2.dp.toPx()))
}
}
}
@@ -2445,7 +2313,7 @@ fun WeatherEffectLayer(weather: String, modifier: Modifier = Modifier) {
@Composable
fun BlurGlassCard(
modifier: Modifier = Modifier,
- blurRadius: Float = 25f,
+ blurRadius: Float = 16f,
overlayColor: String = "#40FFFFFF",
content: @Composable () -> Unit
) {
@@ -2513,8 +2381,8 @@ fun GlassSidebar(
.hazeEffect(
state = hazeState,
style = HazeStyle(
- blurRadius = 40.dp,
- tint = HazeTint(Color.White.copy(alpha = 0.15f)),
+ blurRadius = 16.dp,
+ tint = HazeTint(Color.White.copy(alpha = 0.12f)),
noiseFactor = 0.0f
)
)
@@ -2542,14 +2410,14 @@ fun GlassSidebar(
// 品牌标题
Column {
Text(
- text = "HEMS",
+ text = "TZ",
color = Color.White,
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 1.sp
)
Text(
- text = "Smart Home",
+ text = "Energy-saving system",
color = Color.White.copy(alpha = 0.6f),
fontSize = 14.sp,
letterSpacing = 0.5.sp
@@ -2665,6 +2533,7 @@ fun GlassSidebar(
}
}
}
+}
// 扩展函数用于获取 Activity
fun Context.findActivity(): Activity {
@@ -2675,4 +2544,3 @@ fun Context.findActivity(): Activity {
}
throw IllegalStateException("Permissions should be called in the context of an Activity")
}
-}
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index d3dee3d..0b5d710 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,6 +2,6 @@
-
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index d3dee3d..0b5d710 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,6 +2,6 @@
-
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index 7a83a02..82b70d8 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
index 7a83a02..82b70d8 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 818b1c3..63e352b 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
index 818b1c3..63e352b 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index fad471c..6a06973 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
index fad471c..6a06973 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index aa2ae45..9aabe91 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
index aa2ae45..9aabe91 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index ac85b89..991c108 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
index ac85b89..991c108 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ