1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-08-16 13:44:26 +02:00

Refactor measurement type to improves the logic for setting the initial and updating selectedInputType based on allowed types.

This commit is contained in:
oliexdev
2025-08-13 19:43:29 +02:00
parent 22f5512b83
commit 994361ad5c
3 changed files with 96 additions and 70 deletions

View File

@@ -17,6 +17,7 @@
*/
package com.health.openscale.core.data
import android.text.InputType
import androidx.annotation.StringRes
import com.health.openscale.R
import java.util.Locale
@@ -145,35 +146,36 @@ enum class MeasureUnit {
enum class MeasurementTypeKey(
val id: Int,
@StringRes val localizedNameResId: Int,
val allowedUnitTypes: List<UnitType>
val allowedUnitTypes: List<UnitType>,
val allowedInputType: List<InputFieldType>
) {
WEIGHT(1, R.string.measurement_type_weight, listOf(UnitType.KG, UnitType.LB, UnitType.ST)),
BMI(2, R.string.measurement_type_bmi, listOf(UnitType.NONE)),
BODY_FAT(3, R.string.measurement_type_body_fat, listOf(UnitType.PERCENT)),
WATER(4, R.string.measurement_type_water, listOf(UnitType.PERCENT)),
MUSCLE(5, R.string.measurement_type_muscle, listOf(UnitType.PERCENT, UnitType.KG, UnitType.LB)),
LBM(6, R.string.measurement_type_lbm, listOf(UnitType.KG, UnitType.LB, UnitType.ST)),
BONE(7, R.string.measurement_type_bone, listOf(UnitType.KG, UnitType.LB)),
WAIST(8, R.string.measurement_type_waist, listOf(UnitType.CM, UnitType.INCH)),
WHR(9, R.string.measurement_type_whr, listOf(UnitType.NONE)),
WHTR(10, R.string.measurement_type_whtr, listOf(UnitType.NONE)),
HIPS(11, R.string.measurement_type_hips, listOf(UnitType.CM, UnitType.INCH)),
VISCERAL_FAT(12, R.string.measurement_type_visceral_fat, listOf(UnitType.PERCENT, UnitType.NONE)),
CHEST(13, R.string.measurement_type_chest, listOf(UnitType.CM, UnitType.INCH)),
THIGH(14, R.string.measurement_type_thigh, listOf(UnitType.CM, UnitType.INCH)),
BICEPS(15, R.string.measurement_type_biceps, listOf(UnitType.CM, UnitType.INCH)),
NECK(16, R.string.measurement_type_neck, listOf(UnitType.CM, UnitType.INCH)),
CALIPER_1(17, R.string.measurement_type_caliper1, listOf(UnitType.CM, UnitType.INCH)),
CALIPER_2(18, R.string.measurement_type_caliper2, listOf(UnitType.CM, UnitType.INCH)),
CALIPER_3(19, R.string.measurement_type_caliper3, listOf(UnitType.CM, UnitType.INCH)),
CALIPER(20, R.string.measurement_type_fat_caliper, listOf(UnitType.PERCENT, UnitType.NONE)),
BMR(21, R.string.measurement_type_bmr, listOf(UnitType.KCAL)),
TDEE(22, R.string.measurement_type_tdee, listOf(UnitType.KCAL)),
CALORIES(23, R.string.measurement_type_calories, listOf(UnitType.KCAL)),
DATE(24, R.string.measurement_type_date, listOf(UnitType.NONE)),
TIME(25, R.string.measurement_type_time, listOf(UnitType.NONE)),
COMMENT(26, R.string.measurement_type_comment, listOf(UnitType.NONE)),
CUSTOM(99, R.string.measurement_type_custom_default_name, UnitType.entries.toList());
WEIGHT(1, R.string.measurement_type_weight, listOf(UnitType.KG, UnitType.LB, UnitType.ST), listOf(InputFieldType.FLOAT)),
BMI(2, R.string.measurement_type_bmi, listOf(UnitType.NONE), listOf(InputFieldType.FLOAT)),
BODY_FAT(3, R.string.measurement_type_body_fat, listOf(UnitType.PERCENT), listOf(InputFieldType.FLOAT)),
WATER(4, R.string.measurement_type_water, listOf(UnitType.PERCENT), listOf(InputFieldType.FLOAT)),
MUSCLE(5, R.string.measurement_type_muscle, listOf(UnitType.PERCENT, UnitType.KG, UnitType.LB), listOf(InputFieldType.FLOAT)),
LBM(6, R.string.measurement_type_lbm, listOf(UnitType.KG, UnitType.LB, UnitType.ST), listOf(InputFieldType.FLOAT)),
BONE(7, R.string.measurement_type_bone, listOf(UnitType.KG, UnitType.LB), listOf(InputFieldType.FLOAT)),
WAIST(8, R.string.measurement_type_waist, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
WHR(9, R.string.measurement_type_whr, listOf(UnitType.NONE), listOf(InputFieldType.FLOAT)),
WHTR(10, R.string.measurement_type_whtr, listOf(UnitType.NONE), listOf(InputFieldType.FLOAT)),
HIPS(11, R.string.measurement_type_hips, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
VISCERAL_FAT(12, R.string.measurement_type_visceral_fat, listOf(UnitType.PERCENT, UnitType.NONE), listOf(InputFieldType.INT, InputFieldType.FLOAT)),
CHEST(13, R.string.measurement_type_chest, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
THIGH(14, R.string.measurement_type_thigh, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
BICEPS(15, R.string.measurement_type_biceps, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
NECK(16, R.string.measurement_type_neck, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
CALIPER_1(17, R.string.measurement_type_caliper1, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
CALIPER_2(18, R.string.measurement_type_caliper2, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
CALIPER_3(19, R.string.measurement_type_caliper3, listOf(UnitType.CM, UnitType.INCH), listOf(InputFieldType.FLOAT)),
CALIPER(20, R.string.measurement_type_fat_caliper, listOf(UnitType.PERCENT, UnitType.NONE), listOf(InputFieldType.FLOAT)),
BMR(21, R.string.measurement_type_bmr, listOf(UnitType.KCAL), listOf(InputFieldType.INT)),
TDEE(22, R.string.measurement_type_tdee, listOf(UnitType.KCAL), listOf(InputFieldType.INT)),
CALORIES(23, R.string.measurement_type_calories, listOf(UnitType.KCAL), listOf(InputFieldType.INT)),
DATE(24, R.string.measurement_type_date, listOf(UnitType.NONE), listOf(InputFieldType.DATE)),
TIME(25, R.string.measurement_type_time, listOf(UnitType.NONE), listOf(InputFieldType.TIME)),
COMMENT(26, R.string.measurement_type_comment, listOf(UnitType.NONE), listOf(InputFieldType.TEXT)),
CUSTOM(99, R.string.measurement_type_custom_default_name, UnitType.entries.toList(), listOf(InputFieldType.FLOAT, InputFieldType.INT, InputFieldType.TEXT, InputFieldType.DATE, InputFieldType.TIME));
}

View File

@@ -111,6 +111,10 @@ fun MeasurementTypeDetailScreen(
currentMeasurementTypeKey.allowedUnitTypes
}
val allowedInputTypesForKey = remember(currentMeasurementTypeKey) {
currentMeasurementTypeKey.allowedInputType
}
var name by remember { mutableStateOf(originalExistingType?.getDisplayName(context).orEmpty()) }
// Safely set selectedUnit. If the existing unit isn't allowed or if no existing unit,
@@ -124,7 +128,14 @@ fun MeasurementTypeDetailScreen(
}
}
var selectedInputType by remember { mutableStateOf(originalExistingType?.inputType ?: InputFieldType.FLOAT) }
var selectedInputType by remember {
val initialInputType = originalExistingType?.inputType
if (initialInputType != null && initialInputType in allowedInputTypesForKey) {
mutableStateOf(initialInputType)
} else {
mutableStateOf(allowedInputTypesForKey.firstOrNull() ?: InputFieldType.FLOAT)
}
}
var selectedColor by remember { mutableStateOf(originalExistingType?.color ?: 0xFF6200EE.toInt()) }
var selectedIcon by remember { mutableStateOf(originalExistingType?.icon ?: "ic_weight") }
var isEnabled by remember { mutableStateOf(originalExistingType?.isEnabled ?: true) }
@@ -142,13 +153,13 @@ fun MeasurementTypeDetailScreen(
val titleEdit = stringResource(R.string.measurement_type_detail_title_edit)
val titleAdd = stringResource(R.string.measurement_type_detail_title_add)
// Determines if the unit dropdown should be enabled (i.e., if there's more than one allowed unit).
val unitDropdownEnabled by remember(allowedUnitsForKey) {
derivedStateOf { allowedUnitsForKey.size > 1 }
}
val inputTypeDropdownEnabled by remember(allowedInputTypesForKey) {
derivedStateOf { allowedInputTypesForKey.size > 1 }
}
// Effect to re-evaluate and set selectedUnit if originalExistingType or allowedUnitsForKey change.
// This ensures selectedUnit is always valid.
LaunchedEffect(originalExistingType, allowedUnitsForKey) {
val currentUnitInExistingType = originalExistingType?.unit
if (currentUnitInExistingType != null && currentUnitInExistingType in allowedUnitsForKey) {
@@ -163,6 +174,19 @@ fun MeasurementTypeDetailScreen(
}
}
LaunchedEffect(originalExistingType, allowedInputTypesForKey) {
val currentInputTypeInExistingType = originalExistingType?.inputType
if (currentInputTypeInExistingType != null && currentInputTypeInExistingType in allowedInputTypesForKey) {
if (selectedInputType != currentInputTypeInExistingType) {
selectedInputType = currentInputTypeInExistingType
}
} else if (allowedInputTypesForKey.isNotEmpty() && selectedInputType !in allowedInputTypesForKey) {
selectedInputType = allowedInputTypesForKey.first()
} else if (allowedInputTypesForKey.isEmpty()) {
selectedInputType = InputFieldType.FLOAT
}
}
LaunchedEffect(Unit) {
sharedViewModel.setTopBarTitle(if (isEdit) titleEdit else titleAdd)
sharedViewModel.setTopBarAction(
@@ -252,15 +276,14 @@ fun MeasurementTypeDetailScreen(
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text(stringResource(R.string.measurement_type_label_name)) },
modifier = Modifier.fillMaxWidth(),
// Name field is editable for new types or existing CUSTOM types.
// For predefined types, the name is typically not user-editable.
enabled = !isEdit || (originalExistingType?.key == MeasurementTypeKey.CUSTOM)
)
if (!isEdit || (originalExistingType?.key == MeasurementTypeKey.CUSTOM)) {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text(stringResource(R.string.measurement_type_label_name)) },
modifier = Modifier.fillMaxWidth(),
)
}
OutlinedTextField(
value = String.format("#%06X", 0xFFFFFF and selectedColor),
@@ -376,32 +399,37 @@ fun MeasurementTypeDetailScreen(
}
// InputFieldType Dropdown
ExposedDropdownMenuBox(
expanded = expandedInputType,
onExpandedChange = { expandedInputType = !expandedInputType }
) {
OutlinedTextField(
readOnly = true,
value = selectedInputType.name.lowercase().replaceFirstChar { it.uppercase() },
onValueChange = {},
label = { Text(stringResource(R.string.measurement_type_label_input_type)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedInputType) },
modifier = Modifier
.menuAnchor(type = MenuAnchorType.PrimaryNotEditable, enabled = true)
.fillMaxWidth()
)
ExposedDropdownMenu(
if (inputTypeDropdownEnabled) {
ExposedDropdownMenuBox(
expanded = expandedInputType,
onDismissRequest = { expandedInputType = false }
onExpandedChange = { expandedInputType = !expandedInputType }
) {
InputFieldType.entries.forEach { type ->
DropdownMenuItem(
text = { Text(type.name.lowercase().replaceFirstChar { it.uppercase() }) },
onClick = {
selectedInputType = type
expandedInputType = false
}
)
OutlinedTextField(
readOnly = true,
value = selectedInputType.name.lowercase().replaceFirstChar { it.uppercase() },
onValueChange = {},
label = { Text(stringResource(R.string.measurement_type_label_input_type)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedInputType) },
modifier = Modifier
.menuAnchor(type = MenuAnchorType.PrimaryNotEditable, enabled = true)
.fillMaxWidth()
)
ExposedDropdownMenu(
expanded = expandedInputType,
onDismissRequest = { expandedInputType = false }
) {
allowedInputTypesForKey.forEach { type ->
DropdownMenuItem(
text = {
Text(
type.name.lowercase().replaceFirstChar { it.uppercase() })
},
onClick = {
selectedInputType = type
expandedInputType = false
}
)
}
}
}
}

View File

@@ -251,7 +251,6 @@ fun UserDetailScreen(
modifier = Modifier.fillMaxWidth()
)
Text(stringResource(id = R.string.user_detail_label_height))
OutlinedTextField(
value = heightValueString,
onValueChange = { newValue ->
@@ -302,9 +301,6 @@ fun UserDetailScreen(
}
)
Text(stringResource(id = R.string.user_detail_label_gender)) // "Gender"
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
GenderType.entries.forEach { option ->
Row(
@@ -358,9 +354,9 @@ fun UserDetailScreen(
}
}
Text(stringResource(id = R.string.user_detail_label_birth_date)) // "Birth Date"
OutlinedTextField(
value = dateFormatter.format(Date(birthDate)),
label = { Text(stringResource(R.string.user_detail_label_birth_date)) },
onValueChange = {}, // Input is read-only, selection via DatePicker
modifier = Modifier
.fillMaxWidth()