kotlin,Android,血压记录程序

复制代码
package com.example.mynumset

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
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.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.mynumset.ui.theme.MyNumSetTheme
import kotlinx.coroutines.launch
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.clickable
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.ui.window.Dialog
import java.time.Instant
import java.time.LocalDate
import java.time.LocalTime
import java.time.ZoneId
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.saveable.rememberSaveable
import kotlinx.coroutines.withContext
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import androidx.compose.ui.viewinterop.AndroidView
import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.components.LimitLine
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.res.painterResource

// 在原有类中添加数据库帮助类(放在MainActivity类外)
class RecordDbHelper(context: Context) : SQLiteOpenHelper(
    context, DATABASE_NAME, null, DATABASE_VERSION
) {
    init {
        // 添加连接池配置
        setWriteAheadLoggingEnabled(true)
        writableDatabase.enableWriteAheadLogging()
    }

    companion object {
        // 定义数据库的名称和版本
        private const val DATABASE_NAME = "health_records.db"
        private const val DATABASE_VERSION = 1
    }

    // 创建数据库表
    override fun onCreate(db: SQLiteDatabase) {
        // 执行SQL语句创建records表
        db.execSQL("""
            CREATE TABLE records (
                date TEXT NOT NULL,  
                time TEXT NOT NULL, 
                low INTEGER NOT NULL, 
                heart_rate INTEGER NOT NULL, 
                status TEXT NOT NULL, 
                PRIMARY KEY (date, time) 
            )
        """)
    }

    // 升级数据库
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 如果存在records表,则删除
        db.execSQL("DROP TABLE IF EXISTS records")
        // 重新创建数据库表
        onCreate(db)
    }
}
// 定义一个数据类 HealthRecord,用于存储健康记录信息
data class HealthRecord(
    // 日期字段,存储记录的日期,类型为 String
    val date: String,
    // 时间字段,存储记录的时间,类型为 String
    val time: String,
    // 高血压字段,存储高压值,类型为 Int
    val high: Int,
    // 低压字段,存储低压值,类型为 Int
    val low: Int,
    // 心率字段,存储心率值,类型为 Int
    val heartRate: Int,
    // 状态字段,存储健康状态描述,类型为 String
    val status: String
)

class MainActivity : ComponentActivity() {

    // 原色
    val originalGreen = Color(0xFFE8F5E9)

    // 加深方案(任选其一)
    val deepGreen1 = Color(0xFFC8E6C9)  // 浅灰绿(比原色深10%)
    val deepGreen2 = Color(0xFFA5D6A7)  // 中等青绿(Material Teal 200)
    val deepGreen3 = Color(0xFF80CBC4)  // 深青蓝色(Material Teal 300)
    val gradientColors = listOf(deepGreen2, deepGreen3)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyNumSetTheme {
                BottomNavigationExample()
            }
        }
    }
}

@Composable
fun NumberPicker(
    modifier: Modifier = Modifier,
    initialValue: Int = 0,
    range: IntRange = 20..200,
    onValueChange: (Int) -> Unit
) {
    val itemHeight = 50.dp
    val startOffset = (initialValue - range.first).coerceIn(0 until range.count())
    //Log.d("initialValue=", "读取成功: $initialValue")
    val listState = rememberLazyListState(initialFirstVisibleItemIndex = startOffset)
    val coroutineScope = rememberCoroutineScope()
    val itemHeightPx = with(LocalDensity.current) { itemHeight.toPx() }
    // 实时计算当前选中项索引
    val selectedIndex by remember {
        derivedStateOf {
            val offset = listState.firstVisibleItemScrollOffset
            val index = listState.firstVisibleItemIndex
            if (offset > itemHeightPx / 2) index + 1 else index
        }
    }
    // 当滚动停止时,吸附到中间项并回调
    LaunchedEffect(selectedIndex, listState.isScrollInProgress) {
        if (!listState.isScrollInProgress) {
            coroutineScope.launch {
                listState.animateScrollToItem(selectedIndex)
            }
            onValueChange((range.first + selectedIndex))
        }
    }

    LazyColumn(
        state = listState,
        modifier = modifier
            .height(itemHeight * 3)
            .width(100.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        contentPadding = PaddingValues(vertical = itemHeight),
        flingBehavior = rememberSnapFlingBehavior(lazyListState = listState)
    ) {
        items(range.count()) { index ->
            val value = range.first + index
            val isSelected = selectedIndex == index

            Text(
                text = value.toString(),
                fontSize = if (isSelected) 32.sp else 20.sp,
                fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
                color = if (isSelected) Color.Black else Color.Gray,
                modifier = Modifier
                    .height(itemHeight)
                    .fillMaxWidth(),
                textAlign = TextAlign.Center
            )
        }
    }
}

// 定义 DataStore 的扩展属性,用于访问应用的设置数据存储
val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(name = "my_settings")

// PreferencesKeys 是一个单例对象,用于定义 DataStore 的键
private object PreferencesKeys {
    val NumbGy = intPreferencesKey("saved_gaoya")
    val NumbDy = intPreferencesKey("saved_diya")
    val NumbXl = intPreferencesKey("saved_xinlv")
}
@Composable
fun NumberPickerDemo(dbHelper: RecordDbHelper) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope() // 新增协程作用域
    //val dbHelper = remember { RecordDbHelper(context) }
    val coroutineScope = rememberCoroutineScope()
    var currentStatusText by remember { mutableStateOf("") }
    var currentStatusColor by remember { mutableStateOf(Color.Black) }
    var lastClickTime by remember { mutableStateOf(0L) }
    // 添加两个新状态
    var selectedNumberGy by remember { mutableStateOf<Int?>(null) }
    var selectedNumberDy by remember { mutableStateOf<Int?>(null) }
    var selectedNumberXl by remember { mutableStateOf<Int?>(null) }
    // 记住是否显示日期选择器的状态
    var showDatePicker by remember { mutableStateOf(false) }
    // 记住是否显示时间选择器的状态
    var showTimePicker by remember { mutableStateOf(false) }
    // 记住当前选中的日期和时间
    var selectedDateTime by remember { mutableStateOf(LocalDateTime.now()) }
    // 日期格式化器
    val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.getDefault())
    // 时间格式化器
    val timeFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault())
    LaunchedEffect(Unit) {
        try {
            val preferences = context.settingsDataStore.data.first()
            selectedNumberGy = preferences[PreferencesKeys.NumbGy] ?: 100
            selectedNumberDy = preferences[PreferencesKeys.NumbDy] ?: 80
            selectedNumberXl = preferences[PreferencesKeys.NumbXl] ?: 75
        }
        catch (e: Exception) {
            Log.e("NumberPickerDemo", "读取设置失败", e)
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // 显示血压状态
            selectedNumberGy?.let { gy ->
                selectedNumberDy?.let { dy ->
                    val (statusText, statusColor) = run {
                        // 分别判断高压和低压的等级
                        val gyLevel = when {
                            gy < 90 -> 1
                            gy in 90 until 120 -> 2
                            gy in 120 until 140 -> 3
                            gy in 140 until 160 -> 4
                            gy in 160 until 180 -> 5
                            gy >= 180 -> 6
                            else -> 0
                        }
                        val dyLevel = when {
                            dy < 60 -> 1
                            dy in 60 until 80 -> 2
                            dy in 80 until 90 -> 3
                            dy in 90 until 100 -> 4
                            dy in 100 until 110 -> 5
                            dy >= 110 -> 6
                            else -> 0
                        }
                        // 取最高等级作为最终结果
                        when (maxOf(gyLevel, dyLevel)) {
                            1 -> "低血压" to Color.Blue
                            2 -> "正常血压" to Color(0xFF006400)
                            3 -> "正常高值血压" to Color(0xFFFFB300)
                            4 -> "1级高血压" to Color(0xFFFF6700)
                            5 -> "2级高血压" to Color.Red
                            6 -> "3级高血压" to Color.Red
                            else -> "****" to Color.Black
                        }
                    }.also {
                        currentStatusText = it.first
                        currentStatusColor = it.second
                    }
                    Card(
                        modifier = Modifier
                            .padding(top = 50.dp, bottom = 50.dp)
                            .size(width = 350.dp, height = 140.dp)
                            .background(
                                brush = Brush.verticalGradient(
                                    colors = listOf(
                                        Color.White.copy(alpha = 0.3f),
                                        Color.Transparent
                                    ),
                                    startY = 0f,
                                    endY = 350f
                                ),
                                shape = RoundedCornerShape(24.dp)
                            ),
                        shape = RoundedCornerShape(24.dp),
                        elevation = CardDefaults.cardElevation(4.dp)
                    ) {
                        val density = LocalDensity.current
                        Text(
                            text = statusText,
                            fontSize = 50.sp,
                            color = statusColor,
                            style = LocalTextStyle.current.copy(
                                shadow = with(density) {
                                    Shadow(
                                        color = Color.Black.copy(alpha = 0.3f),
                                        offset = Offset(2.dp.toPx(), 2.dp.toPx()),
                                        blurRadius = 4f
                                    )
                                }
                            ),
                            fontWeight = FontWeight.Bold,
                            modifier = Modifier
                                .fillMaxSize()
                                .padding(24.dp),
                            textAlign = TextAlign.Center
                        )
                    }

                }
            } ?: run {
                CircularProgressIndicator()
            }
            selectedNumberGy?.let { gy ->
                selectedNumberDy?.let { dy ->
                    selectedNumberXl?.let { xl ->
                        // 横向排列三个选择器
                        Row(
                            modifier = Modifier.fillMaxWidth(),
                            horizontalArrangement = Arrangement.SpaceEvenly
                        ) {
                            // 高压选择器
                            NumberPickerComponent(
                                value = gy,
                                label = "收缩压",
                                range = 80..200,
                                onSave = { value ->
                                    coroutineScope.launch {
                                        //delay(300)
                                        withContext(Dispatchers.Main) {
                                            selectedNumberGy = value
                                        }
                                        withContext(Dispatchers.IO) {
                                            context.settingsDataStore.edit { settings ->
                                                settings[PreferencesKeys.NumbGy] = value
                                                Log.d("NumberPickerDemo", "保存高压设置:$value")
                                            }
                                        }
                                    }
                                }
                            )
                            // 低压选择器
                            NumberPickerComponent(
                                value = dy,
                                label = "舒张压",
                                range = 50..150,
                                onSave = { value ->
                                    coroutineScope.launch {
                                        //delay(300)
                                        withContext(Dispatchers.Main) {
                                            selectedNumberDy = value
                                        }
                                        withContext(Dispatchers.IO) {
                                            context.settingsDataStore.edit { settings ->
                                                settings[PreferencesKeys.NumbDy] = value
                                                Log.d("NumberPickerDemo", "保存舒张压设置:$value")
                                            }
                                        }
                                    }
                                }
                            )
                            // 心率选择器
                            NumberPickerComponent(
                                value = xl,
                                label = "心率",
                                range = 40..200,
                                onSave = { value ->
                                    coroutineScope.launch {
                                        //delay(300)
                                        withContext(Dispatchers.Main) {
                                            selectedNumberXl = value
                                        }
                                        withContext(Dispatchers.IO) {
                                            context.settingsDataStore.edit { settings ->
                                                settings[PreferencesKeys.NumbXl] = value
                                                Log.d("NumberPickerDemo", "保存心率设置:$value")
                                            }
                                        }
                                    }
                                }
                            )
                        }
                        val saveLock = remember { Any() } // 添加同步锁对象
                        Button(
                            onClick = {
                                val now = System.currentTimeMillis()
                                if (now - lastClickTime > 1000) {
                                    val date = selectedDateTime.format(dateFormatter)
                                    val time = selectedDateTime.format(timeFormatter)
                                    val high = selectedNumberGy ?: return@Button
                                    val low = selectedNumberDy ?: return@Button
                                    val heartRate = selectedNumberXl ?: return@Button
                                    val status = currentStatusText
                                    lastClickTime = now
                                    synchronized(saveLock) {
                                        scope.launch(Dispatchers.IO){
                                            try {
                                                dbHelper.writableDatabase.use { db ->
                                                    val values = ContentValues().apply {
                                                        put("date", date)
                                                        put("time", time)
                                                        put("high", high)
                                                        put("low", low)
                                                        put("heart_rate", heartRate)
                                                        put("status", status)
                                                    }
                                                    db.insertWithOnConflict(
                                                        "records",
                                                        null,
                                                        values,
                                                        SQLiteDatabase.CONFLICT_REPLACE
                                                    )
                                                }
                                                withContext(Dispatchers.Main) {
                                                    //delay(300)
                                                    Toast.makeText(context, "记录保存成功", Toast.LENGTH_SHORT).show()
                                                }
                                            } catch (e: Exception) {
                                                Log.e("Database", "保存失败", e)
                                            }
                                        }
                                    }
                                }
                            },
                            modifier = Modifier
                                .padding(vertical = 8.dp)
                                .width(200.dp)    // 加长按钮
                                .height(48.dp),   // 增加高度
                            shape = RoundedCornerShape(8.dp),
                            colors = ButtonDefaults.buttonColors(
                                containerColor = Color(0xFF006400), // 使用深绿色配色
                                contentColor = Color.White
                            )
                        ) {
                            Text(
                                "记 录",
                                fontSize = 22.sp,  // 加大字号
                                fontWeight = FontWeight.Medium
                            )
                        }
                        Box(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(16.dp)
                                .background(Color(0xFFE0F2F1), RoundedCornerShape(8.dp))
                                .padding(16.dp)
                        ) {
                            Text(
                                text = when (currentStatusText) {
                                    "低血压" -> "收缩压<90,舒张压<60 ,一般无需治疗,若出现头晕、乏力等症状,建议就医。"
                                    "正常血压" -> "90≤收缩压<120,60≤舒张压<80 ,无需治疗,保持健康生活方式(如低盐饮食、适量运动、戒烟限酒)。"
                                    "正常高值血压" -> "120≤收缩压<140,80≤舒张压<90 ,注意饮食、运动和定期监测血压。建议生活方式干预,必要时咨询医生。"
                                    "1级高血压" -> "140≤收缩压<160,90≤舒张压<100 ,改变生活方式(如减少盐摄入、增加运动),可能需要药物治疗。"
                                    "2级高血压" -> "160≤收缩压<180,100≤舒张压<110 ,需要药物治疗并结合生活方式干预(如饮食调整、运动、减重)。"
                                    "3级高血压" -> "180≤收缩压,110≤舒张压, 需紧急医疗干预,可能需要住院治疗。通常需要联合药物治疗。"
                                    else -> ""
                                },
                                color = Color.Black,
                                fontSize = 20.sp
                            )
                        }
                    }
                }
            } ?: run {
                CircularProgressIndicator()
            }
        // 显示日期和时间选择器
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ){
            Text(
                text = "日期: ${selectedDateTime.format(dateFormatter)}",
                style = MaterialTheme.typography.bodyLarge,
                fontSize = 20.sp, // 增大字体尺寸
                //fontWeight = FontWeight.Bold, // 加粗字体
                color = Color.DarkGray, // 使用更深的颜色
                modifier = Modifier
                    .clickable { showDatePicker = true }
                    .padding(8.dp)
            )
            Text(
                text = "时间: ${selectedDateTime.format(timeFormatter)}",
                style = MaterialTheme.typography.bodyLarge,
                fontSize = 20.sp, // 增大字体尺寸
                //fontWeight = FontWeight.Bold, // 加粗字体
                color = Color.DarkGray, // 使用更深的颜色
                modifier = Modifier
                    .clickable { showTimePicker = true }
                    .padding(8.dp)
            )
            // 日期选择器
            if (showDatePicker) {
                DatePickerDialog(
                    onDismissRequest = { showDatePicker = false }, // 点击外部时关闭日期选择器
                    onDateSelected = { date ->
                        // 更新选中的日期
                        selectedDateTime = selectedDateTime.withYear(date.year).withMonth(date.monthValue).withDayOfMonth(date.dayOfMonth)
                        showDatePicker = false // 关闭日期选择器
                    }
                )
            }
            // 时间选择器
            if (showTimePicker) {
                TimePickerDialog(
                    onDismissRequest = { showTimePicker = false }, // 点击外部时关闭时间选择器
                    onTimeSelected = { time ->
                        // 更新选中的时间
                        selectedDateTime = selectedDateTime.withHour(time.hour).withMinute(time.minute)
                        showTimePicker = false // 关闭时间选择器
                    }
                )
            }
        }
    }
}

@Composable
private fun NumberPickerComponent(
    value: Int,
    label: String,
    range: IntRange,
    onSave: (Int) -> Unit
) {
    var currentValue by remember(value) { mutableStateOf(value) } // 添加当前值状态

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(label, fontSize = 16.sp)
        NumberPicker(
            initialValue = value,
            range = range,
            onValueChange = { newValue ->
                if (newValue != currentValue) { // 只在数值变化时触发保存
                    currentValue = newValue
                    onSave(newValue)
                }
            }
        )
    }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DatePickerDialog(
    onDismissRequest: () -> Unit,
    onDateSelected: (LocalDate) -> Unit
) {
    // 获取当前日期
    val currentDate = LocalDate.now()
    // 创建DatePicker状态,并初始化为当前日期
    val datePickerState = rememberDatePickerState(initialSelectedDateMillis = currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli())
    // 创建对话框
    Dialog(
        onDismissRequest = onDismissRequest,
    ) {
        // 使用Column布局来组织对话框内容
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        ) {
            // 显示标题
            Text(
                text = "Select Date",
                style = MaterialTheme.typography.headlineSmall,
                modifier = Modifier.padding(bottom = 16.dp)
            )
            // 显示日期选择器
            DatePicker(state = datePickerState)
            // 使用Row布局来组织按钮
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 16.dp),
                horizontalArrangement = Arrangement.End
            ) {
                // 取消按钮
                Button(
                    onClick = onDismissRequest,
                    modifier = Modifier.padding(end = 8.dp)
                ) {
                    Text("Cancel")
                }
                // 确定按钮
                Button(onClick = {
                    // 获取选中的日期
                    val selectedDate = LocalDate.ofInstant(
                        Instant.ofEpochMilli(datePickerState.selectedDateMillis ?: currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()),
                        ZoneId.systemDefault()
                    )
                    // 调用回调函数,传递选中的日期
                    onDateSelected(selectedDate)
                }) {
                    Text("OK")
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class) // 使用实验性API的注解
@Composable // 标记为可组合函数
fun TimePickerDialog(
    onDismissRequest: () -> Unit, // 对话框关闭时的回调函数
    onTimeSelected: (LocalTime) -> Unit // 时间选择后的回调函数
) {
    val currentTime = LocalTime.now(ZoneId.systemDefault()) // 获取当前时间
    val timePickerState = rememberTimePickerState(initialHour = currentTime.hour, initialMinute = currentTime.minute) // 创建时间选择器的状态,并初始化为当前时间
    Dialog(
        onDismissRequest = onDismissRequest, // 设置对话框关闭的回调函数
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth() // 填充父布局的宽度
                .padding(16.dp) // 设置内边距
        ) {
            Text(
                text = "Select Time", // 显示文本
                style = MaterialTheme.typography.headlineSmall, // 使用主题中的小标题样式
                modifier = Modifier.padding(bottom = 16.dp) // 设置底部内边距
            )
            TimePicker(state = timePickerState) // 显示时间选择器
            Row(
                modifier = Modifier
                    .fillMaxWidth() // 填充父布局的宽度
                    .padding(top = 16.dp), // 设置顶部内边距
                horizontalArrangement = Arrangement.End // 水平排列方式为靠右
            ) {
                Button(
                    onClick = onDismissRequest, // 点击按钮时调用关闭回调函数
                    modifier = Modifier.padding(end = 8.dp) // 设置右边距
                ) {
                    Text("Cancel") // 显示取消文本
                }
                Button(onClick = {
                    val selectedTime = LocalTime.of(timePickerState.hour, timePickerState.minute) // 获取选中的时间
                    onTimeSelected(selectedTime) // 调用时间选择后的回调函数
                }) {
                    Text("OK") // 显示确定文本
                }
            }
        }
    }
}

@Composable
fun BottomNavigationExample() {
    val context = LocalContext.current
    val activity = context as Activity // 直接转换为 Activity
    val dbHelper = remember { RecordDbHelper(context) }
    var selectedItem by rememberSaveable { mutableStateOf(0) }
    val historyStack = remember { mutableStateListOf<Int>() }
    val originalGreen = Color(0xFFE8F5E9)
    // 加深方案(任选其一)
    val deepGreen1 = Color(0xFFC8E6C9)  // 浅灰绿(比原色深10%)
    val deepGreen2 = Color(0xFFA5D6A7)  // 中等青绿(Material Teal 200)
    val deepGreen3 = Color(0xFF80CBC4)  // 深青蓝色(Material Teal 300)
    val gradientColors = listOf(deepGreen2, deepGreen3)
    // 处理返回键
    BackHandler(enabled = historyStack.isNotEmpty()) {
        if (historyStack.isNotEmpty()) {
            // 兼容性写法(支持所有 Kotlin 版本)
            val lastIndex = historyStack.size - 1
            selectedItem = historyStack.removeAt(lastIndex)
        } else {
            activity.finish() // 正确调用 Activity 的 finish()
        }
    }
    // 点击底部导航项时更新历史栈
    fun onTabSelected(index: Int) {
        historyStack.add(selectedItem)
        selectedItem = index
    }
    val items = listOf("记录", "历史", "图表")
    // 使用Scaffold布局,包含底部导航栏
    Scaffold(
        bottomBar = {
            // 创建底部导航栏
            NavigationBar(
                modifier = Modifier
                    //.height(72.dp) // 保持高度设置
                    .padding(horizontal = 8.dp)
                    .background(
                        brush = Brush.linearGradient(
                            colors = gradientColors,
                            start = Offset(0f, 0f),
                            end = Offset(1000f, 0f)
                        ),
                        alpha = 0.9f  // 添加背景透明度
                    ),
                containerColor = Color.Transparent
            ) {
                // 遍历items列表,为每个项创建一个NavigationBarItem
                items.forEachIndexed { index, item ->
                    NavigationBarItem(
                        // 根据index选择不同的图标
                        icon = {
                            Icon(
                                painter = painterResource(id = when (index) {
                                    0 -> R.drawable.write  // 修正资源引用(去掉_png后缀)
                                    1 -> R.drawable.list // 修正资源引用(去掉_png后缀)
                                    else -> R.drawable.chart2
                                }),
                                contentDescription = item,
                                modifier = Modifier
                                    .size(48.dp),
                                    //.background(Color.Red)
                                tint = Color.Unspecified
                            )
                        },
                        // 显示项的标签
                        label = { Text(
                            item,
                            fontSize = 12.sp,
                            color = Color.Black,  // 强制文字颜色为白色
                            modifier = Modifier.padding(top = 0.dp)
                        //modifier = Modifier.padding(vertical = 2.dp)
                        )  },
                        // 判断当前项是否被选中
                        selected = selectedItem == index,
                        onClick = { onTabSelected(index) },
                        colors = NavigationBarItemDefaults.colors(
                            selectedIconColor = Color.White,
                            indicatorColor = Color(0xFF006400).copy(alpha = 0.5f)
                        )
                    )
                }
            }
        }
    ) { innerPadding ->
        // 根据选中的项显示不同的内容
        when (selectedItem) {
            0 -> Box(modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = Brush.verticalGradient(
                        colors = gradientColors,
                        startY = 0f,
                        endY = 1200f
                    )
                )
                .padding(innerPadding)  // 添加内边距
            ) {
                NumberPickerDemo(dbHelper = dbHelper)
            }
            1 -> Box(modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = Brush.verticalGradient(  // 历史界面也改为渐变背景
                        colors = gradientColors,
                        startY = 0f,
                        endY = 1200f
                    )
                )
                .padding(top = 20.dp)  // 新增顶部内边距
                .padding(innerPadding)  // 添加内边距
            ){
                HistoryScreen(dbHelper = dbHelper)
            }
            2 -> Box(modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = Brush.verticalGradient(  // 统计界面改为渐变背景
                        colors = gradientColors,
                        startY = 0f,
                        endY = 1200f
                    )
                )
                .padding(innerPadding)  // 添加内边距
            ){
                StatisticsScreen(dbHelper = dbHelper)
            }
        }
    }
}

@Composable
fun HistoryScreen(dbHelper: RecordDbHelper) {
    val records = remember { mutableStateListOf<HealthRecord>() }
    var showDeleteDialog by remember { mutableStateOf(false) }
    var selectedRecord by remember { mutableStateOf<HealthRecord?>(null) }
    val scope = rememberCoroutineScope()
    // 加载数据库记录
    LaunchedEffect(Unit) {
        scope.launch(Dispatchers.IO) {
            try {
                val loadedRecords = mutableListOf<HealthRecord>()
                dbHelper.readableDatabase.use { db ->
                    val cursor = db.query(
                        "records",
                        arrayOf("date", "time", "high", "low", "heart_rate", "status"),
                        null, null, null, null,
                        "date DESC, time DESC"
                    )
                    try {
                        while (cursor.moveToNext()) {
                            val date = cursor.getString(0) ?: ""
                            val time = cursor.getString(1) ?: ""
                            val high = cursor.getInt(2)
                            val low = cursor.getInt(3)
                            val heartRate = cursor.getInt(4)
                            val status = cursor.getString(5) ?: "未知"

                            loadedRecords.add(
                                HealthRecord(date, time, high, low, heartRate, status)
                            )
                        }
                    } finally {
                        cursor.close()
                    }
                }
                withContext(Dispatchers.Main) {
                    records.clear()
                    records.addAll(loadedRecords)
                }
            } catch (e: Exception) {
                Log.e("HistoryScreen", "加载记录失败: ${e.message}", e)
            }
        }
    }
    // 删除确认对话框
    if (showDeleteDialog) {
        AlertDialog(
            onDismissRequest = { showDeleteDialog = false },
            title = { Text("删除记录") },
            text = { Text("确定要删除这条记录吗?") },
            confirmButton = {
                TextButton(
                    onClick = {
                        selectedRecord?.let { record ->
                            scope.launch(Dispatchers.IO) {
                                try {
                                    dbHelper.writableDatabase.delete(
                                        "records",
                                        "date = ? AND time = ?",
                                        arrayOf(record.date, record.time)
                                    )
                                    withContext(Dispatchers.Main) {
                                        records.remove(record)
                                        showDeleteDialog = false
                                    }
                                } catch (e: Exception) {
                                    Log.e("HistoryScreen", "删除失败: ${e.message}", e)
                                }
                            }
                        }
                    }
                ) { Text("确认") }
            },
            dismissButton = {
                TextButton(
                    onClick = { showDeleteDialog = false }
                ) { Text("取消") }
            }
        )
    }
    // 记录列表
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        items(
            count = records.size,
            key = { index -> "${records[index].date}_${records[index].time}" }
        ) { index ->
            val record = records[index]
            Card(
                modifier = Modifier
                    .fillMaxWidth(0.9f)
                    .padding(vertical = 2.dp)
                    .clickable {
                        selectedRecord = record
                        showDeleteDialog = true
                    },
                elevation = CardDefaults.cardElevation(4.dp),
                colors = CardDefaults.cardColors(
                    containerColor = when (record.status) {
                        "低血压" -> Color(0xFFE3F2FD)
                        "正常血压" -> Color(0xFFE8F5E9)
                        "正常高值血压" -> Color(0xFFFFF8E1)
                        "1级高血压" -> Color(0xFFFFF3E0)
                        "2级高血压", "3级高血压" -> Color(0xFFFFEBEE)
                        else -> Color.White
                    }
                )
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(top = 16.dp)
                    ) {
                        Text(
                            "日期: ${record.date} ${record.time}",
                            fontSize = 16.sp,
                            modifier = Modifier.weight(1f)
                        )
                        Text(
                            "状态: ${record.status}",
                            color = when (record.status) {
                                "低血压" -> Color.Blue
                                "正常血压" -> Color(0xFF006400)
                                "正常高值血压" -> Color(0xFFFFB300)
                                "1级高血压" -> Color(0xFFFF6700)
                                "2级高血压", "3级高血压" -> Color.Red
                                else -> Color.Gray
                            },
                            fontSize = 18.sp
                        )
                    }
                    Row(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(top = 16.dp)
                    ) {
                        Text(
                            "血压: ${record.high}/${record.low} mmHg",
                            fontSize = 18.sp,
                            modifier = Modifier.weight(1f)
                        )
                        Text(
                            "心率: ${record.heartRate} 次/分钟",
                            fontSize = 18.sp
                        )
                    }
                }
            }
        }
    }
}
@Composable
fun StatisticsScreen(dbHelper: RecordDbHelper) {
    val context = LocalContext.current
    val records = remember { mutableStateListOf<HealthRecord>() }
    val isLoading = remember { mutableStateOf(true) }
    // 加载数据
    LaunchedEffect(Unit) {
        withContext(Dispatchers.IO) {
            val tempList = dbHelper.readableDatabase.use { db ->
                db.query(
                    "records",
                    arrayOf("date", "time", "high", "low", "heart_rate"),
                    null, null, null, null,
                    "date || time ASC"
                ).use { cursor ->
                    val list = mutableListOf<HealthRecord>()
                    while (cursor.moveToNext()) {
                        list.add(
                            HealthRecord(
                                date = cursor.getString(0),
                                time = cursor.getString(1),
                                high = cursor.getInt(2),
                                low = cursor.getInt(3),
                                heartRate = cursor.getInt(4),
                                status = ""
                            )
                        )
                    }
                    list
                }
            }
            withContext(Dispatchers.Main) {
                records.clear()
                records.addAll(tempList)
                isLoading.value = false
            }
        }
    }
    // 加载指示器
    if (isLoading.value) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            CircularProgressIndicator()
        }
        return
    }
    // 图表视图
    AndroidView(
        factory = { context ->
            LineChart(context).apply {
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                setTouchEnabled(true)
                setPinchZoom(true)
                description.isEnabled = false
                // X轴配置
                xAxis.apply {
                    position = XAxis.XAxisPosition.BOTTOM
                    granularity = 1f
                    labelRotationAngle = -45f
                    valueFormatter = object : ValueFormatter() {
                        override fun getFormattedValue(value: Float): String {
                            return records.getOrNull(value.toInt())?.let {
                                "${it.date}\n${it.time}"
                            } ?: ""
                        }
                    }
                    setLabelCount(5, true)
                }
                // Y轴配置
                axisLeft.apply {
                    granularity = 20f
                    axisMinimum = 0f
                    axisMaximum = 220f
                    addLimitLine(LimitLine(90f, "收缩压阈值").apply {
                        lineColor = Color.Red.hashCode()
                        lineWidth = 2f
                        enableDashedLine(10f, 10f, 0f)
                    })
                    addLimitLine(LimitLine(140f, "舒张压阈值").apply {
                        lineColor = Color.Red.hashCode()
                        lineWidth = 2f
                        enableDashedLine(10f, 10f, 0f)
                    })
                }
                axisRight.isEnabled = false
                legend.apply {
                    verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
                    horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER
                    orientation = Legend.LegendOrientation.HORIZONTAL
                    setDrawInside(false)
                    yOffset = 20f
                }
            }
        },
        update = { chart ->
            try {
                if (records.isNotEmpty()) {
                    val highEntries = mutableListOf<Entry>()
                    val lowEntries = mutableListOf<Entry>()
                    val heartEntries = mutableListOf<Entry>()
                    records.forEachIndexed { index, record ->
                        highEntries.add(Entry(index.toFloat(), record.high.toFloat()))
                        lowEntries.add(Entry(index.toFloat(), record.low.toFloat()))
                        heartEntries.add(Entry(index.toFloat(), record.heartRate.toFloat()))
                    }
                    val highSet = LineDataSet(highEntries, "收缩压").apply {
                        color = Color.Red.hashCode()
                        lineWidth = 2f
                        setDrawCircles(false)
                    }
                    val lowSet = LineDataSet(lowEntries, "舒张压").apply {
                        color = Color.Blue.hashCode()
                        lineWidth = 2f
                        setDrawCircles(false)
                    }
                    val heartSet = LineDataSet(heartEntries, "心率").apply {
                        color = Color.Green.hashCode()
                        lineWidth = 2f
                        setDrawCircles(false)
                    }
                    chart.data = LineData(highSet, lowSet, heartSet)
                    chart.animateY(1000)
                    chart.invalidate()
                    chart.setVisibleXRangeMaximum(7f)
                    chart.moveViewToX((records.size - 1).toFloat())
                }
            } catch (e: Exception) {
                Log.e("ChartUpdate", "更新图表失败: ${e.message}", e)
            }
        },
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    )
}

settings.gradle.kts 配置如下:

复制代码
pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://jitpack.io")
        }
    }
}

rootProject.name = "MyNumSet"
include(":app")

build.gradle.kts配置如下:

复制代码
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
}

android {
    namespace = "com.example.mynumset"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.example.mynumset"
        minSdk = 34
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
    buildFeatures {
        compose = true
    }
}

dependencies {

    implementation ("com.github.PhilJay:MPAndroidChart:v3.1.0")
    implementation ("androidx.datastore:datastore-preferences:1.0.0")
    implementation("androidx.compose.foundation:foundation:1.4.0")
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
}
相关推荐
weixin_307779133 分钟前
实现Azure Function安全地请求企业内部API返回数据
开发语言·python·云计算·azure
无敌的牛6 分钟前
list容器介绍及模拟实现和与vector比较
开发语言·数据结构·list
小菜刀刀12 分钟前
XSS跨站脚本攻击漏洞
开发语言·前端·javascript
Leon-zy21 分钟前
【Python笔记 01】变量、标识符
开发语言·python
想不明白的过度思考者28 分钟前
Java从入门到“放弃”(精通)之旅——类和对象全面解析⑦
java·开发语言
顾林海1 小时前
深度解析CopyWriteArrayList工作原理
android·java·面试
薯条不要番茄酱1 小时前
【网络编程】从零开始彻底了解网络编程(二)
开发语言·网络·php
pengyu1 小时前
【Flutter 状态管理 - 伍】 | 万字长文解锁你对观察者模式的认知
android·flutter·dart
西岭千秋雪_1 小时前
Nacos配置中心客户端处理服务端配置信息源码解析
java·开发语言·分布式·spring·微服务·中间件
11111111in1 小时前
PHP伪协议读取文件
开发语言·php