kotlin,Android,血压记载程序

打印 上一主题 下一主题

主题 1304|帖子 1304|积分 3912


  1. package com.example.mynumset
  2. import android.app.Activity
  3. import android.content.Context
  4. import android.os.Bundle
  5. import android.util.Log
  6. import androidx.activity.ComponentActivity
  7. import androidx.activity.compose.setContent
  8. import androidx.activity.enableEdgeToEdge
  9. import androidx.compose.foundation.background
  10. import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
  11. import androidx.compose.foundation.layout.*
  12. import androidx.compose.foundation.lazy.LazyColumn
  13. import androidx.compose.foundation.lazy.rememberLazyListState
  14. import androidx.compose.foundation.shape.RoundedCornerShape
  15. import androidx.compose.material3.*
  16. import androidx.compose.runtime.*
  17. import androidx.compose.ui.Alignment
  18. import androidx.compose.ui.Modifier
  19. import androidx.compose.ui.platform.LocalDensity
  20. import androidx.compose.ui.text.font.FontWeight
  21. import androidx.compose.ui.text.style.TextAlign
  22. import androidx.compose.ui.unit.dp
  23. import androidx.compose.ui.unit.sp
  24. import com.example.mynumset.ui.theme.MyNumSetTheme
  25. import kotlinx.coroutines.launch
  26. import androidx.datastore.core.DataStore
  27. import androidx.datastore.preferences.core.Preferences
  28. import androidx.datastore.preferences.core.edit
  29. import androidx.datastore.preferences.core.intPreferencesKey
  30. import androidx.datastore.preferences.preferencesDataStore
  31. import androidx.compose.ui.graphics.Color
  32. import androidx.compose.ui.platform.LocalContext
  33. import kotlinx.coroutines.Dispatchers
  34. import kotlinx.coroutines.flow.first
  35. import androidx.compose.foundation.layout.fillMaxSize
  36. import androidx.compose.foundation.clickable
  37. import java.time.LocalDateTime
  38. import java.time.format.DateTimeFormatter
  39. import java.util.*
  40. import androidx.compose.material3.ExperimentalMaterial3Api
  41. import androidx.compose.ui.window.Dialog
  42. import java.time.Instant
  43. import java.time.LocalDate
  44. import java.time.LocalTime
  45. import java.time.ZoneId
  46. import android.content.ContentValues
  47. import android.database.sqlite.SQLiteDatabase
  48. import android.database.sqlite.SQLiteOpenHelper
  49. import android.view.ViewGroup
  50. import android.widget.Toast
  51. import androidx.activity.compose.BackHandler
  52. import androidx.compose.runtime.saveable.rememberSaveable
  53. import kotlinx.coroutines.withContext
  54. import com.github.mikephil.charting.charts.LineChart
  55. import com.github.mikephil.charting.components.XAxis
  56. import com.github.mikephil.charting.data.Entry
  57. import com.github.mikephil.charting.data.LineData
  58. import com.github.mikephil.charting.data.LineDataSet
  59. import com.github.mikephil.charting.formatter.ValueFormatter
  60. import androidx.compose.ui.viewinterop.AndroidView
  61. import com.github.mikephil.charting.components.Legend
  62. import com.github.mikephil.charting.components.LimitLine
  63. import androidx.compose.ui.graphics.Brush
  64. import androidx.compose.ui.geometry.Offset
  65. import androidx.compose.ui.graphics.Shadow
  66. import androidx.compose.ui.res.painterResource
  67. // 在原有类中添加数据库帮助类(放在MainActivity类外)
  68. class RecordDbHelper(context: Context) : SQLiteOpenHelper(
  69.     context, DATABASE_NAME, null, DATABASE_VERSION
  70. ) {
  71.     init {
  72.         // 添加连接池配置
  73.         setWriteAheadLoggingEnabled(true)
  74.         writableDatabase.enableWriteAheadLogging()
  75.     }
  76.     companion object {
  77.         // 定义数据库的名称和版本
  78.         private const val DATABASE_NAME = "health_records.db"
  79.         private const val DATABASE_VERSION = 1
  80.     }
  81.     // 创建数据库表
  82.     override fun onCreate(db: SQLiteDatabase) {
  83.         // 执行SQL语句创建records表
  84.         db.execSQL("""
  85.             CREATE TABLE records (
  86.                 date TEXT NOT NULL,  
  87.                 time TEXT NOT NULL,
  88.                 low INTEGER NOT NULL,
  89.                 heart_rate INTEGER NOT NULL,
  90.                 status TEXT NOT NULL,
  91.                 PRIMARY KEY (date, time)
  92.             )
  93.         """)
  94.     }
  95.     // 升级数据库
  96.     override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
  97.         // 如果存在records表,则删除
  98.         db.execSQL("DROP TABLE IF EXISTS records")
  99.         // 重新创建数据库表
  100.         onCreate(db)
  101.     }
  102. }
  103. // 定义一个数据类 HealthRecord,用于存储健康记录信息
  104. data class HealthRecord(
  105.     // 日期字段,存储记录的日期,类型为 String
  106.     val date: String,
  107.     // 时间字段,存储记录的时间,类型为 String
  108.     val time: String,
  109.     // 高血压字段,存储高压值,类型为 Int
  110.     val high: Int,
  111.     // 低压字段,存储低压值,类型为 Int
  112.     val low: Int,
  113.     // 心率字段,存储心率值,类型为 Int
  114.     val heartRate: Int,
  115.     // 状态字段,存储健康状态描述,类型为 String
  116.     val status: String
  117. )
  118. class MainActivity : ComponentActivity() {
  119.     // 原色
  120.     val originalGreen = Color(0xFFE8F5E9)
  121.     // 加深方案(任选其一)
  122.     val deepGreen1 = Color(0xFFC8E6C9)  // 浅灰绿(比原色深10%)
  123.     val deepGreen2 = Color(0xFFA5D6A7)  // 中等青绿(Material Teal 200)
  124.     val deepGreen3 = Color(0xFF80CBC4)  // 深青蓝色(Material Teal 300)
  125.     val gradientColors = listOf(deepGreen2, deepGreen3)
  126.     override fun onCreate(savedInstanceState: Bundle?) {
  127.         super.onCreate(savedInstanceState)
  128.         enableEdgeToEdge()
  129.         setContent {
  130.             MyNumSetTheme {
  131.                 BottomNavigationExample()
  132.             }
  133.         }
  134.     }
  135. }
  136. @Composable
  137. fun NumberPicker(
  138.     modifier: Modifier = Modifier,
  139.     initialValue: Int = 0,
  140.     range: IntRange = 20..200,
  141.     onValueChange: (Int) -> Unit
  142. ) {
  143.     val itemHeight = 50.dp
  144.     val startOffset = (initialValue - range.first).coerceIn(0 until range.count())
  145.     //Log.d("initialValue=", "读取成功: $initialValue")
  146.     val listState = rememberLazyListState(initialFirstVisibleItemIndex = startOffset)
  147.     val coroutineScope = rememberCoroutineScope()
  148.     val itemHeightPx = with(LocalDensity.current) { itemHeight.toPx() }
  149.     // 实时计算当前选中项索引
  150.     val selectedIndex by remember {
  151.         derivedStateOf {
  152.             val offset = listState.firstVisibleItemScrollOffset
  153.             val index = listState.firstVisibleItemIndex
  154.             if (offset > itemHeightPx / 2) index + 1 else index
  155.         }
  156.     }
  157.     // 当滚动停止时,吸附到中间项并回调
  158.     LaunchedEffect(selectedIndex, listState.isScrollInProgress) {
  159.         if (!listState.isScrollInProgress) {
  160.             coroutineScope.launch {
  161.                 listState.animateScrollToItem(selectedIndex)
  162.             }
  163.             onValueChange((range.first + selectedIndex))
  164.         }
  165.     }
  166.     LazyColumn(
  167.         state = listState,
  168.         modifier = modifier
  169.             .height(itemHeight * 3)
  170.             .width(100.dp),
  171.         horizontalAlignment = Alignment.CenterHorizontally,
  172.         contentPadding = PaddingValues(vertical = itemHeight),
  173.         flingBehavior = rememberSnapFlingBehavior(lazyListState = listState)
  174.     ) {
  175.         items(range.count()) { index ->
  176.             val value = range.first + index
  177.             val isSelected = selectedIndex == index
  178.             Text(
  179.                 text = value.toString(),
  180.                 fontSize = if (isSelected) 32.sp else 20.sp,
  181.                 fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
  182.                 color = if (isSelected) Color.Black else Color.Gray,
  183.                 modifier = Modifier
  184.                     .height(itemHeight)
  185.                     .fillMaxWidth(),
  186.                 textAlign = TextAlign.Center
  187.             )
  188.         }
  189.     }
  190. }
  191. // 定义 DataStore 的扩展属性,用于访问应用的设置数据存储
  192. val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(name = "my_settings")
  193. // PreferencesKeys 是一个单例对象,用于定义 DataStore 的键
  194. private object PreferencesKeys {
  195.     val NumbGy = intPreferencesKey("saved_gaoya")
  196.     val NumbDy = intPreferencesKey("saved_diya")
  197.     val NumbXl = intPreferencesKey("saved_xinlv")
  198. }
  199. @Composable
  200. fun NumberPickerDemo(dbHelper: RecordDbHelper) {
  201.     val context = LocalContext.current
  202.     val scope = rememberCoroutineScope() // 新增协程作用域
  203.     //val dbHelper = remember { RecordDbHelper(context) }
  204.     val coroutineScope = rememberCoroutineScope()
  205.     var currentStatusText by remember { mutableStateOf("") }
  206.     var currentStatusColor by remember { mutableStateOf(Color.Black) }
  207.     var lastClickTime by remember { mutableStateOf(0L) }
  208.     // 添加两个新状态
  209.     var selectedNumberGy by remember { mutableStateOf<Int?>(null) }
  210.     var selectedNumberDy by remember { mutableStateOf<Int?>(null) }
  211.     var selectedNumberXl by remember { mutableStateOf<Int?>(null) }
  212.     // 记住是否显示日期选择器的状态
  213.     var showDatePicker by remember { mutableStateOf(false) }
  214.     // 记住是否显示时间选择器的状态
  215.     var showTimePicker by remember { mutableStateOf(false) }
  216.     // 记住当前选中的日期和时间
  217.     var selectedDateTime by remember { mutableStateOf(LocalDateTime.now()) }
  218.     // 日期格式化器
  219.     val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.getDefault())
  220.     // 时间格式化器
  221.     val timeFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault())
  222.     LaunchedEffect(Unit) {
  223.         try {
  224.             val preferences = context.settingsDataStore.data.first()
  225.             selectedNumberGy = preferences[PreferencesKeys.NumbGy] ?: 100
  226.             selectedNumberDy = preferences[PreferencesKeys.NumbDy] ?: 80
  227.             selectedNumberXl = preferences[PreferencesKeys.NumbXl] ?: 75
  228.         }
  229.         catch (e: Exception) {
  230.             Log.e("NumberPickerDemo", "读取设置失败", e)
  231.         }
  232.     }
  233.     Column(
  234.         modifier = Modifier.fillMaxSize(),
  235.         verticalArrangement = Arrangement.Center,
  236.         horizontalAlignment = Alignment.CenterHorizontally
  237.     ) {
  238.         // 显示血压状态
  239.             selectedNumberGy?.let { gy ->
  240.                 selectedNumberDy?.let { dy ->
  241.                     val (statusText, statusColor) = run {
  242.                         // 分别判断高压和低压的等级
  243.                         val gyLevel = when {
  244.                             gy < 90 -> 1
  245.                             gy in 90 until 120 -> 2
  246.                             gy in 120 until 140 -> 3
  247.                             gy in 140 until 160 -> 4
  248.                             gy in 160 until 180 -> 5
  249.                             gy >= 180 -> 6
  250.                             else -> 0
  251.                         }
  252.                         val dyLevel = when {
  253.                             dy < 60 -> 1
  254.                             dy in 60 until 80 -> 2
  255.                             dy in 80 until 90 -> 3
  256.                             dy in 90 until 100 -> 4
  257.                             dy in 100 until 110 -> 5
  258.                             dy >= 110 -> 6
  259.                             else -> 0
  260.                         }
  261.                         // 取最高等级作为最终结果
  262.                         when (maxOf(gyLevel, dyLevel)) {
  263.                             1 -> "低血压" to Color.Blue
  264.                             2 -> "正常血压" to Color(0xFF006400)
  265.                             3 -> "正常高值血压" to Color(0xFFFFB300)
  266.                             4 -> "1级高血压" to Color(0xFFFF6700)
  267.                             5 -> "2级高血压" to Color.Red
  268.                             6 -> "3级高血压" to Color.Red
  269.                             else -> "****" to Color.Black
  270.                         }
  271.                     }.also {
  272.                         currentStatusText = it.first
  273.                         currentStatusColor = it.second
  274.                     }
  275.                     Card(
  276.                         modifier = Modifier
  277.                             .padding(top = 50.dp, bottom = 50.dp)
  278.                             .size(width = 350.dp, height = 140.dp)
  279.                             .background(
  280.                                 brush = Brush.verticalGradient(
  281.                                     colors = listOf(
  282.                                         Color.White.copy(alpha = 0.3f),
  283.                                         Color.Transparent
  284.                                     ),
  285.                                     startY = 0f,
  286.                                     endY = 350f
  287.                                 ),
  288.                                 shape = RoundedCornerShape(24.dp)
  289.                             ),
  290.                         shape = RoundedCornerShape(24.dp),
  291.                         elevation = CardDefaults.cardElevation(4.dp)
  292.                     ) {
  293.                         val density = LocalDensity.current
  294.                         Text(
  295.                             text = statusText,
  296.                             fontSize = 50.sp,
  297.                             color = statusColor,
  298.                             style = LocalTextStyle.current.copy(
  299.                                 shadow = with(density) {
  300.                                     Shadow(
  301.                                         color = Color.Black.copy(alpha = 0.3f),
  302.                                         offset = Offset(2.dp.toPx(), 2.dp.toPx()),
  303.                                         blurRadius = 4f
  304.                                     )
  305.                                 }
  306.                             ),
  307.                             fontWeight = FontWeight.Bold,
  308.                             modifier = Modifier
  309.                                 .fillMaxSize()
  310.                                 .padding(24.dp),
  311.                             textAlign = TextAlign.Center
  312.                         )
  313.                     }
  314.                 }
  315.             } ?: run {
  316.                 CircularProgressIndicator()
  317.             }
  318.             selectedNumberGy?.let { gy ->
  319.                 selectedNumberDy?.let { dy ->
  320.                     selectedNumberXl?.let { xl ->
  321.                         // 横向排列三个选择器
  322.                         Row(
  323.                             modifier = Modifier.fillMaxWidth(),
  324.                             horizontalArrangement = Arrangement.SpaceEvenly
  325.                         ) {
  326.                             // 高压选择器
  327.                             NumberPickerComponent(
  328.                                 value = gy,
  329.                                 label = "收缩压",
  330.                                 range = 80..200,
  331.                                 onSave = { value ->
  332.                                     coroutineScope.launch {
  333.                                         //delay(300)
  334.                                         withContext(Dispatchers.Main) {
  335.                                             selectedNumberGy = value
  336.                                         }
  337.                                         withContext(Dispatchers.IO) {
  338.                                             context.settingsDataStore.edit { settings ->
  339.                                                 settings[PreferencesKeys.NumbGy] = value
  340.                                                 Log.d("NumberPickerDemo", "保存高压设置:$value")
  341.                                             }
  342.                                         }
  343.                                     }
  344.                                 }
  345.                             )
  346.                             // 低压选择器
  347.                             NumberPickerComponent(
  348.                                 value = dy,
  349.                                 label = "舒张压",
  350.                                 range = 50..150,
  351.                                 onSave = { value ->
  352.                                     coroutineScope.launch {
  353.                                         //delay(300)
  354.                                         withContext(Dispatchers.Main) {
  355.                                             selectedNumberDy = value
  356.                                         }
  357.                                         withContext(Dispatchers.IO) {
  358.                                             context.settingsDataStore.edit { settings ->
  359.                                                 settings[PreferencesKeys.NumbDy] = value
  360.                                                 Log.d("NumberPickerDemo", "保存舒张压设置:$value")
  361.                                             }
  362.                                         }
  363.                                     }
  364.                                 }
  365.                             )
  366.                             // 心率选择器
  367.                             NumberPickerComponent(
  368.                                 value = xl,
  369.                                 label = "心率",
  370.                                 range = 40..200,
  371.                                 onSave = { value ->
  372.                                     coroutineScope.launch {
  373.                                         //delay(300)
  374.                                         withContext(Dispatchers.Main) {
  375.                                             selectedNumberXl = value
  376.                                         }
  377.                                         withContext(Dispatchers.IO) {
  378.                                             context.settingsDataStore.edit { settings ->
  379.                                                 settings[PreferencesKeys.NumbXl] = value
  380.                                                 Log.d("NumberPickerDemo", "保存心率设置:$value")
  381.                                             }
  382.                                         }
  383.                                     }
  384.                                 }
  385.                             )
  386.                         }
  387.                         val saveLock = remember { Any() } // 添加同步锁对象
  388.                         Button(
  389.                             onClick = {
  390.                                 val now = System.currentTimeMillis()
  391.                                 if (now - lastClickTime > 1000) {
  392.                                     val date = selectedDateTime.format(dateFormatter)
  393.                                     val time = selectedDateTime.format(timeFormatter)
  394.                                     val high = selectedNumberGy ?: return@Button
  395.                                     val low = selectedNumberDy ?: return@Button
  396.                                     val heartRate = selectedNumberXl ?: return@Button
  397.                                     val status = currentStatusText
  398.                                     lastClickTime = now
  399.                                     synchronized(saveLock) {
  400.                                         scope.launch(Dispatchers.IO){
  401.                                             try {
  402.                                                 dbHelper.writableDatabase.use { db ->
  403.                                                     val values = ContentValues().apply {
  404.                                                         put("date", date)
  405.                                                         put("time", time)
  406.                                                         put("high", high)
  407.                                                         put("low", low)
  408.                                                         put("heart_rate", heartRate)
  409.                                                         put("status", status)
  410.                                                     }
  411.                                                     db.insertWithOnConflict(
  412.                                                         "records",
  413.                                                         null,
  414.                                                         values,
  415.                                                         SQLiteDatabase.CONFLICT_REPLACE
  416.                                                     )
  417.                                                 }
  418.                                                 withContext(Dispatchers.Main) {
  419.                                                     //delay(300)
  420.                                                     Toast.makeText(context, "记录保存成功", Toast.LENGTH_SHORT).show()
  421.                                                 }
  422.                                             } catch (e: Exception) {
  423.                                                 Log.e("Database", "保存失败", e)
  424.                                             }
  425.                                         }
  426.                                     }
  427.                                 }
  428.                             },
  429.                             modifier = Modifier
  430.                                 .padding(vertical = 8.dp)
  431.                                 .width(200.dp)    // 加长按钮
  432.                                 .height(48.dp),   // 增加高度
  433.                             shape = RoundedCornerShape(8.dp),
  434.                             colors = ButtonDefaults.buttonColors(
  435.                                 containerColor = Color(0xFF006400), // 使用深绿色配色
  436.                                 contentColor = Color.White
  437.                             )
  438.                         ) {
  439.                             Text(
  440.                                 "记 录",
  441.                                 fontSize = 22.sp,  // 加大字号
  442.                                 fontWeight = FontWeight.Medium
  443.                             )
  444.                         }
  445.                         Box(
  446.                             modifier = Modifier
  447.                                 .fillMaxWidth()
  448.                                 .padding(16.dp)
  449.                                 .background(Color(0xFFE0F2F1), RoundedCornerShape(8.dp))
  450.                                 .padding(16.dp)
  451.                         ) {
  452.                             Text(
  453.                                 text = when (currentStatusText) {
  454.                                     "低血压" -> "收缩压<90,舒张压<60 ,一般无需治疗,若出现头晕、乏力等症状,建议就医。"
  455.                                     "正常血压" -> "90≤收缩压<120,60≤舒张压<80 ,无需治疗,保持健康生活方式(如低盐饮食、适量运动、戒烟限酒)。"
  456.                                     "正常高值血压" -> "120≤收缩压<140,80≤舒张压<90 ,注意饮食、运动和定期监测血压。建议生活方式干预,必要时咨询医生。"
  457.                                     "1级高血压" -> "140≤收缩压<160,90≤舒张压<100 ,改变生活方式(如减少盐摄入、增加运动),可能需要药物治疗。"
  458.                                     "2级高血压" -> "160≤收缩压<180,100≤舒张压<110 ,需要药物治疗并结合生活方式干预(如饮食调整、运动、减重)。"
  459.                                     "3级高血压" -> "180≤收缩压,110≤舒张压, 需紧急医疗干预,可能需要住院治疗。通常需要联合药物治疗。"
  460.                                     else -> ""
  461.                                 },
  462.                                 color = Color.Black,
  463.                                 fontSize = 20.sp
  464.                             )
  465.                         }
  466.                     }
  467.                 }
  468.             } ?: run {
  469.                 CircularProgressIndicator()
  470.             }
  471.         // 显示日期和时间选择器
  472.         Row(
  473.             modifier = Modifier.fillMaxWidth(),
  474.             horizontalArrangement = Arrangement.SpaceEvenly
  475.         ){
  476.             Text(
  477.                 text = "日期: ${selectedDateTime.format(dateFormatter)}",
  478.                 style = MaterialTheme.typography.bodyLarge,
  479.                 fontSize = 20.sp, // 增大字体尺寸
  480.                 //fontWeight = FontWeight.Bold, // 加粗字体
  481.                 color = Color.DarkGray, // 使用更深的颜色
  482.                 modifier = Modifier
  483.                     .clickable { showDatePicker = true }
  484.                     .padding(8.dp)
  485.             )
  486.             Text(
  487.                 text = "时间: ${selectedDateTime.format(timeFormatter)}",
  488.                 style = MaterialTheme.typography.bodyLarge,
  489.                 fontSize = 20.sp, // 增大字体尺寸
  490.                 //fontWeight = FontWeight.Bold, // 加粗字体
  491.                 color = Color.DarkGray, // 使用更深的颜色
  492.                 modifier = Modifier
  493.                     .clickable { showTimePicker = true }
  494.                     .padding(8.dp)
  495.             )
  496.             // 日期选择器
  497.             if (showDatePicker) {
  498.                 DatePickerDialog(
  499.                     onDismissRequest = { showDatePicker = false }, // 点击外部时关闭日期选择器
  500.                     onDateSelected = { date ->
  501.                         // 更新选中的日期
  502.                         selectedDateTime = selectedDateTime.withYear(date.year).withMonth(date.monthValue).withDayOfMonth(date.dayOfMonth)
  503.                         showDatePicker = false // 关闭日期选择器
  504.                     }
  505.                 )
  506.             }
  507.             // 时间选择器
  508.             if (showTimePicker) {
  509.                 TimePickerDialog(
  510.                     onDismissRequest = { showTimePicker = false }, // 点击外部时关闭时间选择器
  511.                     onTimeSelected = { time ->
  512.                         // 更新选中的时间
  513.                         selectedDateTime = selectedDateTime.withHour(time.hour).withMinute(time.minute)
  514.                         showTimePicker = false // 关闭时间选择器
  515.                     }
  516.                 )
  517.             }
  518.         }
  519.     }
  520. }
  521. @Composable
  522. private fun NumberPickerComponent(
  523.     value: Int,
  524.     label: String,
  525.     range: IntRange,
  526.     onSave: (Int) -> Unit
  527. ) {
  528.     var currentValue by remember(value) { mutableStateOf(value) } // 添加当前值状态
  529.     Column(horizontalAlignment = Alignment.CenterHorizontally) {
  530.         Text(label, fontSize = 16.sp)
  531.         NumberPicker(
  532.             initialValue = value,
  533.             range = range,
  534.             onValueChange = { newValue ->
  535.                 if (newValue != currentValue) { // 只在数值变化时触发保存
  536.                     currentValue = newValue
  537.                     onSave(newValue)
  538.                 }
  539.             }
  540.         )
  541.     }
  542. }
  543. @OptIn(ExperimentalMaterial3Api::class)
  544. @Composable
  545. fun DatePickerDialog(
  546.     onDismissRequest: () -> Unit,
  547.     onDateSelected: (LocalDate) -> Unit
  548. ) {
  549.     // 获取当前日期
  550.     val currentDate = LocalDate.now()
  551.     // 创建DatePicker状态,并初始化为当前日期
  552.     val datePickerState = rememberDatePickerState(initialSelectedDateMillis = currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli())
  553.     // 创建对话框
  554.     Dialog(
  555.         onDismissRequest = onDismissRequest,
  556.     ) {
  557.         // 使用Column布局来组织对话框内容
  558.         Column(
  559.             modifier = Modifier
  560.                 .fillMaxWidth()
  561.                 .padding(16.dp)
  562.         ) {
  563.             // 显示标题
  564.             Text(
  565.                 text = "Select Date",
  566.                 style = MaterialTheme.typography.headlineSmall,
  567.                 modifier = Modifier.padding(bottom = 16.dp)
  568.             )
  569.             // 显示日期选择器
  570.             DatePicker(state = datePickerState)
  571.             // 使用Row布局来组织按钮
  572.             Row(
  573.                 modifier = Modifier
  574.                     .fillMaxWidth()
  575.                     .padding(top = 16.dp),
  576.                 horizontalArrangement = Arrangement.End
  577.             ) {
  578.                 // 取消按钮
  579.                 Button(
  580.                     onClick = onDismissRequest,
  581.                     modifier = Modifier.padding(end = 8.dp)
  582.                 ) {
  583.                     Text("Cancel")
  584.                 }
  585.                 // 确定按钮
  586.                 Button(onClick = {
  587.                     // 获取选中的日期
  588.                     val selectedDate = LocalDate.ofInstant(
  589.                         Instant.ofEpochMilli(datePickerState.selectedDateMillis ?: currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()),
  590.                         ZoneId.systemDefault()
  591.                     )
  592.                     // 调用回调函数,传递选中的日期
  593.                     onDateSelected(selectedDate)
  594.                 }) {
  595.                     Text("OK")
  596.                 }
  597.             }
  598.         }
  599.     }
  600. }
  601. @OptIn(ExperimentalMaterial3Api::class) // 使用实验性API的注解
  602. @Composable // 标记为可组合函数
  603. fun TimePickerDialog(
  604.     onDismissRequest: () -> Unit, // 对话框关闭时的回调函数
  605.     onTimeSelected: (LocalTime) -> Unit // 时间选择后的回调函数
  606. ) {
  607.     val currentTime = LocalTime.now(ZoneId.systemDefault()) // 获取当前时间
  608.     val timePickerState = rememberTimePickerState(initialHour = currentTime.hour, initialMinute = currentTime.minute) // 创建时间选择器的状态,并初始化为当前时间
  609.     Dialog(
  610.         onDismissRequest = onDismissRequest, // 设置对话框关闭的回调函数
  611.     ) {
  612.         Column(
  613.             modifier = Modifier
  614.                 .fillMaxWidth() // 填充父布局的宽度
  615.                 .padding(16.dp) // 设置内边距
  616.         ) {
  617.             Text(
  618.                 text = "Select Time", // 显示文本
  619.                 style = MaterialTheme.typography.headlineSmall, // 使用主题中的小标题样式
  620.                 modifier = Modifier.padding(bottom = 16.dp) // 设置底部内边距
  621.             )
  622.             TimePicker(state = timePickerState) // 显示时间选择器
  623.             Row(
  624.                 modifier = Modifier
  625.                     .fillMaxWidth() // 填充父布局的宽度
  626.                     .padding(top = 16.dp), // 设置顶部内边距
  627.                 horizontalArrangement = Arrangement.End // 水平排列方式为靠右
  628.             ) {
  629.                 Button(
  630.                     onClick = onDismissRequest, // 点击按钮时调用关闭回调函数
  631.                     modifier = Modifier.padding(end = 8.dp) // 设置右边距
  632.                 ) {
  633.                     Text("Cancel") // 显示取消文本
  634.                 }
  635.                 Button(onClick = {
  636.                     val selectedTime = LocalTime.of(timePickerState.hour, timePickerState.minute) // 获取选中的时间
  637.                     onTimeSelected(selectedTime) // 调用时间选择后的回调函数
  638.                 }) {
  639.                     Text("OK") // 显示确定文本
  640.                 }
  641.             }
  642.         }
  643.     }
  644. }
  645. @Composable
  646. fun BottomNavigationExample() {
  647.     val context = LocalContext.current
  648.     val activity = context as Activity // 直接转换为 Activity
  649.     val dbHelper = remember { RecordDbHelper(context) }
  650.     var selectedItem by rememberSaveable { mutableStateOf(0) }
  651.     val historyStack = remember { mutableStateListOf<Int>() }
  652.     val originalGreen = Color(0xFFE8F5E9)
  653.     // 加深方案(任选其一)
  654.     val deepGreen1 = Color(0xFFC8E6C9)  // 浅灰绿(比原色深10%)
  655.     val deepGreen2 = Color(0xFFA5D6A7)  // 中等青绿(Material Teal 200)
  656.     val deepGreen3 = Color(0xFF80CBC4)  // 深青蓝色(Material Teal 300)
  657.     val gradientColors = listOf(deepGreen2, deepGreen3)
  658.     // 处理返回键
  659.     BackHandler(enabled = historyStack.isNotEmpty()) {
  660.         if (historyStack.isNotEmpty()) {
  661.             // 兼容性写法(支持所有 Kotlin 版本)
  662.             val lastIndex = historyStack.size - 1
  663.             selectedItem = historyStack.removeAt(lastIndex)
  664.         } else {
  665.             activity.finish() // 正确调用 Activity 的 finish()
  666.         }
  667.     }
  668.     // 点击底部导航项时更新历史栈
  669.     fun onTabSelected(index: Int) {
  670.         historyStack.add(selectedItem)
  671.         selectedItem = index
  672.     }
  673.     val items = listOf("记录", "历史", "图表")
  674.     // 使用Scaffold布局,包含底部导航栏
  675.     Scaffold(
  676.         bottomBar = {
  677.             // 创建底部导航栏
  678.             NavigationBar(
  679.                 modifier = Modifier
  680.                     //.height(72.dp) // 保持高度设置
  681.                     .padding(horizontal = 8.dp)
  682.                     .background(
  683.                         brush = Brush.linearGradient(
  684.                             colors = gradientColors,
  685.                             start = Offset(0f, 0f),
  686.                             end = Offset(1000f, 0f)
  687.                         ),
  688.                         alpha = 0.9f  // 添加背景透明度
  689.                     ),
  690.                 containerColor = Color.Transparent
  691.             ) {
  692.                 // 遍历items列表,为每个项创建一个NavigationBarItem
  693.                 items.forEachIndexed { index, item ->
  694.                     NavigationBarItem(
  695.                         // 根据index选择不同的图标
  696.                         icon = {
  697.                             Icon(
  698.                                 painter = painterResource(id = when (index) {
  699.                                     0 -> R.drawable.write  // 修正资源引用(去掉_png后缀)
  700.                                     1 -> R.drawable.list // 修正资源引用(去掉_png后缀)
  701.                                     else -> R.drawable.chart2
  702.                                 }),
  703.                                 contentDescription = item,
  704.                                 modifier = Modifier
  705.                                     .size(48.dp),
  706.                                     //.background(Color.Red)
  707.                                 tint = Color.Unspecified
  708.                             )
  709.                         },
  710.                         // 显示项的标签
  711.                         label = { Text(
  712.                             item,
  713.                             fontSize = 12.sp,
  714.                             color = Color.Black,  // 强制文字颜色为白色
  715.                             modifier = Modifier.padding(top = 0.dp)
  716.                         //modifier = Modifier.padding(vertical = 2.dp)
  717.                         )  },
  718.                         // 判断当前项是否被选中
  719.                         selected = selectedItem == index,
  720.                         onClick = { onTabSelected(index) },
  721.                         colors = NavigationBarItemDefaults.colors(
  722.                             selectedIconColor = Color.White,
  723.                             indicatorColor = Color(0xFF006400).copy(alpha = 0.5f)
  724.                         )
  725.                     )
  726.                 }
  727.             }
  728.         }
  729.     ) { innerPadding ->
  730.         // 根据选中的项显示不同的内容
  731.         when (selectedItem) {
  732.             0 -> Box(modifier = Modifier
  733.                 .fillMaxSize()
  734.                 .background(
  735.                     brush = Brush.verticalGradient(
  736.                         colors = gradientColors,
  737.                         startY = 0f,
  738.                         endY = 1200f
  739.                     )
  740.                 )
  741.                 .padding(innerPadding)  // 添加内边距
  742.             ) {
  743.                 NumberPickerDemo(dbHelper = dbHelper)
  744.             }
  745.             1 -> Box(modifier = Modifier
  746.                 .fillMaxSize()
  747.                 .background(
  748.                     brush = Brush.verticalGradient(  // 历史界面也改为渐变背景
  749.                         colors = gradientColors,
  750.                         startY = 0f,
  751.                         endY = 1200f
  752.                     )
  753.                 )
  754.                 .padding(top = 20.dp)  // 新增顶部内边距
  755.                 .padding(innerPadding)  // 添加内边距
  756.             ){
  757.                 HistoryScreen(dbHelper = dbHelper)
  758.             }
  759.             2 -> Box(modifier = Modifier
  760.                 .fillMaxSize()
  761.                 .background(
  762.                     brush = Brush.verticalGradient(  // 统计界面改为渐变背景
  763.                         colors = gradientColors,
  764.                         startY = 0f,
  765.                         endY = 1200f
  766.                     )
  767.                 )
  768.                 .padding(innerPadding)  // 添加内边距
  769.             ){
  770.                 StatisticsScreen(dbHelper = dbHelper)
  771.             }
  772.         }
  773.     }
  774. }
  775. @Composable
  776. fun HistoryScreen(dbHelper: RecordDbHelper) {
  777.     val records = remember { mutableStateListOf<HealthRecord>() }
  778.     var showDeleteDialog by remember { mutableStateOf(false) }
  779.     var selectedRecord by remember { mutableStateOf<HealthRecord?>(null) }
  780.     val scope = rememberCoroutineScope()
  781.     // 加载数据库记录
  782.     LaunchedEffect(Unit) {
  783.         scope.launch(Dispatchers.IO) {
  784.             try {
  785.                 val loadedRecords = mutableListOf<HealthRecord>()
  786.                 dbHelper.readableDatabase.use { db ->
  787.                     val cursor = db.query(
  788.                         "records",
  789.                         arrayOf("date", "time", "high", "low", "heart_rate", "status"),
  790.                         null, null, null, null,
  791.                         "date DESC, time DESC"
  792.                     )
  793.                     try {
  794.                         while (cursor.moveToNext()) {
  795.                             val date = cursor.getString(0) ?: ""
  796.                             val time = cursor.getString(1) ?: ""
  797.                             val high = cursor.getInt(2)
  798.                             val low = cursor.getInt(3)
  799.                             val heartRate = cursor.getInt(4)
  800.                             val status = cursor.getString(5) ?: "未知"
  801.                             loadedRecords.add(
  802.                                 HealthRecord(date, time, high, low, heartRate, status)
  803.                             )
  804.                         }
  805.                     } finally {
  806.                         cursor.close()
  807.                     }
  808.                 }
  809.                 withContext(Dispatchers.Main) {
  810.                     records.clear()
  811.                     records.addAll(loadedRecords)
  812.                 }
  813.             } catch (e: Exception) {
  814.                 Log.e("HistoryScreen", "加载记录失败: ${e.message}", e)
  815.             }
  816.         }
  817.     }
  818.     // 删除确认对话框
  819.     if (showDeleteDialog) {
  820.         AlertDialog(
  821.             onDismissRequest = { showDeleteDialog = false },
  822.             title = { Text("删除记录") },
  823.             text = { Text("确定要删除这条记录吗?") },
  824.             confirmButton = {
  825.                 TextButton(
  826.                     onClick = {
  827.                         selectedRecord?.let { record ->
  828.                             scope.launch(Dispatchers.IO) {
  829.                                 try {
  830.                                     dbHelper.writableDatabase.delete(
  831.                                         "records",
  832.                                         "date = ? AND time = ?",
  833.                                         arrayOf(record.date, record.time)
  834.                                     )
  835.                                     withContext(Dispatchers.Main) {
  836.                                         records.remove(record)
  837.                                         showDeleteDialog = false
  838.                                     }
  839.                                 } catch (e: Exception) {
  840.                                     Log.e("HistoryScreen", "删除失败: ${e.message}", e)
  841.                                 }
  842.                             }
  843.                         }
  844.                     }
  845.                 ) { Text("确认") }
  846.             },
  847.             dismissButton = {
  848.                 TextButton(
  849.                     onClick = { showDeleteDialog = false }
  850.                 ) { Text("取消") }
  851.             }
  852.         )
  853.     }
  854.     // 记录列表
  855.     LazyColumn(
  856.         modifier = Modifier.fillMaxSize(),
  857.         horizontalAlignment = Alignment.CenterHorizontally
  858.     ) {
  859.         items(
  860.             count = records.size,
  861.             key = { index -> "${records[index].date}_${records[index].time}" }
  862.         ) { index ->
  863.             val record = records[index]
  864.             Card(
  865.                 modifier = Modifier
  866.                     .fillMaxWidth(0.9f)
  867.                     .padding(vertical = 2.dp)
  868.                     .clickable {
  869.                         selectedRecord = record
  870.                         showDeleteDialog = true
  871.                     },
  872.                 elevation = CardDefaults.cardElevation(4.dp),
  873.                 colors = CardDefaults.cardColors(
  874.                     containerColor = when (record.status) {
  875.                         "低血压" -> Color(0xFFE3F2FD)
  876.                         "正常血压" -> Color(0xFFE8F5E9)
  877.                         "正常高值血压" -> Color(0xFFFFF8E1)
  878.                         "1级高血压" -> Color(0xFFFFF3E0)
  879.                         "2级高血压", "3级高血压" -> Color(0xFFFFEBEE)
  880.                         else -> Color.White
  881.                     }
  882.                 )
  883.             ) {
  884.                 Column(modifier = Modifier.padding(16.dp)) {
  885.                     Row(
  886.                         modifier = Modifier
  887.                             .fillMaxWidth()
  888.                             .padding(top = 16.dp)
  889.                     ) {
  890.                         Text(
  891.                             "日期: ${record.date} ${record.time}",
  892.                             fontSize = 16.sp,
  893.                             modifier = Modifier.weight(1f)
  894.                         )
  895.                         Text(
  896.                             "状态: ${record.status}",
  897.                             color = when (record.status) {
  898.                                 "低血压" -> Color.Blue
  899.                                 "正常血压" -> Color(0xFF006400)
  900.                                 "正常高值血压" -> Color(0xFFFFB300)
  901.                                 "1级高血压" -> Color(0xFFFF6700)
  902.                                 "2级高血压", "3级高血压" -> Color.Red
  903.                                 else -> Color.Gray
  904.                             },
  905.                             fontSize = 18.sp
  906.                         )
  907.                     }
  908.                     Row(
  909.                         modifier = Modifier
  910.                             .fillMaxWidth()
  911.                             .padding(top = 16.dp)
  912.                     ) {
  913.                         Text(
  914.                             "血压: ${record.high}/${record.low} mmHg",
  915.                             fontSize = 18.sp,
  916.                             modifier = Modifier.weight(1f)
  917.                         )
  918.                         Text(
  919.                             "心率: ${record.heartRate} 次/分钟",
  920.                             fontSize = 18.sp
  921.                         )
  922.                     }
  923.                 }
  924.             }
  925.         }
  926.     }
  927. }
  928. @Composable
  929. fun StatisticsScreen(dbHelper: RecordDbHelper) {
  930.     val context = LocalContext.current
  931.     val records = remember { mutableStateListOf<HealthRecord>() }
  932.     val isLoading = remember { mutableStateOf(true) }
  933.     // 加载数据
  934.     LaunchedEffect(Unit) {
  935.         withContext(Dispatchers.IO) {
  936.             val tempList = dbHelper.readableDatabase.use { db ->
  937.                 db.query(
  938.                     "records",
  939.                     arrayOf("date", "time", "high", "low", "heart_rate"),
  940.                     null, null, null, null,
  941.                     "date || time ASC"
  942.                 ).use { cursor ->
  943.                     val list = mutableListOf<HealthRecord>()
  944.                     while (cursor.moveToNext()) {
  945.                         list.add(
  946.                             HealthRecord(
  947.                                 date = cursor.getString(0),
  948.                                 time = cursor.getString(1),
  949.                                 high = cursor.getInt(2),
  950.                                 low = cursor.getInt(3),
  951.                                 heartRate = cursor.getInt(4),
  952.                                 status = ""
  953.                             )
  954.                         )
  955.                     }
  956.                     list
  957.                 }
  958.             }
  959.             withContext(Dispatchers.Main) {
  960.                 records.clear()
  961.                 records.addAll(tempList)
  962.                 isLoading.value = false
  963.             }
  964.         }
  965.     }
  966.     // 加载指示器
  967.     if (isLoading.value) {
  968.         Box(
  969.             modifier = Modifier.fillMaxSize(),
  970.             contentAlignment = Alignment.Center
  971.         ) {
  972.             CircularProgressIndicator()
  973.         }
  974.         return
  975.     }
  976.     // 图表视图
  977.     AndroidView(
  978.         factory = { context ->
  979.             LineChart(context).apply {
  980.                 layoutParams = ViewGroup.LayoutParams(
  981.                     ViewGroup.LayoutParams.MATCH_PARENT,
  982.                     ViewGroup.LayoutParams.MATCH_PARENT
  983.                 )
  984.                 setTouchEnabled(true)
  985.                 setPinchZoom(true)
  986.                 description.isEnabled = false
  987.                 // X轴配置
  988.                 xAxis.apply {
  989.                     position = XAxis.XAxisPosition.BOTTOM
  990.                     granularity = 1f
  991.                     labelRotationAngle = -45f
  992.                     valueFormatter = object : ValueFormatter() {
  993.                         override fun getFormattedValue(value: Float): String {
  994.                             return records.getOrNull(value.toInt())?.let {
  995.                                 "${it.date}\n${it.time}"
  996.                             } ?: ""
  997.                         }
  998.                     }
  999.                     setLabelCount(5, true)
  1000.                 }
  1001.                 // Y轴配置
  1002.                 axisLeft.apply {
  1003.                     granularity = 20f
  1004.                     axisMinimum = 0f
  1005.                     axisMaximum = 220f
  1006.                     addLimitLine(LimitLine(90f, "收缩压阈值").apply {
  1007.                         lineColor = Color.Red.hashCode()
  1008.                         lineWidth = 2f
  1009.                         enableDashedLine(10f, 10f, 0f)
  1010.                     })
  1011.                     addLimitLine(LimitLine(140f, "舒张压阈值").apply {
  1012.                         lineColor = Color.Red.hashCode()
  1013.                         lineWidth = 2f
  1014.                         enableDashedLine(10f, 10f, 0f)
  1015.                     })
  1016.                 }
  1017.                 axisRight.isEnabled = false
  1018.                 legend.apply {
  1019.                     verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
  1020.                     horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER
  1021.                     orientation = Legend.LegendOrientation.HORIZONTAL
  1022.                     setDrawInside(false)
  1023.                     yOffset = 20f
  1024.                 }
  1025.             }
  1026.         },
  1027.         update = { chart ->
  1028.             try {
  1029.                 if (records.isNotEmpty()) {
  1030.                     val highEntries = mutableListOf<Entry>()
  1031.                     val lowEntries = mutableListOf<Entry>()
  1032.                     val heartEntries = mutableListOf<Entry>()
  1033.                     records.forEachIndexed { index, record ->
  1034.                         highEntries.add(Entry(index.toFloat(), record.high.toFloat()))
  1035.                         lowEntries.add(Entry(index.toFloat(), record.low.toFloat()))
  1036.                         heartEntries.add(Entry(index.toFloat(), record.heartRate.toFloat()))
  1037.                     }
  1038.                     val highSet = LineDataSet(highEntries, "收缩压").apply {
  1039.                         color = Color.Red.hashCode()
  1040.                         lineWidth = 2f
  1041.                         setDrawCircles(false)
  1042.                     }
  1043.                     val lowSet = LineDataSet(lowEntries, "舒张压").apply {
  1044.                         color = Color.Blue.hashCode()
  1045.                         lineWidth = 2f
  1046.                         setDrawCircles(false)
  1047.                     }
  1048.                     val heartSet = LineDataSet(heartEntries, "心率").apply {
  1049.                         color = Color.Green.hashCode()
  1050.                         lineWidth = 2f
  1051.                         setDrawCircles(false)
  1052.                     }
  1053.                     chart.data = LineData(highSet, lowSet, heartSet)
  1054.                     chart.animateY(1000)
  1055.                     chart.invalidate()
  1056.                     chart.setVisibleXRangeMaximum(7f)
  1057.                     chart.moveViewToX((records.size - 1).toFloat())
  1058.                 }
  1059.             } catch (e: Exception) {
  1060.                 Log.e("ChartUpdate", "更新图表失败: ${e.message}", e)
  1061.             }
  1062.         },
  1063.         modifier = Modifier
  1064.             .fillMaxSize()
  1065.             .padding(16.dp)
  1066.     )
  1067. }
复制代码
settings.gradle.kts 配置如下:
  1. pluginManagement {
  2.     repositories {
  3.         google {
  4.             content {
  5.                 includeGroupByRegex("com\\.android.*")
  6.                 includeGroupByRegex("com\\.google.*")
  7.                 includeGroupByRegex("androidx.*")
  8.             }
  9.         }
  10.         mavenCentral()
  11.         gradlePluginPortal()
  12.     }
  13. }
  14. dependencyResolutionManagement {
  15.     repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  16.     repositories {
  17.         google()
  18.         mavenCentral()
  19.         maven {
  20.             url = uri("https://jitpack.io")
  21.         }
  22.     }
  23. }
  24. rootProject.name = "MyNumSet"
  25. include(":app")
复制代码
build.gradle.kts配置如下:
  1. plugins {
  2.     alias(libs.plugins.android.application)
  3.     alias(libs.plugins.kotlin.android)
  4.     alias(libs.plugins.kotlin.compose)
  5. }
  6. android {
  7.     namespace = "com.example.mynumset"
  8.     compileSdk = 35
  9.     defaultConfig {
  10.         applicationId = "com.example.mynumset"
  11.         minSdk = 34
  12.         targetSdk = 35
  13.         versionCode = 1
  14.         versionName = "1.0"
  15.         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
  16.     }
  17.     buildTypes {
  18.         release {
  19.             isMinifyEnabled = false
  20.             proguardFiles(
  21.                 getDefaultProguardFile("proguard-android-optimize.txt"),
  22.                 "proguard-rules.pro"
  23.             )
  24.         }
  25.     }
  26.     compileOptions {
  27.         sourceCompatibility = JavaVersion.VERSION_11
  28.         targetCompatibility = JavaVersion.VERSION_11
  29.     }
  30.     kotlinOptions {
  31.         jvmTarget = "11"
  32.     }
  33.     buildFeatures {
  34.         compose = true
  35.     }
  36. }
  37. dependencies {
  38.     implementation ("com.github.PhilJay:MPAndroidChart:v3.1.0")
  39.     implementation ("androidx.datastore:datastore-preferences:1.0.0")
  40.     implementation("androidx.compose.foundation:foundation:1.4.0")
  41.     implementation(libs.androidx.core.ktx)
  42.     implementation(libs.androidx.lifecycle.runtime.ktx)
  43.     implementation(libs.androidx.activity.compose)
  44.     implementation(platform(libs.androidx.compose.bom))
  45.     implementation(libs.androidx.ui)
  46.     implementation(libs.androidx.ui.graphics)
  47.     implementation(libs.androidx.ui.tooling.preview)
  48.     implementation(libs.androidx.material3)
  49.     testImplementation(libs.junit)
  50.     androidTestImplementation(libs.androidx.junit)
  51.     androidTestImplementation(libs.androidx.espresso.core)
  52.     androidTestImplementation(platform(libs.androidx.compose.bom))
  53.     androidTestImplementation(libs.androidx.ui.test.junit4)
  54.     debugImplementation(libs.androidx.ui.tooling)
  55.     debugImplementation(libs.androidx.ui.test.manifest)
  56. }
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

星球的眼睛

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表