1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-09-08 23:50:40 +02:00

Replace String-based icons with enum and introduce MeasurementIcon composable

This commit is contained in:
oliexdev
2025-08-16 14:32:47 +02:00
parent d61670cc84
commit 4eed5ce2f7
18 changed files with 404 additions and 368 deletions

View File

@@ -31,6 +31,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.health.openscale.core.data.InputFieldType
import com.health.openscale.core.data.MeasurementType
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.core.data.SupportedLanguage
import com.health.openscale.core.data.UnitType
@@ -58,34 +59,34 @@ import kotlinx.coroutines.launch
* @return A list of [MeasurementType] objects.
*/
fun getDefaultMeasurementTypes(): List<MeasurementType> {
return listOf(
MeasurementType(key = MeasurementTypeKey.WEIGHT, unit = UnitType.KG, color = 0xFF7E57C2.toInt(), icon = "ic_weight", isPinned = true, isEnabled = true, isOnRightYAxis = true),
MeasurementType(key = MeasurementTypeKey.BMI, unit = UnitType.NONE, color = 0xFFFFCA28.toInt(), icon = "ic_bmi", isDerived = true, isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BODY_FAT, unit = UnitType.PERCENT, color = 0xFFEF5350.toInt(), icon = "ic_fat", isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WATER, unit = UnitType.PERCENT, color = 0xFF29B6F6.toInt(), icon = "ic_water", isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.MUSCLE, unit = UnitType.PERCENT, color = 0xFF66BB6A.toInt(), icon = "ic_muscle", isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.LBM, unit = UnitType.KG, color = 0xFF4DBAC0.toInt(), icon = "ic_lbm", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BONE, unit = UnitType.KG, color = 0xFFBDBDBD.toInt(), icon = "ic_bone", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WAIST, unit = UnitType.CM, color = 0xFF78909C.toInt(), icon = "ic_waist", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WHR, unit = UnitType.NONE, color = 0xFFFFA726.toInt(), icon = "ic_whr", isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WHTR, unit = UnitType.NONE, color = 0xFFFF7043.toInt(), icon = "ic_whtr", isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.HIPS, unit = UnitType.CM, color = 0xFF5C6BC0.toInt(), icon = "ic_hip", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.VISCERAL_FAT, unit = UnitType.NONE, color = 0xFFD84315.toInt(), icon = "ic_visceral_fat", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CHEST, unit = UnitType.CM, color = 0xFF8E24AA.toInt(), icon = "ic_chest", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.THIGH, unit = UnitType.CM, color = 0xFFA1887F.toInt(), icon = "ic_thigh", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BICEPS, unit = UnitType.CM, color = 0xFFEC407A.toInt(), icon = "ic_biceps", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.NECK, unit = UnitType.CM, color = 0xFFB0BEC5.toInt(), icon = "ic_neck", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER_1, unit = UnitType.CM, color = 0xFFFFF59D.toInt(), icon = "ic_caliper1", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER_2, unit = UnitType.CM, color = 0xFFFFE082.toInt(), icon = "ic_caliper2", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER_3, unit = UnitType.CM, color = 0xFFFFCC80.toInt(), icon = "ic_caliper3", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER, unit = UnitType.PERCENT, color = 0xFFFB8C00.toInt(), icon = "ic_fat_caliper", isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BMR, unit = UnitType.KCAL, color = 0xFFAB47BC.toInt(), icon = "ic_bmr", isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.TDEE, unit = UnitType.KCAL, color = 0xFF26A69A.toInt(), icon = "ic_tdee", isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALORIES, unit = UnitType.KCAL, color = 0xFF4CAF50.toInt(), icon = "ic_calories", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.COMMENT, inputType = InputFieldType.TEXT, unit = UnitType.NONE, color = 0xFFE0E0E0.toInt(), icon = "ic_comment", isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.DATE, inputType = InputFieldType.DATE, unit = UnitType.NONE, color = 0xFF9E9E9E.toInt(), icon = "ic_date", isEnabled = true),
MeasurementType(key = MeasurementTypeKey.TIME, inputType = InputFieldType.TIME, unit = UnitType.NONE, color = 0xFF757575.toInt(), icon = "ic_time", isEnabled = true)
)
return listOf(
MeasurementType(key = MeasurementTypeKey.WEIGHT, unit = UnitType.KG, color = 0xFF7E57C2.toInt(), icon = MeasurementTypeIcon.IC_WEIGHT, isPinned = true, isEnabled = true, isOnRightYAxis = true),
MeasurementType(key = MeasurementTypeKey.BMI, unit = UnitType.NONE, color = 0xFFFFCA28.toInt(), icon = MeasurementTypeIcon.IC_BMI, isDerived = true, isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BODY_FAT, unit = UnitType.PERCENT, color = 0xFFEF5350.toInt(), icon = MeasurementTypeIcon.IC_BODY_FAT, isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WATER, unit = UnitType.PERCENT, color = 0xFF29B6F6.toInt(), icon = MeasurementTypeIcon.IC_WATER, isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.MUSCLE, unit = UnitType.PERCENT, color = 0xFF66BB6A.toInt(), icon = MeasurementTypeIcon.IC_MUSCLE, isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.LBM, unit = UnitType.KG, color = 0xFF4DBAC0.toInt(), icon = MeasurementTypeIcon.IC_LBM, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BONE, unit = UnitType.KG, color = 0xFFBDBDBD.toInt(), icon = MeasurementTypeIcon.IC_BONE, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WAIST, unit = UnitType.CM, color = 0xFF78909C.toInt(), icon = MeasurementTypeIcon.IC_WAIST, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WHR, unit = UnitType.NONE, color = 0xFFFFA726.toInt(), icon = MeasurementTypeIcon.IC_WHR, isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.WHTR, unit = UnitType.NONE, color = 0xFFFF7043.toInt(), icon = MeasurementTypeIcon.IC_WHTR, isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.HIPS, unit = UnitType.CM, color = 0xFF5C6BC0.toInt(), icon = MeasurementTypeIcon.IC_HIPS, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.VISCERAL_FAT, unit = UnitType.NONE, color = 0xFFD84315.toInt(), icon = MeasurementTypeIcon.IC_VISCERAL_FAT, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CHEST, unit = UnitType.CM, color = 0xFF8E24AA.toInt(), icon = MeasurementTypeIcon.IC_CHEST, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.THIGH, unit = UnitType.CM, color = 0xFFA1887F.toInt(), icon = MeasurementTypeIcon.IC_THIGH, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BICEPS, unit = UnitType.CM, color = 0xFFEC407A.toInt(), icon = MeasurementTypeIcon.IC_BICEPS, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.NECK, unit = UnitType.CM, color = 0xFFB0BEC5.toInt(), icon = MeasurementTypeIcon.IC_NECK, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER_1, unit = UnitType.CM, color = 0xFFFFF59D.toInt(), icon = MeasurementTypeIcon.IC_CALIPER1, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER_2, unit = UnitType.CM, color = 0xFFFFE082.toInt(), icon = MeasurementTypeIcon.IC_CALIPER2, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER_3, unit = UnitType.CM, color = 0xFFFFCC80.toInt(), icon = MeasurementTypeIcon.IC_CALIPER3, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALIPER, unit = UnitType.PERCENT, color = 0xFFFB8C00.toInt(), icon = MeasurementTypeIcon.IC_FAT_CALIPER, isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.BMR, unit = UnitType.KCAL, color = 0xFFAB47BC.toInt(), icon = MeasurementTypeIcon.IC_BMR, isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.TDEE, unit = UnitType.KCAL, color = 0xFF26A69A.toInt(), icon = MeasurementTypeIcon.IC_TDEE, isDerived = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.CALORIES, unit = UnitType.KCAL, color = 0xFF4CAF50.toInt(), icon = MeasurementTypeIcon.IC_CALORIES, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.COMMENT, inputType = InputFieldType.TEXT, unit = UnitType.NONE, color = 0xFFE0E0E0.toInt(), icon = MeasurementTypeIcon.IC_COMMENT, isPinned = true, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.DATE, inputType = InputFieldType.DATE, unit = UnitType.NONE, color = 0xFF9E9E9E.toInt(), icon = MeasurementTypeIcon.IC_DATE, isEnabled = true),
MeasurementType(key = MeasurementTypeKey.TIME, inputType = InputFieldType.TIME, unit = UnitType.NONE, color = 0xFF757575.toInt(), icon = MeasurementTypeIcon.IC_TIME, isEnabled = true)
)
}
/**

View File

@@ -18,7 +18,52 @@
package com.health.openscale.core.data
import android.text.InputType
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.DirectionsWalk
import androidx.compose.material.icons.automirrored.filled.HelpOutline
import androidx.compose.material.icons.automirrored.filled.Label
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.automirrored.filled.ShowChart
import androidx.compose.material.icons.automirrored.filled.TrendingDown
import androidx.compose.material.icons.automirrored.filled.TrendingFlat
import androidx.compose.material.icons.automirrored.filled.TrendingUp
import androidx.compose.material.icons.filled.AddCircleOutline
import androidx.compose.material.icons.filled.Analytics
import androidx.compose.material.icons.filled.Bloodtype
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.CheckCircleOutline
import androidx.compose.material.icons.filled.DeviceThermostat
import androidx.compose.material.icons.filled.DirectionsWalk
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.EditNote
import androidx.compose.material.icons.filled.Egg
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FitnessCenter
import androidx.compose.material.icons.filled.Flag
import androidx.compose.material.icons.filled.Grain
import androidx.compose.material.icons.filled.Height
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.LocalDining
import androidx.compose.material.icons.filled.LocalDrink
import androidx.compose.material.icons.filled.Medication
import androidx.compose.material.icons.filled.NightsStay
import androidx.compose.material.icons.filled.OilBarrel
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.PieChart
import androidx.compose.material.icons.filled.RadioButtonUnchecked
import androidx.compose.material.icons.filled.RemoveCircleOutline
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material.icons.filled.SentimentSatisfiedAlt
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.ShowChart
import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.SquareFoot
import androidx.compose.material.icons.filled.StackedLineChart
import androidx.compose.material.icons.filled.Timer
import androidx.compose.material.icons.filled.WarningAmber
import androidx.compose.ui.graphics.vector.ImageVector
import com.health.openscale.R
import java.util.Locale
@@ -145,6 +190,82 @@ enum class MeasureUnit {
}
}
enum class MeasurementTypeIcon(val resource: IconResource) {
IC_DEFAULT(IconResource.VectorResource(Icons.Filled.RadioButtonUnchecked)),
IC_WEIGHT(IconResource.PainterResource(R.drawable.ic_weight)),
IC_BMI(IconResource.PainterResource(R.drawable.ic_bmi)),
IC_BODY_FAT(IconResource.PainterResource(R.drawable.ic_fat)),
IC_WATER(IconResource.PainterResource(R.drawable.ic_water)),
IC_MUSCLE(IconResource.PainterResource(R.drawable.ic_muscle)),
IC_LBM(IconResource.PainterResource(R.drawable.ic_lbm)),
IC_BONE(IconResource.PainterResource(R.drawable.ic_bone)),
IC_WAIST(IconResource.PainterResource(R.drawable.ic_waist)),
IC_WHR(IconResource.PainterResource(R.drawable.ic_whr)),
IC_WHTR(IconResource.PainterResource(R.drawable.ic_whtr)),
IC_HIPS(IconResource.PainterResource(R.drawable.ic_hip)),
IC_VISCERAL_FAT(IconResource.PainterResource(R.drawable.ic_visceral_fat)),
IC_CHEST(IconResource.PainterResource(R.drawable.ic_chest)),
IC_THIGH(IconResource.PainterResource(R.drawable.ic_thigh)),
IC_BICEPS(IconResource.PainterResource(R.drawable.ic_biceps)),
IC_NECK(IconResource.PainterResource(R.drawable.ic_neck)),
IC_CALIPER1(IconResource.PainterResource(R.drawable.ic_caliper1)),
IC_CALIPER2(IconResource.PainterResource(R.drawable.ic_caliper2)),
IC_CALIPER3(IconResource.PainterResource(R.drawable.ic_caliper3)),
IC_FAT_CALIPER(IconResource.PainterResource(R.drawable.ic_fat_caliper)),
IC_BMR(IconResource.PainterResource(R.drawable.ic_bmr)),
IC_TDEE(IconResource.PainterResource(R.drawable.ic_tdee)),
IC_CALORIES(IconResource.PainterResource(R.drawable.ic_calories)),
IC_COMMENT(IconResource.PainterResource(R.drawable.ic_comment)),
IC_TIME(IconResource.PainterResource(R.drawable.ic_time)),
IC_DATE(IconResource.PainterResource(R.drawable.ic_date)),
IC_M_HEIGHT(IconResource.VectorResource(Icons.Filled.Height)),
IC_M_HEART_RATE(IconResource.VectorResource(Icons.Filled.Favorite)),
IC_M_STEPS(IconResource.VectorResource(Icons.AutoMirrored.Filled.DirectionsWalk)),
IC_M_SLEEP(IconResource.VectorResource(Icons.Filled.NightsStay)),
IC_M_WORKOUT(IconResource.VectorResource(Icons.Filled.FitnessCenter)),
IC_M_WATER_INTAKE(IconResource.VectorResource(Icons.Filled.LocalDrink)),
IC_M_GOAL(IconResource.VectorResource(Icons.Filled.Flag)),
IC_M_NOTES(IconResource.VectorResource(Icons.Filled.EditNote)),
IC_M_TEMPERATURE(IconResource.VectorResource(Icons.Filled.DeviceThermostat)),
IC_M_BLOOD_PRESSURE(IconResource.VectorResource(Icons.Filled.Bloodtype)),
IC_M_GLUCOSE(IconResource.VectorResource(Icons.Filled.Bloodtype)),
IC_M_TREND_UP(IconResource.VectorResource(Icons.AutoMirrored.Filled.TrendingUp)),
IC_M_TREND_DOWN(IconResource.VectorResource(Icons.AutoMirrored.Filled.TrendingDown)),
IC_M_TREND_FLAT(IconResource.VectorResource(Icons.AutoMirrored.Filled.TrendingFlat)),
IC_M_CALENDAR(IconResource.VectorResource(Icons.Filled.CalendarMonth)),
IC_M_CLOCK(IconResource.VectorResource(Icons.Filled.Schedule)),
IC_M_TIMER(IconResource.VectorResource(Icons.Filled.Timer)),
IC_M_INFO(IconResource.VectorResource(Icons.Filled.Info)),
IC_M_HELP(IconResource.VectorResource(Icons.AutoMirrored.Filled.HelpOutline)),
IC_M_SETTINGS(IconResource.VectorResource(Icons.Filled.Settings)),
IC_M_ADD(IconResource.VectorResource(Icons.Filled.AddCircleOutline)),
IC_M_REMOVE(IconResource.VectorResource(Icons.Filled.RemoveCircleOutline)),
IC_M_DONE(IconResource.VectorResource(Icons.Filled.Done)),
IC_M_CHECK_CIRCLE(IconResource.VectorResource(Icons.Filled.CheckCircleOutline)),
IC_M_WARNING(IconResource.VectorResource(Icons.Filled.WarningAmber)),
IC_M_ANALYTICS(IconResource.VectorResource(Icons.Filled.Analytics)),
IC_M_CHART_BAR(IconResource.VectorResource(Icons.AutoMirrored.Filled.ShowChart)),
IC_M_CHART_LINE(IconResource.VectorResource(Icons.Filled.StackedLineChart)),
IC_M_CHART_PIE(IconResource.VectorResource(Icons.Filled.PieChart)),
IC_M_NUTRITION(IconResource.VectorResource(Icons.Filled.LocalDining)),
IC_M_PROTEIN(IconResource.VectorResource(Icons.Filled.Egg)),
IC_M_CARBS(IconResource.VectorResource(Icons.Filled.Grain)),
IC_M_FAT_FOOD(IconResource.VectorResource(Icons.Filled.OilBarrel)),
IC_M_SPEED(IconResource.VectorResource(Icons.Filled.Speed)),
IC_M_DISTANCE(IconResource.VectorResource(Icons.Filled.SquareFoot)),
IC_M_MOOD(IconResource.VectorResource(Icons.Filled.SentimentSatisfiedAlt)),
IC_M_MEDICATION(IconResource.VectorResource(Icons.Filled.Medication)),
IC_M_LIST(IconResource.VectorResource(Icons.AutoMirrored.Filled.List)),
IC_M_LABEL(IconResource.VectorResource(Icons.AutoMirrored.Filled.Label)),
IC_M_PERSON(IconResource.VectorResource(Icons.Filled.Person));
sealed class IconResource {
data class PainterResource(@DrawableRes val id: Int) : IconResource()
data class VectorResource(val imageVector: ImageVector) : IconResource()
}
}
enum class MeasurementTypeKey(
val id: Int,
@StringRes val localizedNameResId: Int,

View File

@@ -28,7 +28,7 @@ data class MeasurementType(
val key: MeasurementTypeKey = MeasurementTypeKey.CUSTOM,
val name: String? = null,
val color: Int = 0,
val icon : String = "ic_weight",
val icon : MeasurementTypeIcon = MeasurementTypeIcon.IC_DEFAULT,
val unit: UnitType = UnitType.NONE,
val inputType: InputFieldType = InputFieldType.FLOAT,
val displayOrder: Int = 0,

View File

@@ -0,0 +1,88 @@
package com.health.openscale.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.health.openscale.core.data.MeasurementTypeIcon
/**
* Displays a [MeasurementTypeIcon] directly.
* This is the core component for rendering the icon graphic.
*
* @param icon The [MeasurementTypeIcon] enum constant to display.
* @param modifier [Modifier] to be applied to the Icon composable.
* @param size The size of the icon graphic.
* @param tint The tint color for the icon graphic.
*/
@Composable
fun MeasurementIcon(
icon: MeasurementTypeIcon,
modifier: Modifier = Modifier,
size: Dp = 24.dp,
tint: Color = LocalContentColor.current
) {
when (val resource = icon.resource) {
is MeasurementTypeIcon.IconResource.PainterResource -> {
Icon(
painter = painterResource(id = resource.id),
contentDescription = icon.name,
modifier = modifier.size(size),
tint = tint
)
}
is MeasurementTypeIcon.IconResource.VectorResource -> {
Icon(
imageVector = resource.imageVector,
contentDescription = icon.name,
modifier = modifier.size(size),
tint = tint
)
}
}
}
/**
* Displays a [MeasurementTypeIcon] centered within a circular background.
*
* @param icon The [MeasurementTypeIcon] enum constant to display.
* @param modifier [Modifier] to be applied to the outer Box that forms the circle.
* @param size The size of the actual icon graphic itself (e.g., 24.dp).
* @param backgroundTint The background color of the circle. Defaults to `MaterialTheme.colorScheme.surfaceVariant`.
* @param iconTint The tint color for the icon graphic. Defaults to Black.
*/
@Composable
fun RoundMeasurementIcon(
icon: MeasurementTypeIcon,
modifier: Modifier = Modifier,
size: Dp = 28.dp,
backgroundTint : Color = MaterialTheme.colorScheme.surfaceVariant,
iconTint: Color = Color.Black
) {
Box(
modifier = modifier
.size(size + 18.dp)
.aspectRatio(1f)
.clip(CircleShape)
.background(backgroundTint),
contentAlignment = Alignment.Center
) {
MeasurementIcon(
icon = icon,
size = size,
tint = iconTint
)
}
}

