ui ux update, adding calorie counter
This commit is contained in:
parent
edbb6a52fb
commit
a23005280b
@ -18,6 +18,7 @@ class PreferencesManager(private val context: Context) {
|
||||
val USER_NAME = stringPreferencesKey("user_name")
|
||||
val STEP_GOAL = intPreferencesKey("step_goal")
|
||||
val WATER_GOAL = intPreferencesKey("water_goal")
|
||||
val USER_WEIGHT = intPreferencesKey("user_weight")
|
||||
}
|
||||
|
||||
val userName: Flow<String> = context.dataStore.data.map { preferences ->
|
||||
@ -32,6 +33,10 @@ class PreferencesManager(private val context: Context) {
|
||||
preferences[WATER_GOAL] ?: 2000
|
||||
}
|
||||
|
||||
val userWeight: Flow<Int> = context.dataStore.data.map { preferences ->
|
||||
preferences[USER_WEIGHT] ?: 70
|
||||
|
||||
}
|
||||
suspend fun saveUserName(name: String) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[USER_NAME] = name
|
||||
@ -49,4 +54,9 @@ class PreferencesManager(private val context: Context) {
|
||||
preferences[WATER_GOAL] = goal
|
||||
}
|
||||
}
|
||||
suspend fun saveUserWeight(weight: Int) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[USER_WEIGHT] = weight
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,28 @@
|
||||
package com.example.stepdrink.ui.screen.home
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DirectionsWalk
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.WaterDrop
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.example.stepdrink.ui.components.StatCard
|
||||
import com.example.stepdrink.ui.navigation.Screen
|
||||
import com.example.stepdrink.viewmodel.StepViewModel
|
||||
import com.example.stepdrink.viewmodel.WaterViewModel
|
||||
import com.example.stepdrink.viewmodel.ProfileViewModel
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
@ -36,14 +41,44 @@ fun HomeScreen(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Step & Drink",
|
||||
fontWeight = FontWeight.Bold
|
||||
text = "Step",
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = "&",
|
||||
fontWeight = FontWeight.Light,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
Text(
|
||||
text = "Drink",
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.tertiary
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { navController.navigate(Screen.Profile.route) }) {
|
||||
Icon(Icons.Default.Person, "Profil")
|
||||
IconButton(
|
||||
onClick = { navController.navigate(Screen.Profile.route) }
|
||||
) {
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
|
||||
modifier = Modifier.size(36.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
"Profil",
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
@ -60,61 +95,241 @@ fun HomeScreen(
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// Greeting Card
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color.Transparent
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
MaterialTheme.colorScheme.primaryContainer,
|
||||
MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "Halo, $userName! 👋",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = "Aktivitas Hari Ini",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = "Tetap sehat dengan tracking harian",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Steps Card
|
||||
StatCard(
|
||||
Text(
|
||||
text = "Aktivitas Hari Ini",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
// Steps Card - Improved
|
||||
ImprovedStatCard(
|
||||
icon = Icons.Default.DirectionsWalk,
|
||||
title = "Langkah",
|
||||
value = "${todaySteps?.steps ?: 0}",
|
||||
unit = "langkah",
|
||||
subtitle = "Target: $stepGoal langkah",
|
||||
progress = (todaySteps?.steps ?: 0).toFloat() / stepGoal.toFloat(),
|
||||
gradientColors = listOf(
|
||||
Color(0xFF4CAF50),
|
||||
Color(0xFF81C784)
|
||||
),
|
||||
onClick = { navController.navigate(Screen.Steps.route) }
|
||||
)
|
||||
|
||||
// Water Card
|
||||
StatCard(
|
||||
// Water Card - Improved
|
||||
ImprovedStatCard(
|
||||
icon = Icons.Default.WaterDrop,
|
||||
title = "Air Minum",
|
||||
value = "${todayWater}ml",
|
||||
value = "$todayWater",
|
||||
unit = "ml",
|
||||
subtitle = "Target: ${waterGoal}ml",
|
||||
progress = todayWater.toFloat() / waterGoal.toFloat(),
|
||||
gradientColors = listOf(
|
||||
Color(0xFF2196F3),
|
||||
Color(0xFF64B5F6)
|
||||
),
|
||||
onClick = { navController.navigate(Screen.Water.route) }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// Tips Card
|
||||
// Tips Card - Improved
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "💡 Tips Kesehatan",
|
||||
text = "💡",
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = "Tips Kesehatan",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Berjalan 10.000 langkah per hari dan minum 2 liter air dapat meningkatkan kesehatan tubuh!",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ImprovedStatCard(
|
||||
icon: ImageVector,
|
||||
title: String,
|
||||
value: String,
|
||||
unit: String,
|
||||
subtitle: String,
|
||||
progress: Float,
|
||||
gradientColors: List<Color>,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color.Transparent
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.horizontalGradient(gradientColors)
|
||||
)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = Color.White.copy(alpha = 0.2f),
|
||||
modifier = Modifier.size(40.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color.White.copy(alpha = 0.9f),
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
Text(
|
||||
text = unit,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color.White.copy(alpha = 0.8f),
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Progress Circle
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.size(60.dp)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
progress = { progress.coerceIn(0f, 1f) },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = Color.White,
|
||||
strokeWidth = 6.dp,
|
||||
trackColor = Color.White.copy(alpha = 0.3f)
|
||||
)
|
||||
Text(
|
||||
text = "${(progress * 100).toInt()}%",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
LinearProgressIndicator(
|
||||
progress = { progress.coerceIn(0f, 1f) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.clip(MaterialTheme.shapes.small),
|
||||
color = Color.White,
|
||||
trackColor = Color.White.copy(alpha = 0.3f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,8 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
@ -26,20 +28,26 @@ fun ProfileScreen(
|
||||
val userName by viewModel.userName.collectAsState()
|
||||
val stepGoal by viewModel.stepGoal.collectAsState()
|
||||
val waterGoal by viewModel.waterGoal.collectAsState()
|
||||
val userWeight by viewModel.userWeight.collectAsState() // TAMBAH INI
|
||||
|
||||
var showNameDialog by remember { mutableStateOf(false) }
|
||||
var showStepGoalDialog by remember { mutableStateOf(false) }
|
||||
var showWaterGoalDialog by remember { mutableStateOf(false) }
|
||||
var showWeightDialog by remember { mutableStateOf(false) } // TAMBAH INI
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Profil") },
|
||||
title = { Text("Profil & Pengaturan") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(Icons.Default.ArrowBack, "Kembali")
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
@ -52,59 +60,126 @@ fun ProfileScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// Profile Header
|
||||
// Profile Header dengan Gradient
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color.Transparent
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
MaterialTheme.colorScheme.primaryContainer,
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// Avatar
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.clip(CircleShape)
|
||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
.background(MaterialTheme.colorScheme.surface),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Person,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(60.dp),
|
||||
modifier = Modifier.size(50.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = userName,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
// Stats Mini Card
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
StatsChip(
|
||||
icon = Icons.Default.DirectionsWalk,
|
||||
label = "Target",
|
||||
value = "$stepGoal"
|
||||
)
|
||||
StatsChip(
|
||||
icon = Icons.Default.WaterDrop,
|
||||
label = "Target",
|
||||
value = "${waterGoal}ml"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Settings Section
|
||||
Text(
|
||||
text = "Pengaturan",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
text = "Informasi Pribadi",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
// Name Setting
|
||||
SettingItem(
|
||||
icon = Icons.Default.Person,
|
||||
title = "Nama",
|
||||
title = "Nama Lengkap",
|
||||
value = userName,
|
||||
iconTint = MaterialTheme.colorScheme.primary,
|
||||
onClick = { showNameDialog = true }
|
||||
)
|
||||
|
||||
// TAMBAH INI - Weight Setting
|
||||
SettingItem(
|
||||
icon = Icons.Default.FitnessCenter,
|
||||
title = "Berat Badan",
|
||||
value = "$userWeight kg",
|
||||
iconTint = Color(0xFFFF6B6B),
|
||||
onClick = { showWeightDialog = true }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = "Target Harian",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
// Step Goal Setting
|
||||
SettingItem(
|
||||
icon = Icons.Default.DirectionsWalk,
|
||||
title = "Target Langkah Harian",
|
||||
value = "$stepGoal langkah",
|
||||
title = "Target Langkah",
|
||||
value = "$stepGoal langkah/hari",
|
||||
iconTint = Color(0xFF4CAF50),
|
||||
onClick = { showStepGoalDialog = true }
|
||||
)
|
||||
|
||||
// Water Goal Setting
|
||||
SettingItem(
|
||||
icon = Icons.Default.WaterDrop,
|
||||
title = "Target Air Minum Harian",
|
||||
value = "${waterGoal}ml",
|
||||
title = "Target Air Minum",
|
||||
value = "${waterGoal}ml/hari",
|
||||
iconTint = Color(0xFF2196F3),
|
||||
onClick = { showWaterGoalDialog = true }
|
||||
)
|
||||
|
||||
@ -114,42 +189,42 @@ fun ProfileScreen(
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = "Tentang Aplikasi",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Step & Drink v1.0\nAplikasi untuk tracking langkah harian dan kebutuhan air minum.",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
text = "Step & Drink v1.0\nAplikasi tracking langkah harian dan kebutuhan air minum dengan perhitungan kalori.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dialogs
|
||||
if (showNameDialog) {
|
||||
EditTextDialog(
|
||||
title = "Edit Nama",
|
||||
label = "Nama",
|
||||
label = "Nama Lengkap",
|
||||
currentValue = userName,
|
||||
onDismiss = { showNameDialog = false },
|
||||
onConfirm = { newName ->
|
||||
@ -159,10 +234,24 @@ fun ProfileScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// TAMBAH INI - Weight Dialog
|
||||
if (showWeightDialog) {
|
||||
EditNumberDialog(
|
||||
title = "Edit Berat Badan",
|
||||
label = "Berat (kg)",
|
||||
currentValue = userWeight,
|
||||
onDismiss = { showWeightDialog = false },
|
||||
onConfirm = { newWeight ->
|
||||
viewModel.updateUserWeight(newWeight)
|
||||
showWeightDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showStepGoalDialog) {
|
||||
EditNumberDialog(
|
||||
title = "Edit Target Langkah",
|
||||
label = "Target (langkah)",
|
||||
label = "Target (langkah/hari)",
|
||||
currentValue = stepGoal,
|
||||
onDismiss = { showStepGoalDialog = false },
|
||||
onConfirm = { newGoal ->
|
||||
@ -175,7 +264,7 @@ fun ProfileScreen(
|
||||
if (showWaterGoalDialog) {
|
||||
EditNumberDialog(
|
||||
title = "Edit Target Air Minum",
|
||||
label = "Target (ml)",
|
||||
label = "Target (ml/hari)",
|
||||
currentValue = waterGoal,
|
||||
onDismiss = { showWaterGoalDialog = false },
|
||||
onConfirm = { newGoal ->
|
||||
@ -186,16 +275,56 @@ fun ProfileScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StatsChip(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
label: String,
|
||||
value: String
|
||||
) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f),
|
||||
tonalElevation = 2.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingItem(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
title: String,
|
||||
value: String,
|
||||
iconTint: Color = MaterialTheme.colorScheme.primary,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -206,18 +335,28 @@ fun SettingItem(
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = iconTint.copy(alpha = 0.1f),
|
||||
modifier = Modifier.size(40.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
tint = iconTint,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
@ -227,7 +366,7 @@ fun SettingItem(
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
imageVector = Icons.Default.ChevronRight,
|
||||
contentDescription = "Edit",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
@ -247,17 +386,21 @@ fun EditTextDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = {
|
||||
Icon(Icons.Default.Edit, contentDescription = null)
|
||||
},
|
||||
title = { Text(title) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
label = { Text(label) },
|
||||
singleLine = true
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
Button(
|
||||
onClick = {
|
||||
if (text.isNotBlank()) {
|
||||
onConfirm(text)
|
||||
@ -287,17 +430,21 @@ fun EditNumberDialog(
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = {
|
||||
Icon(Icons.Default.Edit, contentDescription = null)
|
||||
},
|
||||
title = { Text(title) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = number,
|
||||
onValueChange = { number = it.filter { char -> char.isDigit() } },
|
||||
label = { Text(label) },
|
||||
singleLine = true
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
Button(
|
||||
onClick = {
|
||||
val value = number.toIntOrNull()
|
||||
if (value != null && value > 0) {
|
||||
|
||||
@ -4,18 +4,22 @@ import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DirectionsWalk
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Stop
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
@ -31,11 +35,17 @@ fun StepsScreen(
|
||||
val stepGoal by viewModel.dailyGoal.collectAsState()
|
||||
val sensorSteps by viewModel.stepCounterManager.totalSteps.collectAsState()
|
||||
val last7Days by viewModel.last7DaysSteps.collectAsState()
|
||||
val userWeight by viewModel.userWeight.collectAsState() // TAMBAH INI
|
||||
|
||||
var isTracking by remember { mutableStateOf(false) }
|
||||
var permissionGranted by remember { mutableStateOf(false) }
|
||||
|
||||
// Permission launcher untuk Android 10+
|
||||
// Calculate calories - TAMBAH INI
|
||||
val todayCalories = remember(todaySteps?.steps, userWeight) {
|
||||
viewModel.calculateCalories(todaySteps?.steps ?: 0, userWeight)
|
||||
}
|
||||
|
||||
// Permission launcher
|
||||
val permissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
@ -54,11 +64,15 @@ fun StepsScreen(
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
Icon(Icons.Default.ArrowBack, "Kembali")
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = {
|
||||
if (isTracking) {
|
||||
viewModel.stepCounterManager.stopTracking()
|
||||
@ -71,13 +85,16 @@ fun StepsScreen(
|
||||
isTracking = true
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = if (isTracking) Icons.Default.Stop else Icons.Default.PlayArrow,
|
||||
contentDescription = if (isTracking) "Stop" else "Start"
|
||||
)
|
||||
}
|
||||
},
|
||||
text = { Text(if (isTracking) "Berhenti" else "Mulai Tracking") },
|
||||
containerColor = if (isTracking) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
@ -88,37 +105,56 @@ fun StepsScreen(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
// Current Steps Card
|
||||
// Main Steps Card dengan Gradient
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||
)
|
||||
containerColor = Color.Transparent
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(24.dp),
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF4CAF50),
|
||||
Color(0xFF81C784)
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Animated Icon
|
||||
if (isTracking) {
|
||||
AnimatedWalkingIcon()
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.DirectionsWalk,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(64.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = "${todaySteps?.steps ?: 0}",
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Langkah Hari Ini",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
@ -129,39 +165,172 @@ fun StepsScreen(
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp),
|
||||
.height(10.dp)
|
||||
.clip(MaterialTheme.shapes.small),
|
||||
color = Color.White,
|
||||
trackColor = Color.White.copy(alpha = 0.3f),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = "Target: $stepGoal langkah",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
|
||||
if (isTracking) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Badge(
|
||||
containerColor = MaterialTheme.colorScheme.tertiary
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = Color.White.copy(alpha = 0.2f)
|
||||
) {
|
||||
Text("Sedang Melacak: $sensorSteps langkah")
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PulsingDot()
|
||||
Text(
|
||||
"Sedang Tracking: $sensorSteps langkah",
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TAMBAH INI - Calories Card
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color.Transparent
|
||||
),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFFFF6B6B),
|
||||
Color(0xFFFF8E53)
|
||||
)
|
||||
)
|
||||
)
|
||||
.padding(20.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(56.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = 0.2f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "🔥",
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
text = "Kalori Terbakar",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = Color.White.copy(alpha = 0.9f)
|
||||
)
|
||||
Text(
|
||||
text = "$todayCalories kkal",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
text = "Berat: ${userWeight}kg",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.White.copy(alpha = 0.8f)
|
||||
)
|
||||
Text(
|
||||
text = "≈ ${String.format("%.2f", todayCalories / 7.7)} gram lemak",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.White.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Motivational Message - TAMBAH INI
|
||||
item {
|
||||
val message = when {
|
||||
(todaySteps?.steps ?: 0) >= stepGoal -> "🎉 Luar biasa! Target tercapai!"
|
||||
(todaySteps?.steps ?: 0) >= (stepGoal * 0.75) -> "💪 Hampir sampai! Tetap semangat!"
|
||||
(todaySteps?.steps ?: 0) >= (stepGoal * 0.5) -> "🚶 Setengah jalan! Ayo lanjutkan!"
|
||||
(todaySteps?.steps ?: 0) >= (stepGoal * 0.25) -> "👟 Awal yang bagus! Terus bergerak!"
|
||||
else -> "🌟 Yuk mulai bergerak hari ini!"
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = message,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Riwayat 7 Hari Terakhir",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.History,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
items(last7Days) { record ->
|
||||
val recordCalories = viewModel.calculateCalories(record.steps, userWeight)
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@ -170,6 +339,24 @@ fun StepsScreen(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CalendarToday,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
Text(
|
||||
text = record.date,
|
||||
@ -177,19 +364,75 @@ fun StepsScreen(
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = "${record.steps} langkah",
|
||||
text = "${record.steps} langkah • $recordCalories kkal",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
progress = { (record.steps.toFloat() / stepGoal.toFloat()).coerceIn(0f, 1f) },
|
||||
modifier = Modifier.size(48.dp),
|
||||
strokeWidth = 4.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "${((record.steps.toFloat() / stepGoal.toFloat()) * 100).toInt()}%",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnimatedWalkingIcon() {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "walking")
|
||||
val rotation by infiniteTransition.animateFloat(
|
||||
initialValue = -10f,
|
||||
targetValue = 10f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(500, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "rotation"
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.DirectionsWalk,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.rotate(rotation),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PulsingDot() {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
|
||||
val alpha by infiniteTransition.animateFloat(
|
||||
initialValue = 1f,
|
||||
targetValue = 0.3f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(1000, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "alpha"
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.White.copy(alpha = alpha))
|
||||
)
|
||||
}
|
||||
@ -22,6 +22,9 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
|
||||
val waterGoal: StateFlow<Int> = preferencesManager.waterGoal
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 2000)
|
||||
|
||||
val userWeight: StateFlow<Int> = preferencesManager.userWeight
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 70)
|
||||
|
||||
fun updateUserName(name: String) {
|
||||
viewModelScope.launch {
|
||||
preferencesManager.saveUserName(name)
|
||||
@ -39,4 +42,11 @@ class ProfileViewModel(application: Application) : AndroidViewModel(application)
|
||||
preferencesManager.saveWaterGoal(goal)
|
||||
}
|
||||
}
|
||||
fun updateUserWeight(weight: Int) {
|
||||
viewModelScope.launch {
|
||||
preferencesManager.saveUserWeight(weight)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,34 +3,46 @@ package com.example.stepdrink.viewmodel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.example.stepdrink.data.local.PreferencesManager
|
||||
import com.example.stepdrink.data.local.database.AppDatabase
|
||||
import com.example.stepdrink.data.local.entity.StepRecord
|
||||
import com.example.stepdrink.data.repository.StepRepository
|
||||
import com.example.stepdrink.sensor.StepCounterManager
|
||||
import com.example.stepdrink.data.local.PreferencesManager
|
||||
import com.example.stepdrink.util.DateUtils
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class StepViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val repository: StepRepository = StepRepository(
|
||||
AppDatabase.getDatabase(application).stepDao()
|
||||
)
|
||||
val stepCounterManager: StepCounterManager = StepCounterManager(application)
|
||||
|
||||
// PERBAIKAN: Initialize repository dulu di init block
|
||||
private val repository: StepRepository
|
||||
val stepCounterManager: StepCounterManager
|
||||
private val preferencesManager = PreferencesManager(application)
|
||||
|
||||
val dailyGoal: StateFlow<Int> = preferencesManager.stepGoal
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 10000)
|
||||
|
||||
val todaySteps: StateFlow<StepRecord?> = repository.getStepsByDate(DateUtils.getCurrentDate())
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
||||
|
||||
val last7DaysSteps: StateFlow<List<StepRecord>> = repository.getLast7DaysSteps()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
val dailyGoal: StateFlow<Int>
|
||||
val userWeight: StateFlow<Int>
|
||||
val todaySteps: StateFlow<StepRecord?>
|
||||
val last7DaysSteps: StateFlow<List<StepRecord>>
|
||||
|
||||
init {
|
||||
// Initialize database & repository
|
||||
val database = AppDatabase.getDatabase(application)
|
||||
repository = StepRepository(database.stepDao())
|
||||
stepCounterManager = StepCounterManager(application)
|
||||
|
||||
// Initialize flows
|
||||
dailyGoal = preferencesManager.stepGoal
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 10000)
|
||||
|
||||
userWeight = preferencesManager.userWeight
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 70)
|
||||
|
||||
todaySteps = repository.getStepsByDate(DateUtils.getCurrentDate())
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
||||
|
||||
last7DaysSteps = repository.getLast7DaysSteps()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
// Observe sensor dan update database
|
||||
viewModelScope.launch {
|
||||
stepCounterManager.totalSteps.collect { steps ->
|
||||
@ -47,6 +59,25 @@ class StepViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getProgressPercentage(): Float {
|
||||
val today = todaySteps.value?.steps ?: 0
|
||||
return (today.toFloat() / dailyGoal.value.toFloat()).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
// Function untuk hitung kalori
|
||||
fun calculateCalories(steps: Int, weight: Int): Int {
|
||||
// Rumus: (Steps × Weight × 0.57) / 1000
|
||||
// Contoh: 10000 steps × 70kg = 399 kalori
|
||||
return ((steps * weight * 0.57) / 1000).toInt()
|
||||
}
|
||||
|
||||
// Get kalori untuk hari ini
|
||||
fun getTodayCalories(): Int {
|
||||
val steps = todaySteps.value?.steps ?: 0
|
||||
val weight = userWeight.value
|
||||
return calculateCalories(steps, weight)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
stepCounterManager.stopTracking()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user