修改菜单边距,空调和能耗面板样式修改

This commit is contained in:
zzh 2025-11-27 13:36:47 +08:00
parent 9945bee5d2
commit f1bf8fd581
28 changed files with 1976 additions and 206 deletions

View File

@ -0,0 +1,329 @@
package com.example.smarthome.data
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Geocoder
import android.util.Log
import androidx.core.content.ContextCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import java.util.Locale
import java.util.concurrent.TimeUnit
data class WeatherInfo(
val temperature: String,
val weather: String,
val humidity: String,
val airQuality: String,
val aqi: Int,
val city: String
)
object WeatherService {
private const val TAG = "WeatherService"
private val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build()
/**
* 获取天气信息
* 优先使用GPS定位失败则用IP定位
*/
suspend fun getWeather(context: Context): WeatherInfo {
return withContext(Dispatchers.IO) {
// 1. 优先尝试GPS定位
val gpsLocation = getGpsLocation(context)
if (gpsLocation != null) {
val (lat, lon, city) = gpsLocation
Log.d(TAG, "GPS location: $city ($lat, $lon)")
// 用GPS坐标获取天气
val weatherResult = getWeatherByLocation(lat, lon, city)
if (weatherResult != null) {
return@withContext weatherResult
}
}
// 2. GPS失败尝试wttr.inIP定位
Log.w(TAG, "GPS failed, trying wttr.in...")
val wttrResult = tryWttrIn()
if (wttrResult != null) {
return@withContext wttrResult
}
// 3. wttr.in失败尝试备用方案
Log.w(TAG, "wttr.in failed, trying backup...")
val backupResult = tryBackupApi()
if (backupResult != null) {
return@withContext backupResult
}
// 都失败了,返回模拟数据
Log.e(TAG, "All weather APIs failed")
getSimulatedWeather()
}
}
// 获取GPS位置
@Suppress("DEPRECATION")
private fun getGpsLocation(context: Context): Triple<Double, Double, String>? {
// 检查权限
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "No location permission")
return null
}
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as android.location.LocationManager
// 尝试获取缓存的位置
val providers = listOf(
android.location.LocationManager.GPS_PROVIDER,
android.location.LocationManager.NETWORK_PROVIDER,
android.location.LocationManager.PASSIVE_PROVIDER
)
for (provider in providers) {
try {
val location = locationManager.getLastKnownLocation(provider)
if (location != null) {
val lat = location.latitude
val lon = location.longitude
val city = getCityFromLocation(context, lat, lon)
Log.d(TAG, "Got location from $provider: $city ($lat, $lon)")
return Triple(lat, lon, city)
}
} catch (e: Exception) {
Log.w(TAG, "Failed to get location from $provider: ${e.message}")
}
}
Log.w(TAG, "No cached GPS location available")
return null
}
// 通过经纬度获取城市名
@Suppress("DEPRECATION")
private fun getCityFromLocation(context: Context, lat: Double, lon: Double): String {
return try {
val geocoder = Geocoder(context, Locale.CHINESE)
val addresses = geocoder.getFromLocation(lat, lon, 1)
if (!addresses.isNullOrEmpty()) {
addresses[0].locality ?: addresses[0].subAdminArea ?: addresses[0].adminArea ?: "未知"
} else {
"未知"
}
} catch (e: Exception) {
Log.e(TAG, "Geocoder failed", e)
"未知"
}
}
// 通过经纬度获取天气
private fun getWeatherByLocation(lat: Double, lon: Double, city: String): WeatherInfo? {
return try {
// 使用7timer获取天气
val weatherUrl = "https://www.7timer.info/bin/civil.php?lon=$lon&lat=$lat&ac=0&unit=metric&output=json"
val weatherRequest = Request.Builder().url(weatherUrl).build()
val weatherResponse = client.newCall(weatherRequest).execute()
val weatherBody = weatherResponse.body?.string() ?: "{}"
if (!weatherResponse.isSuccessful) {
Log.e(TAG, "7timer failed: ${weatherResponse.code}")
return null
}
val weatherJson = JSONObject(weatherBody)
val dataseries = weatherJson.optJSONArray("dataseries")?.optJSONObject(0)
if (dataseries == null) {
Log.w(TAG, "7timer: No data")
return null
}
val temp = dataseries.optInt("temp2m", 0).toString()
val humidity = dataseries.optInt("rh2m", 0).toString()
val weatherCode = dataseries.optString("weather", "")
val weather = convertWeatherCode(weatherCode)
Log.d(TAG, "GPS weather success: $temp°C, $weather, $city")
WeatherInfo(temp, weather, humidity, "", 50, city)
} catch (e: Exception) {
Log.e(TAG, "getWeatherByLocation exception", e)
null
}
}
// wttr.in API
private fun tryWttrIn(): WeatherInfo? {
return try {
val url = "https://wttr.in/?format=j1&lang=zh"
val request = Request.Builder()
.url(url)
.header("User-Agent", "SmartHome/1.0")
.build()
val response = client.newCall(request).execute()
val body = response.body?.string() ?: "{}"
Log.d(TAG, "wttr.in response code: ${response.code}")
if (!response.isSuccessful) {
Log.e(TAG, "wttr.in request failed: ${response.code}")
return null
}
val json = JSONObject(body)
val currentCondition = json.optJSONArray("current_condition")?.optJSONObject(0)
val nearestArea = json.optJSONArray("nearest_area")?.optJSONObject(0)
if (currentCondition == null) {
Log.w(TAG, "wttr.in: No current condition data")
return null
}
val areaName = nearestArea?.optJSONArray("areaName")?.optJSONObject(0)?.optString("value", "")
val region = nearestArea?.optJSONArray("region")?.optJSONObject(0)?.optString("value", "")
val cityName = when {
!areaName.isNullOrBlank() && areaName != "Unknown" -> convertToChineseCityName(areaName)
!region.isNullOrBlank() && region != "Unknown" -> convertToChineseCityName(region)
else -> "未知"
}
val weatherDescArray = currentCondition.optJSONArray("lang_zh")
val weatherDesc = weatherDescArray?.optJSONObject(0)?.optString("value", "未知") ?: "未知"
val temp = currentCondition.optString("temp_C", "--")
val humidity = currentCondition.optString("humidity", "--")
Log.d(TAG, "wttr.in success: $temp°C, $weatherDesc, $cityName")
WeatherInfo(temp, weatherDesc, humidity, "", 50, cityName)
} catch (e: Exception) {
Log.e(TAG, "wttr.in exception", e)
null
}
}
// 备用API - 使用ip-api获取位置 + 7timer获取天气
private fun tryBackupApi(): WeatherInfo? {
return try {
// 1. 获取IP位置
val ipUrl = "http://ip-api.com/json/?lang=zh-CN"
val ipRequest = Request.Builder().url(ipUrl).build()
val ipResponse = client.newCall(ipRequest).execute()
val ipBody = ipResponse.body?.string() ?: "{}"
Log.d(TAG, "ip-api response: $ipBody")
val ipJson = JSONObject(ipBody)
val city = ipJson.optString("city", "未知")
val lat = ipJson.optDouble("lat", 0.0)
val lon = ipJson.optDouble("lon", 0.0)
if (lat == 0.0 && lon == 0.0) {
Log.w(TAG, "ip-api: Invalid location")
return null
}
Log.d(TAG, "Location: $city ($lat, $lon)")
// 2. 获取天气 - 使用7timer免费无需注册
val weatherUrl = "https://www.7timer.info/bin/civil.php?lon=$lon&lat=$lat&ac=0&unit=metric&output=json"
val weatherRequest = Request.Builder().url(weatherUrl).build()
val weatherResponse = client.newCall(weatherRequest).execute()
val weatherBody = weatherResponse.body?.string() ?: "{}"
Log.d(TAG, "7timer response code: ${weatherResponse.code}")
if (!weatherResponse.isSuccessful) {
Log.e(TAG, "7timer failed: ${weatherResponse.code}")
return null
}
val weatherJson = JSONObject(weatherBody)
val dataseries = weatherJson.optJSONArray("dataseries")?.optJSONObject(0)
if (dataseries == null) {
Log.w(TAG, "7timer: No data")
return null
}
val temp = dataseries.optInt("temp2m", 0).toString()
val humidity = dataseries.optInt("rh2m", 0).toString()
val weatherCode = dataseries.optString("weather", "")
val weather = convertWeatherCode(weatherCode)
Log.d(TAG, "7timer success: $temp°C, $weather, $city")
WeatherInfo(temp, weather, humidity, "", 50, city)
} catch (e: Exception) {
Log.e(TAG, "Backup API exception", e)
null
}
}
// 7timer天气代码转中文
private fun convertWeatherCode(code: String): String {
return when {
code.contains("clear") -> ""
code.contains("pcloudy") -> "多云"
code.contains("mcloudy") -> "多云"
code.contains("cloudy") -> ""
code.contains("humid") -> "潮湿"
code.contains("lightrain") -> "小雨"
code.contains("rain") -> ""
code.contains("oshower") -> "阵雨"
code.contains("ishower") -> "阵雨"
code.contains("lightsnow") -> "小雪"
code.contains("snow") -> ""
code.contains("ts") -> "雷暴"
else -> ""
}
}
// 拼音城市名转中文(常用城市)
private fun convertToChineseCityName(pinyin: String): String {
val cityMap = mapOf(
"Beijing" to "北京",
"Shanghai" to "上海",
"Guangzhou" to "广州",
"Shenzhen" to "深圳",
"Hangzhou" to "杭州",
"Nanjing" to "南京",
"Wuxi" to "无锡",
"Suzhou" to "苏州",
"Chengdu" to "成都",
"Chongqing" to "重庆",
"Wuhan" to "武汉",
"Xi'an" to "西安",
"Tianjin" to "天津",
"Qingdao" to "青岛",
"Dalian" to "大连",
"Xiamen" to "厦门",
"Ningbo" to "宁波",
"Changsha" to "长沙",
"Jiangsu" to "江苏",
"Zhejiang" to "浙江",
"Guangdong" to "广东",
"Sichuan" to "四川"
)
return cityMap[pinyin] ?: pinyin
}
// 模拟天气数据当API不可用时使用
fun getSimulatedWeather(): WeatherInfo {
return WeatherInfo(
temperature = "24",
weather = "",
humidity = "45",
airQuality = "",
aqi = 50,
city = "未知"
)
}
}

View File

@ -0,0 +1,398 @@
package com.example.smarthome.ui
import android.content.Context
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
data class Automation(
val id: String = java.util.UUID.randomUUID().toString(),
val name: String,
val trigger: String,
val action: String,
var isEnabled: Boolean = true
)
@Composable
fun AutomationScreen() {
val context = LocalContext.current
val prefs = remember { context.getSharedPreferences("automations", Context.MODE_PRIVATE) }
val gson = remember { Gson() }
// 加载保存的自动化列表
val savedAutomations = remember {
val json = prefs.getString("automation_list", null)
if (json != null) {
val type = object : TypeToken<List<Automation>>() {}.type
gson.fromJson<List<Automation>>(json, type)
} else {
listOf(
Automation(name = "早安自动化", trigger = "每天 7:00", action = "开启卧室灯光,播放音乐", isEnabled = true),
Automation(name = "离家自动化", trigger = "检测到离家", action = "关闭所有灯光和空调,开启安防", isEnabled = true),
Automation(name = "回家自动化", trigger = "检测到回家", action = "开启客厅灯光和空调", isEnabled = true),
Automation(name = "晚安自动化", trigger = "每天 23:00", action = "关闭所有灯光,空调调至睡眠模式", isEnabled = false),
Automation(name = "温度控制", trigger = "温度 > 28°C", action = "自动开启空调", isEnabled = true),
Automation(name = "安全警报", trigger = "检测到异常", action = "发送通知并开启警报", isEnabled = true)
)
}
}
var automations by remember { mutableStateOf(savedAutomations) }
var showAddDialog by remember { mutableStateOf(false) }
// 保存到SharedPreferences
fun saveAutomations() {
val json = gson.toJson(automations)
prefs.edit().putString("automation_list", json).apply()
}
fun toggleAutomation(automation: Automation) {
automations = automations.map {
if (it.id == automation.id) it.copy(isEnabled = !it.isEnabled) else it
}
saveAutomations()
}
fun addAutomation(name: String, trigger: String, action: String) {
automations = automations + Automation(name = name, trigger = trigger, action = action)
saveAutomations()
}
fun deleteAutomation(automation: Automation) {
automations = automations.filter { it.id != automation.id }
saveAutomations()
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = "自动化",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Color.White
)
Text(
text = "${automations.count { it.isEnabled }} 个自动化运行中",
fontSize = 14.sp,
color = Color(0xFF9AA0A6),
modifier = Modifier.padding(top = 4.dp)
)
}
Box(
modifier = Modifier
.height(40.dp)
.clip(RoundedCornerShape(20.dp))
.background(
Brush.linearGradient(
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
)
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { showAddDialog = true }
.padding(horizontal = 20.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "+ 新建",
color = Color.Black,
fontWeight = FontWeight.Medium,
fontSize = 14.sp
)
}
}
Spacer(modifier = Modifier.height(24.dp))
LazyColumn(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(automations) { automation ->
AutomationCard(
automation = automation,
onToggle = { toggleAutomation(automation) },
onDelete = { deleteAutomation(automation) }
)
}
}
}
if (showAddDialog) {
AddAutomationDialog(
onDismiss = { showAddDialog = false },
onConfirm = { name, trigger, action ->
addAutomation(name, trigger, action)
showAddDialog = false
}
)
}
}
@Composable
fun AutomationCard(automation: Automation, onToggle: () -> Unit, onDelete: () -> Unit) {
var showDeleteConfirm by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(20.dp))
.background(Color(0x50121212))
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = automation.name,
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White
)
Spacer(modifier = Modifier.height(8.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color(0x33A9F0FF))
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Text(
text = automation.trigger,
fontSize = 11.sp,
color = Color(0xFFA9F0FF)
)
}
Text(text = "", color = Color(0xFF9AA0A6), fontSize = 12.sp)
Text(
text = automation.action,
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
)
}
}
Box(
modifier = Modifier
.size(52.dp, 30.dp)
.clip(RoundedCornerShape(15.dp))
.background(
if (automation.isEnabled) {
Brush.linearGradient(
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
)
} else {
Brush.linearGradient(
listOf(Color(0x33222222), Color(0x33222222))
)
}
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onToggle() }
.padding(3.dp)
) {
Box(
modifier = Modifier
.size(24.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color.White)
.align(if (automation.isEnabled) Alignment.CenterEnd else Alignment.CenterStart)
)
}
}
}
}
@Composable
fun AddAutomationDialog(
onDismiss: () -> Unit,
onConfirm: (String, String, String) -> Unit
) {
var name by remember { mutableStateOf("") }
var trigger by remember { mutableStateOf("") }
var action 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(16.dp)
) {
Text(
text = "新建自动化",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
TextField(
value = name,
onValueChange = { name = it },
placeholder = { Text("自动化名称", color = Color(0xFF9AA0A6)) },
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color(0x28FFFFFF),
unfocusedContainerColor = Color(0x28FFFFFF),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color(0xFFA9F0FF),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(12.dp),
singleLine = true
)
TextField(
value = trigger,
onValueChange = { trigger = it },
placeholder = { Text("触发条件(如:每天 7:00", color = Color(0xFF9AA0A6)) },
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color(0x28FFFFFF),
unfocusedContainerColor = Color(0x28FFFFFF),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color(0xFFA9F0FF),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(12.dp),
singleLine = true
)
TextField(
value = action,
onValueChange = { action = it },
placeholder = { Text("执行动作", color = Color(0xFF9AA0A6)) },
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color(0x28FFFFFF),
unfocusedContainerColor = Color(0x28FFFFFF),
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
cursorColor = Color(0xFFA9F0FF),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(12.dp),
maxLines = 2
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.height(48.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0x28FFFFFF))
.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 = name.isNotBlank() && trigger.isNotBlank() && action.isNotBlank()
) {
if (name.isNotBlank() && trigger.isNotBlank() && action.isNotBlank()) {
onConfirm(name.trim(), trigger.trim(), action.trim())
}
}
.then(
if (name.isNotBlank() && trigger.isNotBlank() && action.isNotBlank()) {
Modifier
} else {
Modifier.alpha(0.5f)
}
),
contentAlignment = Alignment.Center
) {
Text(
text = "确定",
color = Color.Black,
fontWeight = FontWeight.Bold
)
}
}
}
}
}
}

