只记录体重版本完成,UI优化,功能添加待后期更新

dev
rayc 2024-03-04 17:31:09 +08:00
parent 52289ce9e0
commit ffd84a661c
7 changed files with 194 additions and 106 deletions

View File

@ -5,6 +5,7 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/local/Cellar/gradle/8.4/libexec" />
<option name="gradleJvm" value="17" /> <option name="gradleJvm" value="17" />
<option name="modules"> <option name="modules">
<set> <set>

View File

@ -78,4 +78,7 @@ dependencies {
// view model // view model
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
implementation("com.google.accompanist:accompanist-systemuicontroller:0.27.0")
} }

View File

@ -7,9 +7,8 @@
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@drawable/weight"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.WeightTrack" android:theme="@style/Theme.WeightTrack"
tools:targetApi="31"> tools:targetApi="31">

View File

@ -24,11 +24,15 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerFormatter
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@ -37,6 +41,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -48,18 +53,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.eacenic.weighttrack.ui.theme.WeightTrackTheme import com.eacenic.weighttrack.ui.theme.WeightTrackTheme
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.Instant import java.time.Instant
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Date
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@ -88,9 +94,18 @@ private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
fun WeightTrackLayout( fun WeightTrackLayout(
weightTrackViewModel: WeightTrackViewModel = viewModel() weightTrackViewModel: WeightTrackViewModel = viewModel()
) { ) {
val systemUIController = rememberSystemUiController()
LaunchedEffect(Unit) {
systemUIController.setStatusBarColor(Color(0xFF424242))
}
val weightRecords by weightTrackViewModel.historyData.collectAsState() val weightRecords by weightTrackViewModel.historyData.collectAsState()
val newestRecord by weightTrackViewModel.newestRecord.collectAsState()
var openBottomSheet by rememberSaveable { mutableStateOf(false) } var openBottomSheet by rememberSaveable { mutableStateOf(false) }
var curRecord by rememberSaveable { mutableStateOf<WeightRecord?>(null) }
weightTrackViewModel.loadWeightRecords() weightTrackViewModel.loadWeightRecords()
@ -101,58 +116,77 @@ fun WeightTrackLayout(
Column( Column(
modifier = Modifier.padding(horizontal = 20.dp) modifier = Modifier.padding(horizontal = 20.dp)
) { ) {
CurrentWeight(62.6f, Modifier.padding(top = 20.dp)) CurrentWeight(newestRecord, Modifier.padding(top = 20.dp))
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxHeight(0.9f) modifier = Modifier.fillMaxHeight(0.9f)
) { ) {
this.items(weightRecords) { this.items(weightRecords) { it ->
// Text(text = "${it.weight}") // Text(text = "${it.weight}")
WeightRecordItem(weightRecord = it) WeightRecordItem(
weightRecord = it,
onClickEdit = { record ->
curRecord = record
openBottomSheet = true
}
)
} }
} }
EditButton(onClick = { openBottomSheet = true }) EditButton(onClick = { openBottomSheet = true })
} }
BottomSheetEditor( if (openBottomSheet) {
expandBottomSheet = openBottomSheet, BottomSheetEditor(
onDismiss = { onDismiss = {
openBottomSheet = false openBottomSheet = false
} curRecord = null
) },
curRecord = curRecord
)
}
} }
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
@Composable @Composable
fun WeightRecordItem(weightRecord: WeightRecord) { fun WeightRecordItem(
Box( weightRecord: WeightRecord,
onClickEdit: (curRecord: WeightRecord) -> Unit
) {
Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(vertical = 5.dp) .padding(vertical = 5.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = "${weightRecord.weight}", text = "${weightRecord.weight}",
modifier = Modifier.align(Alignment.CenterStart),
fontFamily = doodleShadowFontFamily, fontFamily = doodleShadowFontFamily,
fontSize = 36.sp, fontSize = 36.sp,
modifier = Modifier.weight(1f)
) )
Text( Text(
text = dateTimeFormatter.format(weightRecord.date), text = dateTimeFormatter.format(weightRecord.date),
modifier = Modifier.align(Alignment.CenterEnd),
fontFamily = doodleShadowFontFamily, fontFamily = doodleShadowFontFamily,
fontSize = 18.sp fontSize = 18.sp,
modifier = Modifier.padding(end = 20.dp)
) )
IconButton(
onClick = { onClickEdit(weightRecord) },
modifier = Modifier.padding(horizontal = 5.dp)
) {
Icon(Icons.Filled.Edit, contentDescription = "edit")
}
} }
} }
@Composable @Composable
fun CurrentWeight( fun CurrentWeight(
currentWeight: Float? = null, curRecord: WeightRecord? = null,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Row ( Row(
modifier = modifier modifier = modifier
.wrapContentHeight() .wrapContentHeight()
.fillMaxWidth(), .fillMaxWidth(),
@ -163,13 +197,14 @@ fun CurrentWeight(
.fillMaxWidth() .fillMaxWidth()
.height(120.dp) .height(120.dp)
.clip(RoundedCornerShape(10.dp)) .clip(RoundedCornerShape(10.dp))
.background(Color.Cyan) .background(Color(0xFF424242))
) { ) {
Text( Text(
text = "${currentWeight ?: "No Data"}", text = "${curRecord?.weight ?: "No Data"}",
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
fontSize = 80.sp, fontSize = 80.sp,
color = Color.White,
fontFamily = doodleShadowFontFamily fontFamily = doodleShadowFontFamily
) )
} }
@ -192,10 +227,15 @@ fun EditButton(
.height(48.dp) .height(48.dp)
.fillMaxWidth(), .fillMaxWidth(),
shape = RoundedCornerShape(5.dp), shape = RoundedCornerShape(5.dp),
colors = ButtonDefaults.buttonColors(Color.Cyan), colors = ButtonDefaults.buttonColors(Color(0xFF424242)),
onClick = onClick, onClick = onClick,
) { ) {
Text(text = "ADD", color = Color.Black, fontSize = 36.sp, fontFamily = doodleShadowFontFamily) Text(
text = "ADD",
color = Color.White,
fontSize = 36.sp,
fontFamily = doodleShadowFontFamily
)
} }
} }
} }
@ -204,98 +244,116 @@ fun EditButton(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BottomSheetEditor( fun BottomSheetEditor(
expandBottomSheet: Boolean = false,
onDismiss: () -> Unit, onDismiss: () -> Unit,
weightTrackViewModel: WeightTrackViewModel = viewModel() weightTrackViewModel: WeightTrackViewModel = viewModel(),
curRecord: WeightRecord? = null
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val bottomSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var text by remember { mutableStateOf("") } var text by remember { mutableStateOf("${curRecord?.weight ?: ""}") }
var clickable by remember { mutableStateOf(false) } var clickable = text.isNotEmpty()
val datePickerState = rememberDatePickerState()
if (expandBottomSheet) { val datePickerState = rememberDatePickerState(
ModalBottomSheet( initialSelectedDateMillis = curRecord?.date?.toInstant(ZoneOffset.UTC)?.toEpochMilli()
onDismissRequest = onDismiss, )
sheetState = bottomSheetState
ModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = bottomSheetState
) {
Column(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
) { ) {
Column( Row(
modifier = Modifier modifier = Modifier
.wrapContentHeight()
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 20.dp)
) { ) {
Row( OutlinedTextField(
modifier = Modifier value = text,
.fillMaxWidth() modifier = Modifier.fillMaxWidth(),
.padding(horizontal = 20.dp) onValueChange = {
) { val newVal = it.toFloatOrNull()
OutlinedTextField( if ((it.isNotEmpty() && newVal == null) || (newVal != null && newVal > 1000f)) {
value = text, Toast.makeText(
modifier = Modifier.fillMaxWidth(), context,
onValueChange = { "只能输入数字且要求数字小于1000",
val newVal = it.toFloatOrNull() Toast.LENGTH_SHORT
if ((it.isNotEmpty() && newVal == null) || (newVal !=null && newVal > 1000f)) { ).show()
Toast.makeText(context, "只能输入数字且要求数字小于1000", Toast.LENGTH_SHORT).show() return@OutlinedTextField
return@OutlinedTextField }
} if (it.length > 6) {
text = it Toast.makeText(
clickable = text.isNotEmpty() context,
}, "数字长度不能超过6位",
) Toast.LENGTH_SHORT
} ).show()
Spacer(modifier = Modifier return@OutlinedTextField
}
text = it
clickable = text.isNotEmpty()
},
)
}
Spacer(
modifier = Modifier
.height(20.dp) .height(20.dp)
.fillMaxWidth()) .fillMaxWidth()
)
Row(
modifier = Modifier.fillMaxWidth() Row(
) { modifier = Modifier.fillMaxWidth()
DatePicker( ) {
state = datePickerState, DatePicker(
modifier = Modifier.padding(16.dp), state = datePickerState,
title = { Text(text="称重日期") }, modifier = Modifier.padding(16.dp),
) title = { Text(text = "称重日期") },
} )
}
Row(
horizontalArrangement = Arrangement.Center, Row(
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth() verticalAlignment = Alignment.CenterVertically,
) { modifier = Modifier.fillMaxWidth()
Button( ) {
onClick = { Button(
scope.launch { bottomSheetState.hide() }.invokeOnCompletion { onClick = {
if (!bottomSheetState.isVisible) { scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
onDismiss() if (!bottomSheetState.isVisible) {
} onDismiss()
} }
} }
) {
Text(text = "CANCEL", fontSize = 18.sp, fontFamily = doodleShadowFontFamily)
}
Spacer(modifier = Modifier.size(60.dp))
Button(
onClick = {
Toast.makeText(context, "save weight", Toast.LENGTH_SHORT).show()
Log.e(TAG, "BottomSheetEditor: ${Date(datePickerState.selectedDateMillis!!)}")
weightTrackViewModel.addWeightRecord(WeightRecord(weight = text.toFloat(), date = LocalDateTime.now()))
text = ""
onDismiss()
},
enabled = clickable
) {
Text(text = "CONFIRM", fontSize = 18.sp, fontFamily = doodleShadowFontFamily)
} }
) {
Text(text = "CANCEL", fontSize = 18.sp, fontFamily = doodleShadowFontFamily)
} }
Spacer(modifier = Modifier.size(60.dp))
Button(
onClick = {
val newDate = LocalDateTime.ofInstant(
Instant.ofEpochMilli(datePickerState.selectedDateMillis!!),
ZoneId.systemDefault()
)
val newRecord = curRecord?.copy(weight = text.toFloat(), date = newDate)
?: WeightRecord(weight = text.toFloat(), date = newDate)
weightTrackViewModel.addWeightRecord(newRecord)
text = ""
onDismiss()
},
enabled = clickable
) {
Text(text = "CONFIRM", fontSize = 18.sp, fontFamily = doodleShadowFontFamily)
Spacer(modifier = Modifier.size(40.dp)) }
} }
Spacer(modifier = Modifier.size(40.dp))
} }
} }

