From 4eed5ce2f70ab5e56c968b61232625d4f960e583 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sat, 16 Aug 2025 14:32:47 +0200 Subject: [PATCH] Replace String-based icons with enum and introduce MeasurementIcon composable --- .../java/com/health/openscale/MainActivity.kt | 57 +++++---- .../com/health/openscale/core/data/Enums.kt | 121 ++++++++++++++++++ .../openscale/core/data/MeasurementType.kt | 2 +- .../ui/screen/components/MeasurementIcon.kt | 88 +++++++++++++ .../components/MeasurementTypeFilterRow.kt | 49 ++----- .../ui/screen/dialog/ColorPickerDialog.kt | 25 ++-- .../ui/screen/dialog/DateInputDialog.kt | 25 ++-- .../ui/screen/dialog/IconPickerDialog.kt | 101 ++++----------- .../ui/screen/dialog/NumberInputDialog.kt | 24 ++-- .../ui/screen/dialog/TextInputDialog.kt | 24 ++-- .../ui/screen/dialog/TimeInputDialog.kt | 24 ++-- .../overview/MeasurementDetailScreen.kt | 47 +++---- .../ui/screen/overview/OverviewScreen.kt | 35 +---- .../settings/MeasurementTypeDetailScreen.kt | 88 +++++++------ .../settings/MeasurementTypeSettingsScreen.kt | 35 ++--- .../ui/screen/statistics/StatisticsScreen.kt | 25 +--- .../app/src/main/res/values-de/strings.xml | 1 - .../app/src/main/res/values/strings.xml | 1 - 18 files changed, 404 insertions(+), 368 deletions(-) create mode 100644 android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementIcon.kt diff --git a/android_app/app/src/main/java/com/health/openscale/MainActivity.kt b/android_app/app/src/main/java/com/health/openscale/MainActivity.kt index 8f66f364..bdd74a8a 100644 --- a/android_app/app/src/main/java/com/health/openscale/MainActivity.kt +++ b/android_app/app/src/main/java/com/health/openscale/MainActivity.kt @@ -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 { - 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) + ) } /** diff --git a/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt b/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt index aee1ed86..9bd6406a 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt @@ -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, diff --git a/android_app/app/src/main/java/com/health/openscale/core/data/MeasurementType.kt b/android_app/app/src/main/java/com/health/openscale/core/data/MeasurementType.kt index 0a09349b..f24fb330 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/data/MeasurementType.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/data/MeasurementType.kt @@ -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, diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementIcon.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementIcon.kt new file mode 100644 index 00000000..a766eeb3 --- /dev/null +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementIcon.kt @@ -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 + ) + } +} diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt index ef206d6b..8a7803d1 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt @@ -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 + ) } } } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/ColorPickerDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/ColorPickerDialog.kt index 863755c8..58a8626d 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/ColorPickerDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/ColorPickerDialog.kt @@ -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 = 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 { diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt index 8441bc84..cb3e8d53 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt @@ -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) } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt index 84c2bc83..a5ebfb32 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt @@ -1,111 +1,54 @@ -/* - * openScale - * Copyright (C) 2025 olie.xdev - * - * 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 . - */ 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) } + ) } } }, diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/NumberInputDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/NumberInputDialog.kt index c646168c..dfcac667 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/NumberInputDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/NumberInputDialog.kt @@ -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) } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TextInputDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TextInputDialog.kt index ea7b9c45..3010b1aa 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TextInputDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TextInputDialog.kt @@ -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) } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TimeInputDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TimeInputDialog.kt index 75dbb700..f0574c9e 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TimeInputDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/TimeInputDialog.kt @@ -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) } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/MeasurementDetailScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/MeasurementDetailScreen.kt index 1630d647..64386888 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/MeasurementDetailScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/MeasurementDetailScreen.kt @@ -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) diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/OverviewScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/OverviewScreen.kt index 9cf88334..aa59386f 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/OverviewScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/overview/OverviewScreen.kt @@ -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( diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt index f03c0089..401571e3 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt @@ -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) diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeSettingsScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeSettingsScreen.kt index 013b2b02..a6fccb36 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeSettingsScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeSettingsScreen.kt @@ -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 diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt index 22fb5549..ade62c01 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt @@ -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), diff --git a/android_app/app/src/main/res/values-de/strings.xml b/android_app/app/src/main/res/values-de/strings.xml index 4deb7e22..0fb5866b 100644 --- a/android_app/app/src/main/res/values-de/strings.xml +++ b/android_app/app/src/main/res/values-de/strings.xml @@ -256,7 +256,6 @@ Keine Daten zum Anzeigen oder Typen zur Auswahl. Keine Daten für die ausgewählten Typen verfügbar. Messfilter ein-/ausblenden - Nach %1$s filtern Datum diff --git a/android_app/app/src/main/res/values/strings.xml b/android_app/app/src/main/res/values/strings.xml index 71555ccf..a141aebc 100644 --- a/android_app/app/src/main/res/values/strings.xml +++ b/android_app/app/src/main/res/values/strings.xml @@ -258,7 +258,6 @@ No data to display or types to select. No data available for the selected types. Show/Hide Measurement Filter - Filter by %1$s Date