View File

@ -60,15 +60,18 @@ fun MainScaffold(
onNavItemSelect = onNavItemSelect
)
// 根据选中的导航项显示不同的内容
when (selectedNavItem) {
0 -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom)
1 -> SceneScreen()
2 -> AutomationScreen()
3 -> StatisticsScreen()
4 -> SecurityScreen()
5 -> SettingsContent()
else -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom)
// 内容区域 - 减少左侧间距
Box(modifier = Modifier.fillMaxSize()) {
// 根据选中的导航项显示不同的内容
when (selectedNavItem) {
0 -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom)
1 -> SceneScreen()
2 -> AutomationScreen()
3 -> StatisticsScreen()
4 -> SecurityScreen()
5 -> SettingsContent()
else -> DashboardContent(selectedRoom, onRoomSelect, rooms, onAddRoom, onDeleteRoom)
}
}
}
}
@ -95,7 +98,7 @@ fun DashboardContent(
}
}
) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Column(modifier = Modifier.fillMaxSize().padding(start = 0.dp, end = 16.dp, top = 16.dp, bottom = 16.dp)) {
TopBar()
RoomTabs(
selectedRoom = selectedRoom,
@ -486,7 +489,7 @@ fun NavItem(title: String, selected: Boolean) {
@Composable
fun SideNavRail(selectedNavItem: Int = 0, onNavItemSelect: (Int) -> Unit = {}) {
Column(modifier = Modifier.width(120.dp).fillMaxHeight().padding(12.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
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) })
@ -831,29 +834,23 @@ fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间")
val activeDevices = remember(roomName) { (3 + (Math.random() * 8).toInt()) }
val trend = remember(roomName) { if (Math.random() > 0.5) "" else "" }
val trendPercent = remember(roomName) { (5 + (Math.random() * 15).toInt()) }
val costToday = remember(roomName) { String.format("%.1f", 2 + Math.random() * 8) }
// 主题色
val accentColor = Color(0xFF00D1FF)
Box(
modifier = modifier
.height(220.dp)
.height(260.dp)
.clip(RoundedCornerShape(24.dp))
.background(
Brush.linearGradient(
Brush.verticalGradient(
colors = listOf(
Color(0x60121212),
Color(0x50121212)
accentColor.copy(alpha = 0.15f),
Color(0xFF1A1A2E).copy(alpha = 0.9f)
)
)
)
.border(
width = 1.dp,
brush = Brush.linearGradient(
colors = listOf(
Color(0x22FFFFFF),
Color(0x0AFFFFFF)
)
),
shape = RoundedCornerShape(24.dp)
)
.padding(16.dp)
) {
Column(
@ -866,140 +863,153 @@ fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间")
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = "能耗统计",
fontWeight = FontWeight.Bold,
color = Color.White,
fontSize = 16.sp
)
Text(
text = roomName,
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
// 图标
Box(
modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(10.dp))
.background(accentColor.copy(alpha = 0.2f)),
contentAlignment = Alignment.Center
) {
Text(text = "", fontSize = 18.sp)
}
Column {
Text(
text = "能耗统计",
fontWeight = FontWeight.SemiBold,
color = Color.White,
fontSize = 16.sp
)
Text(
text = roomName,
fontSize = 11.sp,
color = Color(0xFF9AA0A6)
)
}
}
// 趋势指示器
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.clip(RoundedCornerShape(8.dp))
.background(
if (trend == "") Color(0x33FF5252) else Color(0x3300E676)
)
.padding(horizontal = 10.dp, vertical = 6.dp)
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = trend,
color = if (trend == "") Color(0xFFFF5252) else Color(0xFF00E676),
fontSize = 14.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "${trendPercent}%",
color = if (trend == "") Color(0xFFFF5252) else Color(0xFF00E676),
fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
}
Text(
text = "$trend${trendPercent}%",
color = if (trend == "") Color(0xFFFF5252) else Color(0xFF00E676),
fontSize = 12.sp,
fontWeight = FontWeight.Bold
)
}
}
// 数据指标区域
// 主数据显示
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Bottom
) {
// 总消耗
Column(
modifier = Modifier.weight(1f)
) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
// 今日用电
Column {
Row(verticalAlignment = Alignment.Top) {
Text(
text = totalPower,
color = Color(0xFFA9F0FF),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
fontSize = 42.sp,
color = Color.White,
fontWeight = FontWeight.Light
)
Text(
text = "kWh",
color = Color(0xFF9AA0A6),
fontSize = 12.sp,
modifier = Modifier.padding(bottom = 2.dp)
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.6f),
modifier = Modifier.padding(top = 10.dp, start = 4.dp)
)
}
Text(
text = "总消耗",
color = Color(0xFF9AA0A6),
fontSize = 11.sp
text = "今日用电",
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
)
}
// 运行时长
Column(
modifier = Modifier.weight(1f)
) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "$totalHours",
color = Color(0xFFB89CFF),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "小时",
color = Color(0xFF9AA0A6),
fontSize = 12.sp,
modifier = Modifier.padding(bottom = 2.dp)
)
}
// 预计费用
Column(horizontalAlignment = Alignment.End) {
Text(
text = "运行时长",
color = Color(0xFF9AA0A6),
fontSize = 11.sp
text = "¥$costToday",
fontSize = 20.sp,
color = accentColor,
fontWeight = FontWeight.Bold
)
}
// 活跃设备
Column(
modifier = Modifier.weight(1f)
) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "$activeDevices",
color = Color(0xFF00E676),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Text(
text = "",
color = Color(0xFF9AA0A6),
fontSize = 12.sp,
modifier = Modifier.padding(bottom = 2.dp)
)
}
Text(
text = "活跃设备",
color = Color(0xFF9AA0A6),
fontSize = 11.sp
text = "预计费用",
fontSize = 11.sp,
color = Color(0xFF9AA0A6)
)
}
}
Spacer(modifier = Modifier.height(4.dp))
// 详细数据行
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF2A2A3E).copy(alpha = 0.5f))
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
// 运行时长
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "$totalHours",
color = Color(0xFFB89CFF),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Text(text = "小时", fontSize = 10.sp, color = Color(0xFF9AA0A6))
}
// 分隔线
Box(
modifier = Modifier
.width(1.dp)
.height(30.dp)
.background(Color(0xFF3A3A4E))
)
// 活跃设备
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "$activeDevices",
color = Color(0xFF00E676),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Text(text = "设备", fontSize = 10.sp, color = Color(0xFF9AA0A6))
}
// 分隔线
Box(
modifier = Modifier
.width(1.dp)
.height(30.dp)
.background(Color(0xFF3A3A4E))
)
// 峰值功率
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = "${(800 + (Math.random() * 400).toInt())}",
color = Color(0xFFFFB74D),
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
Text(text = "W峰值", fontSize = 10.sp, color = Color(0xFF9AA0A6))
}
}
// 图表区域
Box(
@ -1011,59 +1021,32 @@ fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间")
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
horizontalArrangement = Arrangement.spacedBy(6.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.Bottom
) {
values.forEachIndexed { index, v ->
val normalizedHeight = (v / maxValue) * 60
val normalizedHeight = (v / maxValue) * 50
val isHighlight = index == values.size - 1
Box(
modifier = Modifier
.weight(1f)
.height(normalizedHeight.dp)
.clip(RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp))
.clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))
.background(
if (isHighlight) {
Brush.verticalGradient(
colors = listOf(
Color(0xFFA9F0FF),
Color(0xFF00D1FF)
)
colors = listOf(accentColor, accentColor.copy(alpha = 0.5f))
)
} else {
Brush.verticalGradient(
colors = listOf(
Color(0xFF3A3A3A),
Color(0xFF2A2A2A)
)
colors = listOf(Color(0xFF4A4A5A), Color(0xFF3A3A4A))
)
}
)
)
}
}
// 时间标签
Row(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.offset(y = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "12h前",
fontSize = 10.sp,
color = Color(0xFF666666)
)
Text(
text = "现在",
fontSize = 10.sp,
color = Color(0xFFA9F0FF),
fontWeight = FontWeight.Medium
)
}
}
}
}
@ -1073,76 +1056,180 @@ fun UsageStatusChart(modifier: Modifier = Modifier, roomName: String = "房间")
fun AirConditionerCard(modifier: Modifier = Modifier, roomName: String = "房间") {
var temp by remember { mutableStateOf(24f) }
var isOn by remember { mutableStateOf(true) }
var selectedMode by remember { mutableStateOf(0) } // 0:制冷 1:制热 2:除湿 3:送风
var fanSpeed by remember { mutableStateOf(1) } // 0:自动 1:低 2:中 3:高
Box(modifier = modifier.height(220.dp).clip(RoundedCornerShape(24.dp)).background(Color(0x50121212)).padding(16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Row(horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Column {
Text(text = "空调", fontWeight = FontWeight.SemiBold, color = Color.White, fontSize = 18.sp)
Text(text = roomName, fontSize = 12.sp, color = Color(0xFF9AA0A6))
val modes = listOf("❄️" to "制冷", "🔥" to "制热", "💧" to "除湿", "🌀" to "送风")
val fanSpeeds = listOf("自动", "", "", "")
// 根据模式决定主题色
val accentColor = when (selectedMode) {
0 -> Color(0xFF4FC3F7) // 制冷 - 蓝色
1 -> Color(0xFFFF8A65) // 制热 - 橙色
2 -> Color(0xFF81C784) // 除湿 - 绿色
else -> Color(0xFFB0BEC5) // 送风 - 灰色
}
Box(
modifier = modifier
.height(260.dp)
.clip(RoundedCornerShape(24.dp))
.background(
Brush.verticalGradient(
colors = if (isOn) listOf(
accentColor.copy(alpha = 0.3f),
Color(0xFF1A1A2E).copy(alpha = 0.9f)
) else listOf(
Color(0xFF2A2A3E).copy(alpha = 0.6f),
Color(0xFF1A1A2E).copy(alpha = 0.9f)
)
)
)
.padding(16.dp)
) {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
// 顶部:标题和开关
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
// 空调图标
Box(
modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(10.dp))
.background(accentColor.copy(alpha = if (isOn) 0.3f else 0.1f)),
contentAlignment = Alignment.Center
) {
Text(text = "❄️", fontSize = 18.sp)
}
Column {
Text(text = "空调", fontWeight = FontWeight.SemiBold, color = Color.White, fontSize = 16.sp)
Text(text = roomName, fontSize = 11.sp, color = Color(0xFF9AA0A6))
}
}
Switch(checked = isOn, onCheckedChange = { isOn = it })
Switch(
checked = isOn,
onCheckedChange = { isOn = it },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.White,
checkedTrackColor = accentColor,
uncheckedThumbColor = Color.Gray,
uncheckedTrackColor = Color(0xFF3A3A4E)
)
)
}
// 温度显示和调节
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${temp.toInt()}°C",
style = MaterialTheme.typography.headlineLarge,
color = Color.White,
fontWeight = FontWeight.Bold
)
// 大温度显示
Row(verticalAlignment = Alignment.Top) {
Text(
text = "${temp.toInt()}",
fontSize = 52.sp,
color = if (isOn) Color.White else Color.Gray,
fontWeight = FontWeight.Light
)
Text(
text = "°C",
fontSize = 20.sp,
color = if (isOn) Color.White.copy(alpha = 0.7f) else Color.Gray,
modifier = Modifier.padding(top = 8.dp)
)
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
// 降温按钮
Box(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0x55FFFFFF))
.clickable(enabled = isOn && temp > 16f) {
if (temp > 16f) temp -= 1f
},
contentAlignment = Alignment.Center
) {
Text(text = "", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Bold)
}
// 温度调节按钮
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
// 升温按钮
Box(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0x55FFFFFF))
.clickable(enabled = isOn && temp < 32f) {
if (temp < 32f) temp += 1f
},
.size(36.dp)
.clip(RoundedCornerShape(10.dp))
.background(if (isOn) accentColor.copy(alpha = 0.3f) else Color(0xFF3A3A4E))
.clickable(enabled = isOn && temp < 32f) { temp += 1f },
contentAlignment = Alignment.Center
) {
Text(text = "+", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Bold)
Text(text = "", color = if (isOn) Color.White else Color.Gray, fontSize = 14.sp)
}
// 降温按钮
Box(
modifier = Modifier
.size(36.dp)
.clip(RoundedCornerShape(10.dp))
.background(if (isOn) accentColor.copy(alpha = 0.3f) else Color(0xFF3A3A4E))
.clickable(enabled = isOn && temp > 16f) { temp -= 1f },
contentAlignment = Alignment.Center
) {
Text(text = "", color = if (isOn) Color.White else Color.Gray, fontSize = 14.sp)
}
}
}
// 自定义滑动条
CustomSlider(
value = temp,
onValueChange = { temp = it },
valueRange = 16f..32f,
enabled = isOn
)
// 温度标记
// 模式选择
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(text = "16°C", fontSize = 12.sp, color = Color(0xFF9AA0A6))
Text(text = "24°C", fontSize = 12.sp, color = Color(0xFF9AA0A6))
Text(text = "32°C", fontSize = 12.sp, color = Color(0xFF9AA0A6))
modes.forEachIndexed { index, (icon, name) ->
val isSelected = selectedMode == index
Box(
modifier = Modifier
.weight(1f)
.height(44.dp)
.clip(RoundedCornerShape(12.dp))
.background(
if (isSelected && isOn) accentColor.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 = name,
fontSize = 10.sp,
color = if (isSelected && isOn) Color.White else Color(0xFF9AA0A6)
)
}
}
}
}
// 风速选择
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "风速", fontSize = 12.sp, color = Color(0xFF9AA0A6))
fanSpeeds.forEachIndexed { index, speed ->
val isSelected = fanSpeed == index
Box(
modifier = Modifier
.weight(1f)
.height(28.dp)
.clip(RoundedCornerShape(8.dp))
.background(
if (isSelected && isOn) accentColor.copy(alpha = 0.5f)
else Color(0xFF2A2A3E).copy(alpha = 0.4f)
)
.clickable(enabled = isOn) { fanSpeed = index },
contentAlignment = Alignment.Center
) {
Text(
text = speed,
fontSize = 11.sp,
color = if (isSelected && isOn) Color.White else Color(0xFF9AA0A6)
)
}
}
}
}
}

View File

@ -0,0 +1,178 @@
package com.example.smarthome.ui
import android.content.Context
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
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.material3.*
import androidx.compose.runtime.*
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.graphics.Brush
import androidx.compose.ui.graphics.Color
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.R
data class Scene(
val id: String,
val name: String,
val description: String,
val icon: Int,
val gradient: Brush,
var isActive: Boolean = false
)
@Composable
fun SceneScreen() {
val context = LocalContext.current
val prefs = remember { context.getSharedPreferences("scenes", Context.MODE_PRIVATE) }
val scenes = remember {
listOf(
Scene("home", "回家模式", "开启灯光、空调,关闭安防", R.drawable.ic_mode_home,
Brush.linearGradient(listOf(Color(0xFF00D1FF), Color(0xFF00C9A7)))),
Scene("away", "出门模式", "关闭所有设备,开启安防", R.drawable.ic_mode_away,
Brush.linearGradient(listOf(Color(0xFFFF8A65), Color(0xFFFF7043)))),
Scene("fun", "玩乐模式", "影音设备开启,灯光调暗", R.drawable.ic_mode_fun,
Brush.linearGradient(listOf(Color(0xFF6A3DFF), Color(0xFF3A0CA3)))),
Scene("sleep", "睡眠模式", "关闭灯光,空调调至舒适温度", R.drawable.ic_curtain,
Brush.linearGradient(listOf(Color(0xFF5E35B1), Color(0xFF311B92)))),
Scene("reading", "阅读模式", "主灯调亮,关闭电视", R.drawable.ic_light,
Brush.linearGradient(listOf(Color(0xFFFFD54F), Color(0xFFFF8F00)))),
Scene("movie", "观影模式", "灯光调暗,窗帘关闭", R.drawable.ic_media,
Brush.linearGradient(listOf(Color(0xFFE91E63), Color(0xFFC2185B))))
).map { scene ->
scene.copy(isActive = prefs.getBoolean(scene.id, false))
}
}
var sceneStates by remember { mutableStateOf(scenes.associate { it.id to it.isActive }) }
fun toggleScene(sceneId: String) {
val newState = !(sceneStates[sceneId] ?: false)
sceneStates = sceneStates.toMutableMap().apply { put(sceneId, newState) }
prefs.edit().putBoolean(sceneId, newState).apply()
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "场景模式",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "一键切换家居场景",
fontSize = 14.sp,
color = Color(0xFF9AA0A6),
modifier = Modifier.padding(bottom = 24.dp)
)
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
items(scenes) { scene ->
SceneCard(
scene = scene,
isActive = sceneStates[scene.id] ?: false,
onToggle = { toggleScene(scene.id) }
)
}
}
}
}
@Composable
fun SceneCard(scene: Scene, isActive: Boolean, onToggle: () -> Unit) {
Box(
modifier = Modifier
.height(180.dp)
.then(
if (isActive) {
Modifier
.shadow(12.dp, RoundedCornerShape(24.dp))
.clip(RoundedCornerShape(24.dp))
.background(scene.gradient)
} else {
Modifier
.clip(RoundedCornerShape(24.dp))
.background(Color(0x50121212))
}
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onToggle() }
.padding(20.dp)
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween
) {
Box(
modifier = Modifier
.size(56.dp)
.clip(RoundedCornerShape(16.dp))
.background(Color(0x33FFFFFF)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = scene.icon),
contentDescription = scene.name,
modifier = Modifier.size(32.dp),
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(Color.White)
)
}
Column {
Text(
text = scene.name,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = scene.description,
fontSize = 12.sp,
color = Color(0xCCFFFFFF),
lineHeight = 16.sp
)
}
}
if (isActive) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(24.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0x33FFFFFF)),
contentAlignment = Alignment.Center
) {
Text(text = "", color = Color.White, fontSize = 14.sp, fontWeight = FontWeight.Bold)
}
}
}
}