View File

@ -1,34 +1,31 @@
package com.eacenic.weighttrack package com.eacenic.weighttrack
import android.os.Build import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.Arrays
const val TAG = "WeightTrackViewModel" const val TAG = "WeightTrackViewModel"
class WeightTrackViewModel: ViewModel() { class WeightTrackViewModel: ViewModel() {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private var _historyData = MutableStateFlow<List<WeightRecord>>(mutableListOf<WeightRecord>( private var _historyData = MutableStateFlow<List<WeightRecord>>(mutableListOf<WeightRecord>())
WeightRecord(weight = 62.6f, date = LocalDateTime.now()),
WeightRecord(weight = 63.6f, date = LocalDateTime.now()),
WeightRecord(weight = 65.6f, date = LocalDateTime.now())
))
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
val historyData: StateFlow<List<WeightRecord>> = _historyData val historyData: StateFlow<List<WeightRecord>> = _historyData
private var _curRecord = MutableStateFlow<WeightRecord?>(null)
val newestRecord = _curRecord
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun addWeightRecord(newRecord: WeightRecord) { fun addWeightRecord(newRecord: WeightRecord) {
saveWeightRecord(newRecord) saveWeightRecord(newRecord)
loadWeightRecords() loadWeightRecords()
} }
private val weightBox = WeightTrackObjectBox.store.boxFor(WeightRecord::class.java) private val weightBox = WeightTrackObjectBox.store.boxFor(WeightRecord::class)
private fun saveWeightRecord(weightRecord: WeightRecord) { private fun saveWeightRecord(weightRecord: WeightRecord) {
weightBox.put(weightRecord) weightBox.put(weightRecord)
@ -37,6 +34,10 @@ class WeightTrackViewModel: ViewModel() {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun loadWeightRecords() { fun loadWeightRecords() {
val storedData = weightBox.all val storedData = weightBox.all
storedData.sortByDescending { it.date }
_historyData.value = storedData _historyData.value = storedData
if (storedData.isNotEmpty()) {
_curRecord.value = storedData[0]
}
} }
} }

View File

@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="297dp"
android:height="210dp"
android:viewportWidth="297"
android:viewportHeight="210">
<path
android:pathData="M21.15,51.45L21.15,51.45A24.41,14.3 90,0 1,35.44 75.85L35.44,133.96A24.41,14.3 90,0 1,21.15 158.37L21.15,158.37A24.41,14.3 90,0 1,6.85 133.96L6.85,75.85A24.41,14.3 90,0 1,21.15 51.45z"
android:strokeWidth="0.59"
android:fillColor="#000000"/>
<path
android:pathData="M115.64,88.33l63.41,0l0,33.15l-63.41,0z"
android:strokeWidth="0.49"
android:fillColor="#000000"/>
<path
android:pathData="m76.68,15.39c-22.04,0 -39.78,18.23 -39.78,40.87l0,97.3c0,22.64 17.74,40.87 39.78,40.87 22.04,0 39.78,-18.23 39.78,-40.87L116.46,56.26c0,-22.64 -17.74,-40.87 -39.78,-40.87zM76.68,52.69c7.35,0 13.26,10.63 13.26,23.84l0,56.76c0,13.21 -5.92,23.84 -13.26,23.84 -7.35,0 -13.26,-10.63 -13.26,-23.84L63.42,76.53c0,-13.21 5.91,-23.84 13.26,-23.84z"
android:strokeWidth="1.28"
android:fillColor="#666666"/>
<path
android:pathData="m218.41,15.39c-22.04,0 -39.78,18.23 -39.78,40.87l0,97.3c0,22.64 17.74,40.87 39.78,40.87 22.04,0 39.78,-18.23 39.78,-40.87L258.2,56.26c0,-22.64 -17.74,-40.87 -39.78,-40.87zM218.41,52.69c7.35,0 13.26,10.63 13.26,23.84l0,56.76c0,13.21 -5.92,23.84 -13.26,23.84 -7.35,0 -13.26,-10.63 -13.26,-23.84L205.15,76.53c0,-13.21 5.91,-23.84 13.26,-23.84z"
android:strokeWidth="1.28"
android:fillColor="#666666"/>
<path
android:pathData="M274.77,51.45L274.77,51.45A24.41,14.3 90,0 1,289.07 75.85L289.07,133.96A24.41,14.3 90,0 1,274.77 158.37L274.77,158.37A24.41,14.3 90,0 1,260.48 133.96L260.48,75.85A24.41,14.3 90,0 1,274.77 51.45z"
android:strokeWidth="0.59"
android:fillColor="#000000"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB