图标和界面优化

This commit is contained in:
zzh 2026-07-14 09:47:22 +08:00
parent 8051ff242a
commit e2c6741afc
14 changed files with 183 additions and 331 deletions

View File

@ -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

View File

@ -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")
}
}

View File

@ -2,6 +2,6 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground>
<inset android:drawable="@drawable/taozhi_icon_v2" android:inset="10%" />
<inset android:drawable="@drawable/taozhi_icon_v3" android:inset="17%" />
</foreground>
</adaptive-icon>

View File

@ -2,6 +2,6 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground>
<inset android:drawable="@drawable/taozhi_icon_v2" android:inset="10%" />
<inset android:drawable="@drawable/taozhi_icon_v3" android:inset="17%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 68 KiB