View File

@ -0,0 +1,284 @@
package com.example.smarthome.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.sp
import com.example.smarthome.R
data class SecurityEvent(
val title: String,
val time: String,
val type: String, // "info", "warning", "alert"
val description: String
)
@Composable
fun SecurityScreen() {
val context = LocalContext.current
val sharedPreferences = remember {
context.getSharedPreferences("smart_home_prefs", android.content.Context.MODE_PRIVATE)
}
// 从SharedPreferences加载安防状态
var isArmed by remember {
mutableStateOf(sharedPreferences.getBoolean("security_armed", false))
}
// 保存安防状态的函数
fun saveArmedState(armed: Boolean) {
sharedPreferences.edit().putBoolean("security_armed", armed).apply()
isArmed = armed
}
val events = remember {
listOf(
SecurityEvent("前门已锁定", "2分钟前", "info", "智能门锁自动上锁"),
SecurityEvent("检测到异常移动", "15分钟前", "warning", "客厅摄像头检测到移动"),
SecurityEvent("窗户已关闭", "1小时前", "info", "所有窗户传感器确认关闭"),
SecurityEvent("烟雾警报测试", "今天 10:30", "info", "烟雾探测器自检正常"),
SecurityEvent("门铃按下", "昨天 18:45", "info", "有访客按门铃")
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "安全中心",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = if (isArmed) "安防系统已启动" else "安防系统未启动",
fontSize = 14.sp,
color = if (isArmed) Color(0xFF00E676) else Color(0xFF9AA0A6),
modifier = Modifier.padding(bottom = 24.dp)
)
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item {
SecurityStatusCard(isArmed = isArmed, onToggle = { saveArmedState(it) })
}
item {
SecurityDevicesCard()
}
item {
Text(
text = "最近事件",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(vertical = 8.dp)
)
}
items(events) { event ->
SecurityEventCard(event)
}
}
}
}
@Composable
fun SecurityStatusCard(isArmed: Boolean, onToggle: (Boolean) -> Unit) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(24.dp))
.background(
if (isArmed) {
Brush.linearGradient(
listOf(Color(0xFF00E676), Color(0xFF00C853))
)
} else {
Brush.linearGradient(
listOf(Color(0x60121212), Color(0x50121212))
)
}
)
.padding(24.dp)
) {
Column {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = if (isArmed) "安防已启动" else "安防未启动",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = if (isArmed) Color.Black else Color.White
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = if (isArmed) "家庭安全受到保护" else "点击启动安防系统",
fontSize = 14.sp,
color = if (isArmed) Color(0xCC000000) else Color(0xFF9AA0A6)
)
}
Box(
modifier = Modifier
.size(64.dp)
.clip(RoundedCornerShape(16.dp))
.background(
if (isArmed) Color(0x33000000) else Color(0x33FFFFFF)
)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onToggle(!isArmed) },
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.ic_security),
contentDescription = "安防",
modifier = Modifier.size(32.dp),
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(
if (isArmed) Color.Black else Color.White
)
)
}
}
}
}
}
@Composable
fun SecurityDevicesCard() {
val devices = listOf(
"智能门锁" to true,
"门窗传感器" to true,
"烟雾探测器" to true,
"摄像头" to true,
"燃气传感器" to true
)
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(24.dp))
.background(Color(0x50121212))
.padding(20.dp)
) {
Column {
Text(
text = "安防设备",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 16.dp)
)
devices.forEach { (name, status) ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = name,
fontSize = 14.sp,
color = Color.White
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(8.dp)
.clip(RoundedCornerShape(4.dp))
.background(if (status) Color(0xFF00E676) else Color(0xFFFF5252))
)
Text(
text = if (status) "正常" else "异常",
fontSize = 12.sp,
color = if (status) Color(0xFF00E676) else Color(0xFFFF5252)
)
}
}
}
}
}
}
@Composable
fun SecurityEventCard(event: SecurityEvent) {
val backgroundColor = when (event.type) {
"alert" -> Color(0x33FF5252)
"warning" -> Color(0x33FFA726)
else -> Color(0x50121212)
}
val accentColor = when (event.type) {
"alert" -> Color(0xFFFF5252)
"warning" -> Color(0xFFFFA726)
else -> Color(0xFFA9F0FF)
}
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(backgroundColor)
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = event.title,
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = event.description,
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
)
}
Text(
text = event.time,
fontSize = 11.sp,
color = accentColor
)
}
}
}