View File

@@ -17,19 +17,14 @@
*/
package com.health.openscale.ui.screen.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -40,15 +35,12 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.health.openscale.R
import com.health.openscale.core.data.MeasurementType
import com.health.openscale.ui.components.RoundMeasurementIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
@@ -194,8 +186,8 @@ fun MeasurementTypeFilterRow(
) {
selectableTypes.forEach { type ->
val isSelected = type.id in displayedSelectedIds
val backgroundColor = if (isSelected) Color(type.color) else MaterialTheme.colorScheme.surfaceVariant
val contentColor = if (isSelected) Color.Black else MaterialTheme.colorScheme.onSurfaceVariant // Consider MaterialTheme.colorScheme.onPrimary for selected state if type.color is primary-like
val iconBackgroundColor = if (isSelected) Color(type.color) else MaterialTheme.colorScheme.surfaceVariant
val iconColor = if (isSelected) Color.Black else MaterialTheme.colorScheme.onSurfaceVariant // Consider MaterialTheme.colorScheme.onPrimary for selected state if type.color is primary-like
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -231,36 +223,11 @@ fun MeasurementTypeFilterRow(
}
}
) {
Box(
modifier = Modifier
.size(iconBoxSize)
.clip(CircleShape)
.background(backgroundColor)
.padding((iconBoxSize - iconSize) / 2), // Center icon within the box
contentAlignment = Alignment.Center
) {
val iconResId = remember(type.icon, context) {
if (type.icon.isNotBlank()) {
// It's generally safer to handle potential ResourceNotFoundException if icon names might be invalid
try {
context.resources.getIdentifier(type.icon, "drawable", context.packageName)
} catch (e: Exception) {
// Log error or handle missing icon gracefully
0 // Return 0 if icon not found
}
} else 0
}
if (iconResId != 0) {
Icon(
painter = painterResource(id = iconResId),
contentDescription = stringResource(R.string.content_desc_measurement_type_icon, type.getDisplayName(LocalContext.current)),
tint = contentColor,
modifier = Modifier.size(iconSize)
)
}
}
// Optionally, add a Text Composable here to display type.name below the icon
// Text(text = type.name, style = MaterialTheme.typography.labelSmall, color = contentColor)
RoundMeasurementIcon(
icon = type.icon,
backgroundTint = iconBackgroundColor,
iconTint = iconColor
)
}
}
}

View File

@@ -40,21 +40,22 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.health.openscale.R
val tangoColors = listOf(
Color(0xFFEF2929), Color(0xFFF57900), Color(0xFFFFCE44), Color(0xFF8AE234),
Color(0xFF729FCF), Color(0xFFAD7FA8), Color(0xFFE9B96E), Color(0xFF888A85),
Color(0xFF204A87), Color(0xFF3465A4), Color(0xFF4E9A06), Color(0xFF5C3566),
Color(0xFFC17D11), Color(0xFFA40000), Color(0xFFCE5C00), Color(0xFFEDD400),
Color(0xFF73D216), Color(0xFF11A879), Color(0xFF555753), Color(0xFFBABDB6),
Color(0xFFD3D7CF), Color(0xFFEEEEEC), Color(0xFF2E3436), Color(0xFF000000),
Color(0xFFFFC0CB), Color(0xFFFFA07A), Color(0xFF87CEEB), Color(0xFF20B2AA),
Color(0xFF9370DB), Color(0xFFFFD700), Color(0xFFFF8C00), Color(0xFFB22222)
val colorPickerPalette: List<Color> = listOf(
Color(0xFF7E57C2), Color(0xFFFFCA28), Color(0xFFEF5350), Color(0xFF29B6F6),
Color(0xFF66BB6A), Color(0xFF4DBAC0), Color(0xFFBDBDBD), Color(0xFF78909C),
Color(0xFFFFA726), Color(0xFFFF7043), Color(0xFF5C6BC0), Color(0xFFD84315),
Color(0xFF8E24AA), Color(0xFFA1887F), Color(0xFFEC407A), Color(0xFFB0BEC5),
Color(0xFFFFF59D), Color(0xFFFFE082), Color(0xFFFFCC80), Color(0xFFFB8C00),
Color(0xFFAB47BC), Color(0xFF26A69A), Color(0xFF4CAF50), Color(0xFFE0E0E0),
Color(0xFF9E9E9E), Color(0xFF757575), Color(0xFFF57900), Color(0xFF8AE234),
Color(0xFF729FCF), Color(0xFFAD7FA8), Color(0xFF000000), Color(0xFFFFFFFF)
)
@Composable
@@ -83,16 +84,16 @@ fun ColorPickerDialog(
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(tangoColors) { color ->
items(colorPickerPalette) { color ->
Box(
modifier = Modifier
.aspectRatio(1f) // stellt sicher, dass Höhe = Breite = Kreis
.aspectRatio(1f)
.padding(4.dp)
.clip(CircleShape)
.background(color)
.border(
width = if (color == currentColor) 3.dp else 1.dp,
color = if (color == currentColor) Color.Black else Color.Gray,
color = if (color == currentColor) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outlineVariant,
shape = CircleShape
)
.clickable {

View File

@@ -41,14 +41,17 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.health.openscale.R
import com.health.openscale.core.data.MeasurementType
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.ui.components.RoundMeasurementIcon
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DateInputDialog(
title: String,
initialTimestamp: Long,
iconRes: Int,
color: Color,
measurementIcon: MeasurementTypeIcon,
iconBackgroundColor: Color,
onDismiss: () -> Unit,
onConfirm: (Long) -> Unit
) {
@@ -78,20 +81,10 @@ fun DateInputDialog(
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(color),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(20.dp)
)
}
RoundMeasurementIcon(
icon = measurementIcon,
backgroundTint = iconBackgroundColor,
)
Spacer(modifier = Modifier.width(12.dp))
Text(title, style = MaterialTheme.typography.titleMedium)
}

View File

@@ -1,111 +1,54 @@
/*
* openScale
* Copyright (C) 2025 olie.xdev <olie.xdeveloper@googlemail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.health.openscale.ui.screen.dialog
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.health.openscale.R
fun getIconResIdByName(name: String): Int {
return when (name) {
"ic_weight" -> R.drawable.ic_weight
"ic_bmi" -> R.drawable.ic_bmi
"ic_body_fat" -> R.drawable.ic_fat
"ic_water" -> R.drawable.ic_water
"ic_muscle" -> R.drawable.ic_muscle
"ic_lbm" -> R.drawable.ic_lbm
"ic_bone" -> R.drawable.ic_bone
"ic_waist" -> R.drawable.ic_waist
"ic_whr" -> R.drawable.ic_whr
"ic_hips" -> R.drawable.ic_hip
"ic_visceral_fat" -> R.drawable.ic_visceral_fat
"ic_chest" -> R.drawable.ic_chest
"ic_thigh" -> R.drawable.ic_thigh
"ic_biceps" -> R.drawable.ic_biceps
"ic_neck" -> R.drawable.ic_neck
"ic_caliper1" -> R.drawable.ic_caliper1
"ic_caliper2" -> R.drawable.ic_caliper2
"ic_caliper3" -> R.drawable.ic_caliper3
"ic_fat_caliper" -> R.drawable.ic_fat_caliper
"ic_bmr" -> R.drawable.ic_bmr
"ic_tdee" -> R.drawable.ic_tdee
"ic_calories" -> R.drawable.ic_calories
"ic_comment" -> R.drawable.ic_comment
"ic_time" -> R.drawable.ic_time
"ic_date" -> R.drawable.ic_date
else -> R.drawable.ic_weight // Fallback
}
}
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.ui.components.RoundMeasurementIcon
@Composable
fun IconPickerDialog(
onIconSelected: (String) -> Unit,
iconBackgroundColor: Color,
onIconSelected: (MeasurementTypeIcon) -> Unit,
onDismiss: () -> Unit
) {
val icons = listOf(
"ic_weight", "ic_bmi", "ic_body_fat", "ic_water", "ic_muscle", "ic_lbm", "ic_bone",
"ic_waist", "ic_whr", "ic_hips", "ic_visceral_fat", "ic_chest", "ic_thigh", "ic_biceps",
"ic_neck", "ic_caliper", "ic_bmr", "ic_tdee", "ic_calories", "ic_comment", "ic_time", "ic_date"
)
val availableIcons = remember {
MeasurementTypeIcon.values().filter { it != MeasurementTypeIcon.IC_DEFAULT }
}
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.dialog_title_select_icon))},
title = { Text(stringResource(R.string.dialog_title_select_icon)) },
text = {
LazyVerticalGrid(
columns = GridCells.Fixed(4),
modifier = Modifier.height(200.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp)
) {
items(icons) { iconName ->
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.clickable { onIconSelected(iconName) },
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = getIconResIdByName(iconName)),
contentDescription = iconName,
modifier = Modifier.size(28.dp)
)
}
items(availableIcons) { iconEnum ->
RoundMeasurementIcon(
icon = iconEnum,
backgroundTint = iconBackgroundColor,
size = 28.dp,
modifier = Modifier.clickable { onIconSelected(iconEnum) }
)
}
}
},

View File

@@ -56,7 +56,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.health.openscale.R
import com.health.openscale.core.data.InputFieldType
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.core.data.UnitType
import com.health.openscale.ui.components.RoundMeasurementIcon
@Composable
fun NumberInputDialog(
@@ -64,8 +66,8 @@ fun NumberInputDialog(
initialValue: String,
inputType: InputFieldType,
unit: UnitType,
iconRes: Int,
color: Color,
measurementIcon: MeasurementTypeIcon,
iconBackgroundColor: Color,
onDismiss: () -> Unit,
onConfirm: (String) -> Unit
) {
@@ -88,20 +90,10 @@ fun NumberInputDialog(
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(color),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(20.dp)
)
}
RoundMeasurementIcon(
icon = measurementIcon,
backgroundTint = iconBackgroundColor,
)
Spacer(modifier = Modifier.width(8.dp))
Text(title)
}

View File

@@ -44,13 +44,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.health.openscale.R
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.ui.components.RoundMeasurementIcon
@Composable
fun TextInputDialog(
title: String,
initialValue: String,
iconRes: Int,
color: Color,
measurementIcon: MeasurementTypeIcon,
iconBackgroundColor: Color,
onDismiss: () -> Unit,
onConfirm: (String) -> Unit
) {
@@ -69,20 +71,10 @@ fun TextInputDialog(
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(color),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(20.dp)
)
}
RoundMeasurementIcon(
icon = measurementIcon,
backgroundTint = iconBackgroundColor,
)
Spacer(modifier = Modifier.width(12.dp))
Text(text = title, style = MaterialTheme.typography.titleMedium)
}

View File

@@ -52,14 +52,16 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.health.openscale.R
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.ui.components.RoundMeasurementIcon
import java.util.Calendar
@Composable
fun TimeInputDialog(
title: String,
initialTimestamp: Long,
iconRes: Int,
color: Color,
measurementIcon: MeasurementTypeIcon,
iconBackgroundColor: Color,
onDismiss: () -> Unit,
onConfirm: (Long) -> Unit
) {
@@ -90,20 +92,10 @@ fun TimeInputDialog(
},
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(color),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(20.dp)
)
}
RoundMeasurementIcon(
icon = measurementIcon,
backgroundTint = iconBackgroundColor,
)
Spacer(modifier = Modifier.width(12.dp))
Text(title, style = MaterialTheme.typography.titleMedium)
}

View File

@@ -64,15 +64,17 @@ import com.health.openscale.R
import com.health.openscale.core.data.InputFieldType
import com.health.openscale.core.data.Measurement
import com.health.openscale.core.data.MeasurementType
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.core.data.MeasurementValue
import com.health.openscale.core.data.UnitType
import com.health.openscale.ui.components.RoundMeasurementIcon
import com.health.openscale.ui.screen.SharedViewModel
import com.health.openscale.ui.screen.dialog.DateInputDialog
import com.health.openscale.ui.screen.dialog.NumberInputDialog
import com.health.openscale.ui.screen.dialog.TextInputDialog
import com.health.openscale.ui.screen.dialog.TimeInputDialog
import com.health.openscale.ui.screen.dialog.decrementValue
import com.health.openscale.ui.screen.dialog.getIconResIdByName
import com.health.openscale.ui.screen.dialog.incrementValue
import java.text.SimpleDateFormat
import java.util.Calendar
@@ -357,7 +359,7 @@ fun MeasurementDetailScreen(
// --- Dialogs for FLOAT, INT, TEXT based on dialogTargetType ---
dialogTargetType?.let { currentType ->
val typeIconRes = remember(currentType.icon) { getIconResIdByName(currentType.icon) }
val measurementTypeIcon = remember(currentType.icon) { currentType.icon }
val typeColor = remember(currentType.color) { Color(currentType.color) }
val initialDialogValue = valuesState[currentType.id] ?: when (currentType.inputType) {
InputFieldType.FLOAT -> "0.0" // Default for empty float
@@ -373,8 +375,8 @@ fun MeasurementDetailScreen(
initialValue = initialDialogValue,
inputType = currentType.inputType,
unit = currentType.unit,
iconRes = typeIconRes,
color = typeColor,
measurementIcon = measurementTypeIcon,
iconBackgroundColor = typeColor,
onDismiss = { dialogTargetType = null },
onConfirm = { confirmedValue ->
val trimmedValue = confirmedValue.trim()
@@ -411,8 +413,8 @@ fun MeasurementDetailScreen(
TextInputDialog(
title = dialogTitle,
initialValue = initialDialogValue,
iconRes = typeIconRes,
color = typeColor,
measurementIcon = measurementTypeIcon,
iconBackgroundColor = typeColor,
onDismiss = { dialogTargetType = null },
onConfirm = { confirmedValue ->
val finalValue = confirmedValue.trim()
@@ -433,13 +435,13 @@ fun MeasurementDetailScreen(
// --- Dialogs for the main measurement timestamp (measurementTimestampState) ---
if (showDatePickerForMainTimestamp) {
val triggeringType = allMeasurementTypes.find { it.inputType == InputFieldType.DATE }
val triggeringType = allMeasurementTypes.find { it.key == MeasurementTypeKey.DATE }
val dateDialogTitle = stringResource(R.string.dialog_title_change_date, triggeringType?.getDisplayName(context) ?: stringResource(R.string.label_date))
DateInputDialog(
title = dateDialogTitle,
initialTimestamp = measurementTimestampState,
iconRes = getIconResIdByName(triggeringType?.icon ?: "ic_calendar"),
color = triggeringType?.let { Color(it.color) } ?: MaterialTheme.colorScheme.primary,
measurementIcon = triggeringType?.icon ?: MeasurementTypeIcon.IC_DATE,
iconBackgroundColor = triggeringType?.let { Color(it.color) } ?: MaterialTheme.colorScheme.primary,
onDismiss = { showDatePickerForMainTimestamp = false },
onConfirm = { newDateMillis ->
val newCal = Calendar.getInstance().apply { timeInMillis = newDateMillis }
@@ -452,13 +454,13 @@ fun MeasurementDetailScreen(
}
if (showTimePickerForMainTimestamp) {
val triggeringType = allMeasurementTypes.find { it.inputType == InputFieldType.TIME }
val triggeringType = allMeasurementTypes.find { it.key == MeasurementTypeKey.TIME }
val timeDialogTitle = stringResource(R.string.dialog_title_change_time, triggeringType?.getDisplayName(context) ?: stringResource(R.string.label_time))
TimeInputDialog(
title = timeDialogTitle,
initialTimestamp = measurementTimestampState,
iconRes = getIconResIdByName(triggeringType?.icon ?: "ic_time"),
color = triggeringType?.let { Color(it.color) } ?: MaterialTheme.colorScheme.primary,
measurementIcon = triggeringType?.icon ?: MeasurementTypeIcon.IC_TIME,
iconBackgroundColor = triggeringType?.let { Color(it.color) } ?: MaterialTheme.colorScheme.primary,
onDismiss = { showTimePickerForMainTimestamp = false },
onConfirm = { newTimeMillis ->
val newCal = Calendar.getInstance().apply { timeInMillis = newTimeMillis }
@@ -501,23 +503,10 @@ fun MeasurementValueEditRow(
.padding(vertical = 8.dp)
.clickable(onClick = onEditClick, enabled = !type.isDerived) // Clicking row triggers edit, disabled for derived
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color(type.color)), // Uses the type's specific color
contentAlignment = Alignment.Center
) {
val iconId = remember(type.icon) { getIconResIdByName( type.icon) }
if (iconId != 0) {
Icon(
painter = painterResource(id = iconId),
contentDescription = type.getDisplayName(context), // Type name serves as base content description
tint = Color.Black, // Consider a more adaptive tint based on background color for accessibility
modifier = Modifier.size(24.dp)
)
}
}
RoundMeasurementIcon(
icon = type.icon,
backgroundTint = Color(type.color),
)
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
Text(text = type.getDisplayName(context), style = MaterialTheme.typography.bodyLarge)

View File

@@ -89,6 +89,7 @@ import com.health.openscale.core.data.InputFieldType
import com.health.openscale.core.data.Trend
import com.health.openscale.core.model.MeasurementWithValues
import com.health.openscale.core.database.UserPreferenceKeys
import com.health.openscale.ui.components.RoundMeasurementIcon
import com.health.openscale.ui.navigation.Routes
import com.health.openscale.ui.screen.SharedViewModel
import com.health.openscale.ui.screen.ValueWithDifference
@@ -775,11 +776,7 @@ fun MeasurementValueRow(valueWithTrend: ValueWithDifference) {
} ?: "-" // Default to dash if value is null
val context = LocalContext.current
val iconId = remember(type.icon) {
// Attempt to get the drawable resource ID for the type's icon name
// This relies on the icon name string matching a drawable resource name
context.resources.getIdentifier(type.icon, "drawable", context.packageName)
}
val iconMeasurementType = remember(type.icon) {type.icon }
// Dynamic content description for the icon based on type name
val iconContentDescription = stringResource(R.string.measurement_type_icon_desc, type.getDisplayName(context))
// Fallback content description if the icon is not found (e.g. shows question mark)
@@ -795,30 +792,10 @@ fun MeasurementValueRow(valueWithTrend: ValueWithDifference) {
modifier = Modifier.weight(1f), // Takes available space, pushing value & trend to the right
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(36.dp) // Standardized size for the icon container
.clip(CircleShape)
.background(Color(type.color)),
contentAlignment = Alignment.Center
) {
if (iconId != 0) { // Check if the resource ID is valid
Icon(
painter = painterResource(id = iconId),
contentDescription = type.getDisplayName(context),
tint = Color.Black,
modifier = Modifier.size(20.dp)
)
} else {
// Fallback icon if the specified icon resource is not found
Icon(
imageVector = Icons.Filled.QuestionMark,
contentDescription = unknownTypeContentDescription,
modifier = Modifier.size(20.dp),
tint = Color.Black
)
}
}
RoundMeasurementIcon(
icon = iconMeasurementType,
backgroundTint = Color(type.color),
)
Spacer(modifier = Modifier.width(12.dp))
Column(verticalArrangement = Arrangement.Center) {
Text(

View File

@@ -17,6 +17,8 @@
*/
package com.health.openscale.ui.screen.settings
import android.R.attr.enabled
import android.R.attr.label
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
@@ -25,12 +27,14 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.QuestionMark
@@ -73,12 +77,13 @@ import androidx.navigation.NavController
import com.health.openscale.R
import com.health.openscale.core.data.InputFieldType
import com.health.openscale.core.data.MeasurementType
import com.health.openscale.core.data.MeasurementTypeIcon
import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.core.data.UnitType
import com.health.openscale.ui.components.MeasurementIcon
import com.health.openscale.ui.screen.SharedViewModel
import com.health.openscale.ui.screen.dialog.ColorPickerDialog
import com.health.openscale.ui.screen.dialog.IconPickerDialog
import com.health.openscale.ui.screen.dialog.getIconResIdByName
import kotlin.text.lowercase
@OptIn(ExperimentalMaterial3Api::class)
@@ -137,7 +142,7 @@ fun MeasurementTypeDetailScreen(
}
}
var selectedColor by remember { mutableStateOf(originalExistingType?.color ?: 0xFF6200EE.toInt()) }
var selectedIcon by remember { mutableStateOf(originalExistingType?.icon ?: "ic_weight") }
var selectedIcon by remember { mutableStateOf(originalExistingType?.icon ?: MeasurementTypeIcon.IC_WEIGHT) }
var isEnabled by remember { mutableStateOf(originalExistingType?.isEnabled ?: true) }
var isPinned by remember { mutableStateOf(originalExistingType?.isPinned ?: false) }
var isOnRightYAxis by remember { mutableStateOf(originalExistingType?.isOnRightYAxis ?: false) }
@@ -286,7 +291,7 @@ fun MeasurementTypeDetailScreen(
}
OutlinedTextField(
value = String.format("#%06X", 0xFFFFFF and selectedColor),
value = "",
onValueChange = {}, // Read-only
label = { Text(stringResource(R.string.measurement_type_label_color)) },
modifier = Modifier
@@ -300,7 +305,6 @@ fun MeasurementTypeDetailScreen(
.size(24.dp)
.clip(CircleShape)
.background(Color(selectedColor))
.border(1.dp, Color.Gray, CircleShape)
)
},
colors = TextFieldDefaults.colors(
@@ -313,7 +317,7 @@ fun MeasurementTypeDetailScreen(
)
OutlinedTextField(
value = selectedIcon,
value = "",
onValueChange = {}, // Read-only
label = { Text(stringResource(R.string.measurement_type_label_icon)) },
modifier = Modifier
@@ -322,14 +326,8 @@ fun MeasurementTypeDetailScreen(
readOnly = true,
enabled = false, // To make it look like a display field
trailingIcon = {
Icon(
painter = runCatching {
painterResource(id = getIconResIdByName(selectedIcon))
}.getOrElse {
Icons.Filled.QuestionMark // Fallback icon
} as Painter,
contentDescription = stringResource(R.string.content_desc_selected_icon_preview),
modifier = Modifier.size(24.dp)
MeasurementIcon(
icon = selectedIcon,
)
},
colors = TextFieldDefaults.colors(
@@ -342,50 +340,55 @@ fun MeasurementTypeDetailScreen(
)
if (unitDropdownEnabled) {
// UnitType Dropdown
ExposedDropdownMenuBox(
expanded = expandedUnit && unitDropdownEnabled,
onExpandedChange = {
if (unitDropdownEnabled) expandedUnit = !expandedUnit
}
},
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
readOnly = true,
value = selectedUnit.displayName.lowercase()
.replaceFirstChar { it.uppercase() },
onValueChange = {},
label = { Text(stringResource(R.string.measurement_type_label_unit)) },
trailingIcon = {
if (unitDropdownEnabled) {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedUnit)
}
},
modifier = Modifier
OutlinedSettingRow(
label = stringResource(R.string.measurement_type_label_unit),
surfaceModifier = Modifier
.menuAnchor(
type = MenuAnchorType.PrimaryNotEditable,
enabled = unitDropdownEnabled
)
.fillMaxWidth(),
colors = if (!unitDropdownEnabled) OutlinedTextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(
alpha = 0.38f
)
) else OutlinedTextFieldDefaults.colors()
.clickable(enabled = unitDropdownEnabled) {
if (unitDropdownEnabled) expandedUnit = true
},
controlContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = selectedUnit.displayName,
style = MaterialTheme.typography.bodyLarge,
color = if (unitDropdownEnabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
)
if (unitDropdownEnabled) {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedUnit)
}
}
}
)
if (unitDropdownEnabled) {
ExposedDropdownMenu(
expanded = expandedUnit,
onDismissRequest = { expandedUnit = false }
onDismissRequest = { expandedUnit = false },
modifier = Modifier.exposedDropdownSize(matchTextFieldWidth = true)
) {
allowedUnitsForKey.forEach { unit ->
DropdownMenuItem(
text = {
Text(
unit.displayName.lowercase()
.replaceFirstChar { it.uppercase() })
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
Text(
text = unit.displayName,
modifier = Modifier.padding(end = 32.dp)
)
}
},
onClick = {
selectedUnit = unit
@@ -459,6 +462,7 @@ fun MeasurementTypeDetailScreen(
if (showIconPicker) {
IconPickerDialog(
iconBackgroundColor = Color(selectedColor),
onIconSelected = {
selectedIcon = it
showIconPicker = false
@@ -472,12 +476,14 @@ fun MeasurementTypeDetailScreen(
private fun OutlinedSettingRow(
label: String,
modifier: Modifier = Modifier,
surfaceModifier: Modifier = Modifier,
controlContent: @Composable () -> Unit
) {
Surface(
modifier = modifier
.fillMaxWidth()
.heightIn(min = OutlinedTextFieldDefaults.MinHeight),
.heightIn(min = OutlinedTextFieldDefaults.MinHeight)
.then(surfaceModifier),
shape = OutlinedTextFieldDefaults.shape,
color = MaterialTheme.colorScheme.surface,
border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.outline)

View File

@@ -58,6 +58,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.health.openscale.R
import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.ui.components.MeasurementIcon
import com.health.openscale.ui.components.RoundMeasurementIcon
import com.health.openscale.ui.screen.SharedViewModel
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -145,30 +147,15 @@ fun MeasurementTypeSettingsScreen(
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Display colored circle with icon
Box(
modifier = Modifier
.size(48.dp)
.background(
Color(type.color).copy(alpha = iconBackgroundAlpha),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
val context = LocalContext.current
// Remember the icon resource ID to avoid repeated lookups
val iconId = remember(type.icon) {
context.resources.getIdentifier(type.icon, "drawable", context.packageName)
}
if (iconId != 0) { // Check if icon resource was found
Icon(
painter = painterResource(id = iconId),
contentDescription = null, // Decorative icon
tint = Color.Black.copy(alpha = iconTintAlpha), // Consider a more theme-aware tint
modifier = Modifier.size(32.dp)
)
}
}
val iconMeasurementType = remember(type.icon) {type.icon }
RoundMeasurementIcon(
icon = iconMeasurementType,
iconTint = Color.Black.copy(alpha = iconTintAlpha),
backgroundTint = Color(type.color).copy(alpha = iconBackgroundAlpha),
modifier = Modifier.size(48.dp)
)
Spacer(Modifier.size(16.dp))
// Display measurement type name

View File

@@ -61,12 +61,12 @@ import com.health.openscale.R
import com.health.openscale.core.data.InputFieldType
import com.health.openscale.core.data.MeasurementType
import com.health.openscale.core.database.UserPreferenceKeys
import com.health.openscale.ui.components.RoundMeasurementIcon
import com.health.openscale.ui.screen.EnrichedMeasurement
import com.health.openscale.ui.screen.SharedViewModel
import com.health.openscale.ui.screen.components.LineChart
import com.health.openscale.ui.screen.components.provideFilterTopBarAction
import com.health.openscale.ui.screen.components.rememberContextualTimeRangeFilter
import com.health.openscale.ui.screen.dialog.getIconResIdByName
import java.text.DecimalFormat
import java.time.Instant
import java.time.LocalDate
@@ -337,23 +337,12 @@ fun StatisticCard(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Box(
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(Color(measurementType.color)),
contentAlignment = Alignment.Center
) {
val iconId = remember(measurementType.icon) { getIconResIdByName(measurementType.icon) }
if (iconId != 0) {
Icon(
painter = painterResource(id = iconId),
contentDescription = measurementType.getDisplayName(LocalContext.current), // Icon related to measurement type
tint = Color.Black, // Assuming black provides good contrast on the colored background
modifier = Modifier.size(18.dp)
)
}
}
val iconMeasurementType = remember(measurementType.icon) { measurementType.icon }
RoundMeasurementIcon(
icon = iconMeasurementType,
backgroundTint = Color(measurementType.color),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = measurementType.getDisplayName(LocalContext.current),

View File

@@ -256,7 +256,6 @@
<string name="line_chart_no_data_or_types_to_select">Keine Daten zum Anzeigen oder Typen zur Auswahl.</string>
<string name="line_chart_no_data_for_selected_types">Keine Daten für die ausgewählten Typen verfügbar.</string>
<string name="menu_item_measurement_filter">Messfilter ein-/ausblenden</string>
<string name="content_desc_measurement_type_icon">Nach %1$s filtern</string>
<!-- Tabellen-Bildschirm -->
<string name="table_header_date">Datum</string>

View File

@@ -258,7 +258,6 @@
<string name="line_chart_no_data_or_types_to_select">No data to display or types to select.</string>
<string name="line_chart_no_data_for_selected_types">No data available for the selected types.</string>
<string name="menu_item_measurement_filter">Show/Hide Measurement Filter</string>
<string name="content_desc_measurement_type_icon">Filter by %1$s</string>
<!-- Table Screen -->
<string name="table_header_date">Date</string>