diff --git a/app/src/main/java/com/example/smarthome/MainActivity.kt b/app/src/main/java/com/example/smarthome/MainActivity.kt
index 3ce39da..c2793aa 100644
--- a/app/src/main/java/com/example/smarthome/MainActivity.kt
+++ b/app/src/main/java/com/example/smarthome/MainActivity.kt
@@ -26,6 +26,8 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.layout.ContentScale
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.runtime.collectAsState
+import com.example.smarthome.data.BackgroundManager
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -113,10 +115,20 @@ fun AppRoot() {
rooms = roomList
}
+ // 初始化背景管理器并获取当前选中的背景
+ androidx.compose.runtime.LaunchedEffect(Unit) {
+ BackgroundManager.init(context)
+ }
+ val selectedBgId by BackgroundManager.selectedBackground.collectAsState()
+ val currentBg = BackgroundManager.backgrounds.getOrNull(selectedBgId)
+ val backgroundRes = currentBg?.resourceId ?: R.drawable.background1
+ // 根据背景类型调整遮罩透明度:暗色背景减少遮罩,亮色背景增加遮罩
+ val overlayAlpha = if (currentBg?.isDark == true) 0x11 else 0x88
+
Box(modifier = Modifier.fillMaxSize()) {
// 背景图片 - 填充整个屏幕包括系统栏,使用FillBounds确保完全填充
Image(
- painter = painterResource(id = R.drawable.background),
+ painter = painterResource(id = backgroundRes),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillBounds,
@@ -127,7 +139,7 @@ fun AppRoot() {
Box(
modifier = Modifier
.fillMaxSize()
- .background(Color(0x88121212))
+ .background(Color(overlayAlpha shl 24 or 0x121212))
)
// 主内容 - 添加系统栏内边距
diff --git a/app/src/main/java/com/example/smarthome/data/WeatherService.kt b/app/src/main/java/com/example/smarthome/data/WeatherService.kt
index 78f546e..2340249 100644
--- a/app/src/main/java/com/example/smarthome/data/WeatherService.kt
+++ b/app/src/main/java/com/example/smarthome/data/WeatherService.kt
@@ -26,6 +26,11 @@ data class WeatherInfo(
object WeatherService {
private const val TAG = "WeatherService"
+ // 缓存天气数据,避免重复获取
+ private var cachedWeather: WeatherInfo? = null
+ private var lastFetchTime: Long = 0
+ private const val CACHE_DURATION = 10 * 60 * 1000L // 10分钟缓存
+
private val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
@@ -34,8 +39,16 @@ object WeatherService {
/**
* 获取天气信息
* 优先使用GPS定位,失败则用IP定位
+ * 带缓存机制,10分钟内不重复获取
*/
- suspend fun getWeather(context: Context): WeatherInfo {
+ suspend fun getWeather(context: Context, forceRefresh: Boolean = false): WeatherInfo {
+ // 检查缓存
+ val now = System.currentTimeMillis()
+ if (!forceRefresh && cachedWeather != null && (now - lastFetchTime) < CACHE_DURATION) {
+ Log.d(TAG, "Using cached weather data")
+ return cachedWeather!!
+ }
+
return withContext(Dispatchers.IO) {
// 1. 优先尝试GPS定位
val gpsLocation = getGpsLocation(context)
@@ -46,6 +59,8 @@ object WeatherService {
// 用GPS坐标获取天气
val weatherResult = getWeatherByLocation(lat, lon, city)
if (weatherResult != null) {
+ cachedWeather = weatherResult
+ lastFetchTime = now
return@withContext weatherResult
}
}
@@ -54,6 +69,8 @@ object WeatherService {
Log.w(TAG, "GPS failed, trying wttr.in...")
val wttrResult = tryWttrIn()
if (wttrResult != null) {
+ cachedWeather = wttrResult
+ lastFetchTime = now
return@withContext wttrResult
}
@@ -61,6 +78,8 @@ object WeatherService {
Log.w(TAG, "wttr.in failed, trying backup...")
val backupResult = tryBackupApi()
if (backupResult != null) {
+ cachedWeather = backupResult
+ lastFetchTime = now
return@withContext backupResult
}
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 c451f51..b24cbf0 100644
--- a/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt
+++ b/app/src/main/java/com/example/smarthome/ui/MainScaffold.kt
@@ -316,7 +316,7 @@ fun RoomTabs(
}
// 固定的添加按钮
- GradientButton("+ 添加", onClick = onAddRoomClick)
+ GradientButton("+ 添加房间", onClick = onAddRoomClick)
}
}
@@ -490,17 +490,82 @@ fun NavItem(title: String, selected: Boolean) {
@Composable
fun SideNavRail(selectedNavItem: Int = 0, onNavItemSelect: (Int) -> Unit = {}) {
- Column(modifier = Modifier.width(100.dp).fillMaxHeight().padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
- Spacer(modifier = Modifier.height(48.dp))
- NavRailItem("控制台", R.drawable.ic_dashboard, selectedNavItem == 0, onClick = { onNavItemSelect(0) })
- NavRailItem("场景", R.drawable.ic_scene, selectedNavItem == 1, onClick = { onNavItemSelect(1) })
- NavRailItem("自动化", R.drawable.ic_automation, selectedNavItem == 2, onClick = { onNavItemSelect(2) })
- NavRailItem("统计", R.drawable.ic_statistics, selectedNavItem == 3, onClick = { onNavItemSelect(3) })
- NavRailItem("安全", R.drawable.ic_security, selectedNavItem == 4, onClick = { onNavItemSelect(4) })
- NavRailItem("设置", R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) })
+ val context = androidx.compose.ui.platform.LocalContext.current
+ val selectedBg by com.example.smarthome.data.BackgroundManager.selectedBackground.collectAsState()
+ val isDarkMode = com.example.smarthome.data.BackgroundManager.backgrounds.getOrNull(selectedBg)?.isDark ?: false
+
+ Column(
+ modifier = Modifier
+ .width(100.dp)
+ .fillMaxHeight()
+ .padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ // 上半部分:导航项
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Spacer(modifier = Modifier.height(48.dp))
+ NavRailItem("控制台", R.drawable.ic_dashboard, selectedNavItem == 0, onClick = { onNavItemSelect(0) })
+ NavRailItem("场景", R.drawable.ic_scene, selectedNavItem == 1, onClick = { onNavItemSelect(1) })
+ NavRailItem("自动化", R.drawable.ic_automation, selectedNavItem == 2, onClick = { onNavItemSelect(2) })
+ NavRailItem("统计", R.drawable.ic_statistics, selectedNavItem == 3, onClick = { onNavItemSelect(3) })
+ NavRailItem("安全", R.drawable.ic_security, selectedNavItem == 4, onClick = { onNavItemSelect(4) })
+ NavRailItem("设置", R.drawable.ic_settings, selectedNavItem == 5, onClick = { onNavItemSelect(5) })
+ }
+
+ // 底部:模式切换按钮(暂时注释)
+ /*
+ ThemeModeToggle(
+ isDarkMode = isDarkMode,
+ onToggle = {
+ if (isDarkMode) {
+ com.example.smarthome.data.BackgroundManager.setBackground(context, 0) // 丝绸白
+ } else {
+ com.example.smarthome.data.BackgroundManager.setBackground(context, 2) // 紫金绸
+ }
+ }
+ )
+ */
}
}
+/*
+@Composable
+fun ThemeModeToggle(isDarkMode: Boolean, onToggle: () -> Unit) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier
+ .clickable(
+ indication = null,
+ interactionSource = remember { MutableInteractionSource() }
+ ) { onToggle() }
+ .padding(vertical = 4.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(RoundedCornerShape(14.dp))
+ .background(
+ if (isDarkMode) {
+ Brush.linearGradient(
+ listOf(Color(0xFF2C2C54), Color(0xFF1A1A2E))
+ )
+ } else {
+ Brush.linearGradient(
+ listOf(Color(0xFFFFE082), Color(0xFFFFD54F))
+ )
+ }
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = if (isDarkMode) "🌙" else "☀️",
+ fontSize = 20.sp
+ )
+ }
+ }
+}
+*/
+
@Composable
fun NavRailItem(text: String, iconRes: Int, selected: Boolean, onClick: () -> Unit = {}) {
val scale by androidx.compose.animation.core.animateFloatAsState(
@@ -574,11 +639,7 @@ fun TopBar() {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
Text(text = "控制台", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = Color.White)
- Row(horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically) {
- // 天气信息卡片
- WeatherCard(weatherInfo = weatherInfo, isLoading = isLoading)
- GradientButton("+ 添加设备")
- }
+ WeatherCard(weatherInfo = weatherInfo, isLoading = isLoading)
}
}
@@ -1060,9 +1121,40 @@ fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间
var selectedMode by remember { mutableStateOf(0) } // 0:制冷 1:制热 2:除湿 3:送风
var fanSpeed by remember { mutableStateOf(1) } // 0:自动 1:低 2:中 3:高
- val modes = listOf("❄️" to "制冷", "🔥" to "制热", "💧" to "除湿", "🌀" to "送风")
+ // 模式emoji图标
+ val modeIcons = listOf(
+ "❄️" to "制冷",
+ "☀️" to "制热",
+ "💧" to "除湿",
+ "🌀" to "送风"
+ )
val fanSpeeds = listOf("自动", "低", "中", "高")
+ // 图标动画
+ val infiniteTransition = rememberInfiniteTransition(label = "ac_icon")
+
+ // 旋转动画(制冷、制热、送风)
+ val iconRotation by infiniteTransition.animateFloat(
+ initialValue = 0f,
+ targetValue = 360f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(durationMillis = 3000, easing = LinearEasing),
+ repeatMode = RepeatMode.Restart
+ ),
+ label = "icon_rotation"
+ )
+
+ // 水滴下落动画(除湿)
+ val dropOffset by infiniteTransition.animateFloat(
+ initialValue = -2f,
+ targetValue = 2f,
+ animationSpec = infiniteRepeatable(
+ animation = tween(durationMillis = 800, easing = androidx.compose.animation.core.EaseInOut),
+ repeatMode = RepeatMode.Reverse
+ ),
+ label = "drop_offset"
+ )
+
// 根据模式决定主题色
val accentColor = when (selectedMode) {
0 -> Color(0xFF4FC3F7) // 制冷 - 蓝色
@@ -1071,6 +1163,9 @@ fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间
else -> Color(0xFFB0BEC5) // 送风 - 灰色
}
+ // 当前模式的emoji
+ val currentEmoji = modeIcons[selectedMode].first
+
Box(
modifier = modifier
.height(260.dp)
@@ -1096,7 +1191,7 @@ fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间
modifier = Modifier.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- // 空调图标
+ // 空调图标 - 根据模式应用不同动画
Box(
modifier = Modifier
.size(36.dp)
@@ -1104,7 +1199,17 @@ fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间
.background(accentColor.copy(alpha = if (isOn) 0.3f else 0.1f)),
contentAlignment = Alignment.Center
) {
- Text(text = "❄️", fontSize = 18.sp)
+ // 根据模式选择动画:除湿用上下移动,其他用旋转
+ val emojiModifier = when {
+ !isOn -> Modifier
+ selectedMode == 2 -> Modifier.offset(y = dropOffset.dp) // 除湿:上下移动
+ else -> Modifier.rotate(iconRotation) // 其他:旋转
+ }
+ Text(
+ text = currentEmoji,
+ fontSize = 18.sp,
+ modifier = emojiModifier
+ )
}
Column {
Text(text = "空调", fontWeight = FontWeight.SemiBold, color = Color.White, fontSize = 16.sp)
@@ -1177,22 +1282,28 @@ fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
- modes.forEachIndexed { index, (icon, name) ->
+ modeIcons.forEachIndexed { index, (emoji, name) ->
val isSelected = selectedMode == index
+ val modeColor = when (index) {
+ 0 -> Color(0xFF4FC3F7)
+ 1 -> Color(0xFFFF8A65)
+ 2 -> Color(0xFF81C784)
+ else -> Color(0xFFB0BEC5)
+ }
Box(
modifier = Modifier
.weight(1f)
.height(44.dp)
.clip(RoundedCornerShape(12.dp))
.background(
- if (isSelected && isOn) accentColor.copy(alpha = 0.4f)
+ if (isSelected && isOn) modeColor.copy(alpha = 0.4f)
else Color(0xFF2A2A3E).copy(alpha = 0.6f)
)
.clickable(enabled = isOn) { selectedMode = index },
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Text(text = icon, fontSize = 14.sp)
+ Text(text = emoji, fontSize = 14.sp)
Text(
text = name,
fontSize = 10.sp,
diff --git a/app/src/main/java/com/example/smarthome/ui/SettingsScreen.kt b/app/src/main/java/com/example/smarthome/ui/SettingsScreen.kt
index e65a45e..103f99d 100644
--- a/app/src/main/java/com/example/smarthome/ui/SettingsScreen.kt
+++ b/app/src/main/java/com/example/smarthome/ui/SettingsScreen.kt
@@ -1,5 +1,6 @@
package com.example.smarthome.ui
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
@@ -15,9 +16,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import com.example.smarthome.data.BackgroundManager
@Composable
fun SettingsScreen(onBack: () -> Unit) {
@@ -75,12 +80,25 @@ fun SettingsContentList() {
}}
item { SettingsSection(title = "显示设置") {
- var darkMode by remember { mutableStateOf(true) }
+ BackgroundSelector()
+
+ val context = LocalContext.current
+ val selectedBg by BackgroundManager.selectedBackground.collectAsState()
+ // 深色模式根据当前背景是否为暗色来判断
+ val isDarkMode = BackgroundManager.backgrounds.getOrNull(selectedBg)?.isDark ?: false
+
SettingsSwitchItem(
title = "深色模式",
subtitle = "使用深色主题",
- checked = darkMode,
- onCheckedChange = { darkMode = it }
+ checked = isDarkMode,
+ onCheckedChange = { dark ->
+ // 切换深色/浅色模式时自动更换背景
+ if (dark) {
+ BackgroundManager.setBackground(context, 2) // 紫金绸
+ } else {
+ BackgroundManager.setBackground(context, 0) // 丝绸白
+ }
+ }
)
SettingsItem(
@@ -355,3 +373,117 @@ fun SettingsContent() {
SettingsContentList()
}
}
+
+@Composable
+fun BackgroundSelector() {
+ val context = LocalContext.current
+ val selectedBackground by BackgroundManager.selectedBackground.collectAsState()
+
+ val interactionSources = remember {
+ BackgroundManager.backgrounds.map { MutableInteractionSource() }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(16.dp))
+ .background(Color(0x33121212))
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Text(
+ text = "背景壁纸",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = Color.White
+ )
+ Text(
+ text = "选择您喜欢的背景图片",
+ fontSize = 13.sp,
+ color = Color(0xFF9AA0A6)
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ BackgroundManager.backgrounds.forEachIndexed { index, bg ->
+ val isSelected = selectedBackground == bg.id
+
+ Column(
+ modifier = Modifier.weight(1f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(6.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp)
+ .clip(RoundedCornerShape(10.dp))
+ .then(
+ if (isSelected) {
+ Modifier.border(
+ width = 2.dp,
+ brush = Brush.linearGradient(
+ listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
+ ),
+ shape = RoundedCornerShape(10.dp)
+ )
+ } else Modifier
+ )
+ .clickable(
+ indication = null,
+ interactionSource = interactionSources[index]
+ ) {
+ BackgroundManager.setBackground(context, bg.id)
+ }
+ ) {
+ Image(
+ painter = painterResource(id = bg.thumbId),
+ contentDescription = bg.name,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize()
+ )
+
+ if (isSelected) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Black.copy(alpha = 0.3f)),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(20.dp)
+ .clip(RoundedCornerShape(10.dp))
+ .background(
+ Brush.linearGradient(
+ listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
+ )
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "✓",
+ color = Color.White,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ }
+
+ Text(
+ text = bg.name,
+ fontSize = 11.sp,
+ fontWeight = if (isSelected) FontWeight.Medium else FontWeight.Normal,
+ color = if (isSelected) Color(0xFFA9F0FF) else Color(0xFF9AA0A6)
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/ic_automation.xml b/app/src/main/res/drawable/ic_automation.xml
index 497ab8e..8ce4ed6 100644
--- a/app/src/main/res/drawable/ic_automation.xml
+++ b/app/src/main/res/drawable/ic_automation.xml
@@ -4,28 +4,8 @@
android:viewportWidth="24"
android:viewportHeight="24">
-
+
-
-
-
-
-
-
-
-
+ android:pathData="M13,2 L4,14 L11,14 L11,22 L20,10 L13,10 L13,2 Z"/>
diff --git a/app/src/main/res/drawable/ic_scene.xml b/app/src/main/res/drawable/ic_scene.xml
index d4c209f..53d7e8c 100644
--- a/app/src/main/res/drawable/ic_scene.xml
+++ b/app/src/main/res/drawable/ic_scene.xml
@@ -4,28 +4,24 @@
android:viewportWidth="24"
android:viewportHeight="24">
-
+
+
+ android:pathData="M4,4 L10,4 C10.55,4 11,4.45 11,5 L11,10 C11,10.55 10.55,11 10,11 L4,11 C3.45,11 3,10.55 3,10 L3,5 C3,4.45 3.45,4 4,4 Z"/>
+
+ android:pathData="M14,4 L20,4 C20.55,4 21,4.45 21,5 L21,10 C21,10.55 20.55,11 20,11 L14,11 C13.45,11 13,10.55 13,10 L13,5 C13,4.45 13.45,4 14,4 Z"/>
+
+ android:pathData="M4,13 L10,13 C10.55,13 11,13.45 11,14 L11,19 C11,19.55 10.55,20 10,20 L4,20 C3.45,20 3,19.55 3,19 L3,14 C3,13.45 3.45,13 4,13 Z"/>
-
+
-
-
+ android:pathData="M14,13 L20,13 C20.55,13 21,13.45 21,14 L21,19 C21,19.55 20.55,20 20,20 L14,20 C13.45,20 13,19.55 13,19 L13,14 C13,13.45 13.45,13 14,13 Z"/>
diff --git a/app/src/main/res/drawable/ic_security.xml b/app/src/main/res/drawable/ic_security.xml
index 6ada778..bdfaf4d 100644
--- a/app/src/main/res/drawable/ic_security.xml
+++ b/app/src/main/res/drawable/ic_security.xml
@@ -1,3 +1,11 @@
-
-
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_statistics.xml b/app/src/main/res/drawable/ic_statistics.xml
index b94951b..56851eb 100644
--- a/app/src/main/res/drawable/ic_statistics.xml
+++ b/app/src/main/res/drawable/ic_statistics.xml
@@ -4,29 +4,19 @@
android:viewportWidth="24"
android:viewportHeight="24">
-
+
+
+ android:pathData="M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z M12,20 C7.59,20 4,16.41 4,12 C4,7.59 7.59,4 12,4 C16.41,4 20,7.59 20,12 C20,16.41 16.41,20 12,20 Z"/>
+
+ android:pathData="M12,12 L12,4 C16.41,4 20,7.59 20,12 L12,12 Z"/>
+
-
-
-
-
-
-
+ android:pathData="M12,12 L20,12 C20,14.5 19,16.8 17.3,18.3 L12,12 Z"/>