View File

@ -0,0 +1,301 @@
package com.example.smarthome.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun StatisticsScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "能耗统计",
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "本月数据分析",
fontSize = 14.sp,
color = Color(0xFF9AA0A6),
modifier = Modifier.padding(bottom = 24.dp)
)
LazyColumn(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item { OverallStatsCard() }
item { RoomStatsCard() }
item { DeviceRankingCard() }
}
}
}
@Composable
fun OverallStatsCard() {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(24.dp))
.background(
Brush.linearGradient(
colors = listOf(
Color(0x60121212),
Color(0x50121212)
)
)
)
.border(
width = 1.dp,
brush = Brush.linearGradient(
colors = listOf(
Color(0x22FFFFFF),
Color(0x0AFFFFFF)
)
),
shape = RoundedCornerShape(24.dp)
)
.padding(20.dp)
) {
Column {
Text(
text = "本月总览",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 16.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
StatItem(
label = "总消耗",
value = "342.5",
unit = "kWh",
color = Color(0xFFA9F0FF),
modifier = Modifier.weight(1f)
)
StatItem(
label = "总费用",
value = "¥205",
unit = "",
color = Color(0xFFB89CFF),
modifier = Modifier.weight(1f)
)
StatItem(
label = "运行时长",
value = "720",
unit = "小时",
color = Color(0xFF00E676),
modifier = Modifier.weight(1f)
)
}
}
}
}
@Composable
fun StatItem(label: String, value: String, unit: String, color: Color, modifier: Modifier = Modifier) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = value,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = color
)
if (unit.isNotEmpty()) {
Text(
text = unit,
fontSize = 12.sp,
color = Color(0xFF9AA0A6),
modifier = Modifier.padding(bottom = 4.dp)
)
}
}
Spacer(modifier = Modifier.height(4.dp))
Text(
text = label,
fontSize = 12.sp,
color = Color(0xFF9AA0A6)
)
}
}
@Composable
fun RoomStatsCard() {
val rooms = listOf(
Triple("客厅", 85.2f, 0.35f),
Triple("卧室", 62.8f, 0.26f),
Triple("厨房", 48.5f, 0.20f),
Triple("影音室", 32.1f, 0.13f),
Triple("游戏房", 15.9f, 0.06f)
)
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(24.dp))
.background(Color(0x50121212))
.padding(20.dp)
) {
Column {
Text(
text = "房间能耗排名",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 16.dp)
)
rooms.forEach { (name, value, percentage) ->
RoomStatItem(name, value, percentage)
Spacer(modifier = Modifier.height(12.dp))
}
}
}
}
@Composable
fun RoomStatItem(name: String, value: Float, percentage: Float) {
Column {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = name,
fontSize = 14.sp,
color = Color.White
)
Text(
text = "${value} kWh",
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFFA9F0FF)
)
}
Spacer(modifier = Modifier.height(6.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.height(6.dp)
.clip(RoundedCornerShape(3.dp))
.background(Color(0x33FFFFFF))
) {
Box(
modifier = Modifier
.fillMaxWidth(percentage)
.fillMaxHeight()
.background(
Brush.horizontalGradient(
listOf(Color(0xFFA9F0FF), Color(0xFFB89CFF))
)
)
)
}
}
}
@Composable
fun DeviceRankingCard() {
val devices = listOf(
"空调" to "156.3 kWh",
"热水器" to "78.5 kWh",
"冰箱" to "45.2 kWh",
"洗衣机" to "32.8 kWh",
"电视" to "29.7 kWh"
)
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(24.dp))
.background(Color(0x50121212))
.padding(20.dp)
) {
Column {
Text(
text = "设备能耗排名",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.padding(bottom = 16.dp)
)
devices.forEachIndexed { index, (name, value) ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(32.dp)
.clip(RoundedCornerShape(8.dp))
.background(
when (index) {
0 -> Brush.linearGradient(listOf(Color(0xFFFFD700), Color(0xFFFFA500)))
1 -> Brush.linearGradient(listOf(Color(0xFFC0C0C0), Color(0xFF808080)))
2 -> Brush.linearGradient(listOf(Color(0xFFCD7F32), Color(0xFF8B4513)))
else -> Brush.linearGradient(listOf(Color(0x33FFFFFF), Color(0x33FFFFFF)))
}
),
contentAlignment = Alignment.Center
) {
Text(
text = "${index + 1}",
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
Text(
text = name,
fontSize = 14.sp,
color = Color.White
)
}
Text(
text = value,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF9AA0A6)
)
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- 自动化图标 - 齿轮和箭头 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2 L10.5,4.5 L7.5,4.5 L6,7 L3.5,8.5 L3.5,11.5 L6,13 L7.5,15.5 L10.5,15.5 L12,18 L13.5,15.5 L16.5,15.5 L18,13 L20.5,11.5 L20.5,8.5 L18,7 L16.5,4.5 L13.5,4.5 Z M12,8 C14.21,8 16,9.79 16,12 C16,14.21 14.21,16 12,16 C9.79,16 8,14.21 8,12 C8,9.79 9.79,8 12,8 Z"/>
<!-- 内圈 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,10 C10.9,10 10,10.9 10,12 C10,13.1 10.9,14 12,14 C13.1,14 14,13.1 14,12 C14,10.9 13.1,10 12,10 Z"/>
<!-- 循环箭头 -->
<path
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.5"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M19,12 C19,15.87 15.87,19 12,19 C8.13,19 5,15.87 5,12 M5,12 L7,10 M5,12 L7,14"/>
<path
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.5"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M5,12 C5,8.13 8.13,5 12,5 C15.87,5 19,8.13 19,12 M19,12 L17,10 M19,12 L17,14"/>
</vector>

View File

@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<!-- 锁的主体 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M32,20 L30,20 L30,16 C30,12.69 27.31,10 24,10 C20.69,10 18,12.69 18,16 L18,20 L16,20 C14.9,20 14,20.9 14,22 L14,36 C14,37.1 14.9,38 16,38 L32,38 C33.1,38 34,37.1 34,36 L34,22 C34,20.9 33.1,20 32,20 Z M24,32 C22.9,32 22,31.1 22,30 C22,28.9 22.9,28 24,28 C25.1,28 26,28.9 26,30 C26,31.1 25.1,32 24,32 Z M28,20 L20,20 L20,16 C20,13.79 21.79,12 24,12 C26.21,12 28,13.79 28,16 Z"/>
<!-- 锁孔高光 -->
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.4"
android:pathData="M24,28 C25.1,28 26,28.9 26,30 C26,31.1 25.1,32 24,32 C22.9,32 22,31.1 22,30 C22,28.9 22.9,28 24,28 Z"/>
<!-- 装饰性光晕 -->
<path
android:strokeColor="#FFFFFF"
android:strokeWidth="2"
android:strokeAlpha="0.3"
android:fillColor="#00000000"
android:pathData="M24,6 C13.51,6 5,14.51 5,25 C5,35.49 13.51,44 24,44 C34.49,44 43,35.49 43,25 C43,14.51 34.49,6 24,6 Z"/>
</vector>

View File

@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<!-- 游戏手柄完整主体 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M32,14 L16,14 C11.58,14 8,17.58 8,22 L8,30 C8,33 10,36 12,38 C13,37 14,35 14,33 L14,26 C14,28.21 15.79,30 18,30 L30,30 C32.21,30 34,28.21 34,26 L34,33 C34,35 35,37 36,38 C38,36 40,33 40,30 L40,22 C40,17.58 36.42,14 32,14 Z"/>
<!-- 左侧十字键 -->
<path
android:fillColor="#000000"
android:fillAlpha="0.3"
android:pathData="M18,17 L20,17 L20,19 L22,19 L22,21 L20,21 L20,23 L18,23 L18,21 L16,21 L16,19 L18,19 Z"/>
<!-- 右侧按钮 A -->
<path
android:fillColor="#000000"
android:fillAlpha="0.3"
android:pathData="M32,18 C32.83,18 33.5,18.67 33.5,19.5 C33.5,20.33 32.83,21 32,21 C31.17,21 30.5,20.33 30.5,19.5 C30.5,18.67 31.17,18 32,18 Z"/>
<!-- 右侧按钮 B -->
<path
android:fillColor="#000000"
android:fillAlpha="0.3"
android:pathData="M28,18 C28.83,18 29.5,18.67 29.5,19.5 C29.5,20.33 28.83,21 28,21 C27.17,21 26.5,20.33 26.5,19.5 C26.5,18.67 27.17,18 28,18 Z"/>
<!-- 装饰星星 -->
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.6"
android:pathData="M24,8 L25,11 L28,11 L25.5,13 L26.5,16 L24,14 L21.5,16 L22.5,13 L20,11 L23,11 Z"/>
</vector>

View File

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<!-- 房子主体 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24,8 L10,18 L10,38 C10,39.1 10.9,40 12,40 L20,40 L20,30 L28,30 L28,40 L36,40 C37.1,40 38,39.1 38,38 L38,18 Z"/>
<!-- 房顶 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24,4 L4,20 L8,20 L24,8 L40,20 L44,20 Z"/>
<!---->
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.3"
android:pathData="M20,30 L28,30 L28,40 L20,40 Z"/>
<!-- 窗户 -->
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.3"
android:pathData="M14,22 L18,22 L18,26 L14,26 Z"/>
<path
android:fillColor="#FFFFFF"
android:fillAlpha="0.3"
android:pathData="M30,22 L34,22 L34,26 L30,26 Z"/>
</vector>

View File

@ -0,0 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- 场景图标 - 魔法棒 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20,8 L18,8 L18,6 C18,5.45 17.55,5 17,5 C16.45,5 16,5.45 16,6 L16,8 L14,8 C13.45,8 13,8.45 13,9 C13,9.55 13.45,10 14,10 L16,10 L16,12 C16,12.55 16.45,13 17,13 C17.55,13 18,12.55 18,12 L18,10 L20,10 C20.55,10 21,9.55 21,9 C21,8.45 20.55,8 20,8 Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11,4 L10,4 L10,3 C10,2.45 9.55,2 9,2 C8.45,2 8,2.45 8,3 L8,4 L7,4 C6.45,4 6,4.45 6,5 C6,5.55 6.45,6 7,6 L8,6 L8,7 C8,7.55 8.45,8 9,8 C9.55,8 10,7.55 10,7 L10,6 L11,6 C11.55,6 12,5.55 12,5 C12,4.45 11.55,4 11,4 Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3.5,11 L3.5,10 L2.5,10 C2.22,10 2,9.78 2,9.5 C2,9.22 2.22,9 2.5,9 L3.5,9 L3.5,8 C3.5,7.72 3.72,7.5 4,7.5 C4.28,7.5 4.5,7.72 4.5,8 L4.5,9 L5.5,9 C5.78,9 6,9.22 6,9.5 C6,9.78 5.78,10 5.5,10 L4.5,10 L4.5,11 C4.5,11.28 4.28,11.5 4,11.5 C3.72,11.5 3.5,11.28 3.5,11 Z"/>
<!-- 魔法棒主体 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M15.5,14.5 L4.5,3.5 C4.11,3.11 3.48,3.11 3.09,3.5 L3.5,3.91 L14.5,14.91 L15.5,14.5 Z"/>
<path
android:strokeColor="#FFFFFFFF"
android:strokeWidth="2"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M15,15 L4,4"/>
</vector>

View File

@ -0,0 +1,32 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- 统计图标 - 柱状图 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M5,19 L5,9 C5,8.45 5.45,8 6,8 L8,8 C8.55,8 9,8.45 9,9 L9,19 C9,19.55 8.55,20 8,20 L6,20 C5.45,20 5,19.55 5,19 Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M10,19 L10,5 C10,4.45 10.45,4 11,4 L13,4 C13.55,4 14,4.45 14,5 L14,19 C14,19.55 13.55,20 13,20 L11,20 C10.45,20 10,19.55 10,19 Z"/>
<path
android:fillColor="#FFFFFFFF"
android:pathData="M15,19 L15,12 C15,11.45 15.45,11 16,11 L18,11 C18.55,11 19,11.45 19,12 L19,19 C19,19.55 18.55,20 18,20 L16,20 C15.45,20 15,19.55 15,19 Z"/>
<!-- 趋势线 -->
<path
android:strokeColor="#FFFFFFFF"
android:strokeWidth="1.5"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M3,17 L7,13 L12,15 L17,9 L21,11"/>
<!-- 箭头 -->
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21,11 L19,9 L19,13 Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

3
ed25519-private.pem Normal file
View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJ2hPOHEhH2nfZaN139WbrOTqR5pTrlNU3mlDO67G8QI
-----END PRIVATE KEY-----

3
ed25519-public.pem Normal file
View File

@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAWqYmIIeu1UuVhPIcTPZpVWRTF9bq2bH+1rY+LeHyv1E=
-----END PUBLIC KEY-----

1
icon.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1764135432659" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8969" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 540.444444m-312.888889 0a312.888889 312.888889 0 1 0 625.777778 0 312.888889 312.888889 0 1 0-625.777778 0Z" fill="#bfbfbf" p-id="8970" data-spm-anchor-id="a313x.search_index.0.i0.209b3a81CSm3N4" class=""></path><path d="M512 398.222222c227.555556 0 412.444444 56.888889 412.444444 128S739.555556 654.222222 512 654.222222 99.555556 597.333333 99.555556 526.222222 284.444444 398.222222 512 398.222222m0-56.888889c-174.364444 0-469.333333 38.968889-469.333333 184.888889S337.635556 711.111111 512 711.111111s469.333333-38.968889 469.333333-184.888889S686.364444 341.333333 512 341.333333z" fill="" p-id="8971" data-spm-anchor-id="a313x.search_index.0.i6.209b3a81CSm3N4" class=""></path><path d="M312.888889 174.364444c69.973333 0 199.111111 113.777778 307.484444 281.6C744.106667 646.257778 796.444444 832 739.555556 870.968889a45.795556 45.795556 0 0 1-25.884445 7.111111c-69.973333 0-199.111111-113.777778-307.484444-281.6C279.893333 406.186667 227.555556 220.444444 284.444444 181.475556a45.795556 45.795556 0 0 1 28.444445-7.111112m0-56.888888a102.684444 102.684444 0 0 0-56.888889 16.497777c-36.124444 23.893333-71.395556 80.213333-32.711111 213.902223a1077.475556 1077.475556 0 0 0 135.964444 279.893333c93.013333 142.222222 241.777778 307.2 354.986667 307.2a102.684444 102.684444 0 0 0 56.888889-16.497778c36.124444-23.893333 71.395556-80.213333 32.711111-213.902222a1077.475556 1077.475556 0 0 0-135.964444-279.893333C573.44 284.444444 424.675556 117.475556 312.888889 117.475556z" fill="#7dc5eb" p-id="8972" data-spm-anchor-id="a313x.search_index.0.i3.209b3a81CSm3N4" class=""></path><path d="M695.182222 163.271111a47.786667 47.786667 0 0 1 23.04 5.688889c61.155556 35.555556 18.488889 223.857778-95.288889 421.262222-101.546667 175.502222-224.711111 298.951111-294.115555 298.951111a47.786667 47.786667 0 0 1-23.04-5.688889c-61.155556-35.555556-18.488889-223.857778 95.288889-421.262222C502.613333 286.72 625.777778 163.271111 695.182222 163.271111m0-56.888889c-113.777778 0-256 176.355556-343.324444 327.395556s-200.817778 426.666667-74.524445 498.915555a102.115556 102.115556 0 0 0 51.484445 13.368889c113.777778 0 256-176.355556 343.324444-327.395555s200.817778-426.666667 74.524445-498.915556a102.115556 102.115556 0 0 0-51.484445-13.368889z" fill="#e89abe" p-id="8973" data-spm-anchor-id="a313x.search_index.0.i1.209b3a81CSm3N4" class=""></path><path d="M170.666667 625.777778m-56.888889 0a56.888889 56.888889 0 1 0 113.777778 0 56.888889 56.888889 0 1 0-113.777778 0Z" fill="" p-id="8974"></path><path d="M597.333333 170.666667m-56.888889 0a56.888889 56.888889 0 1 0 113.777778 0 56.888889 56.888889 0 1 0-113.777778 0Z" fill="" p-id="8975"></path><path d="M625.777778 881.777778m-56.888889 0a56.888889 56.888889 0 1 0 113.777778 0 56.888889 56.888889 0 1 0-113.777778 0Z" fill="" p-id="8976"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB