diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..71d8cef 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ef7126a..dc0ece6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,8 @@
+
+
= _totalSteps.asStateFlow()
+
+ private val _stepDetected = MutableStateFlow(false)
+ val stepDetected: StateFlow = _stepDetected.asStateFlow()
+
+ private val _isWalking = MutableStateFlow(false)
+ val isWalking: StateFlow = _isWalking.asStateFlow()
+
+ private val _sensorStatus = MutableStateFlow("")
+ val sensorStatus: StateFlow = _sensorStatus.asStateFlow()
+
+ // Internal state
+ private var baselineSteps = -1 // Changed to -1 for better initial detection
+ private var sessionSteps = 0
+ private var detectorStepCount = 0
+ private var lastStepTime = 0L
+ private var isTracking = false
+
+ // Sensor availability flags
+ private var hasStepCounter = false
+ private var hasStepDetector = false
+
+ companion object {
+ private const val TAG = "ImprovedStepManager"
+ private const val WALKING_TIMEOUT = 2000L
+ }
+
+ init {
+ checkSensorAvailability()
+ }
+
+ private fun checkSensorAvailability() {
+ hasStepCounter = stepCounterSensor != null
+ hasStepDetector = stepDetectorSensor != null
+
+ Log.d(TAG, "=== SENSOR CHECK ===")
+ Log.d(TAG, "STEP_COUNTER available: $hasStepCounter")
+ Log.d(TAG, "STEP_DETECTOR available: $hasStepDetector")
+
+ if (hasStepCounter) {
+ Log.d(TAG, "STEP_COUNTER: ${stepCounterSensor?.name} (${stepCounterSensor?.vendor})")
+ }
+ if (hasStepDetector) {
+ Log.d(TAG, "STEP_DETECTOR: ${stepDetectorSensor?.name} (${stepDetectorSensor?.vendor})")
+ }
+
+ updateSensorStatus()
+ }
+
+ private fun updateSensorStatus() {
+ _sensorStatus.value = when {
+ hasStepCounter && hasStepDetector -> "Hybrid Mode (Counter + Detector)"
+ hasStepDetector -> "Detector Only Mode"
+ hasStepCounter -> "Counter Only Mode"
+ else -> "No Sensor Available"
+ }
+ }
+
+ fun isSensorAvailable(): Boolean {
+ return hasStepCounter || hasStepDetector
+ }
+
+ fun startTracking() {
+ if (isTracking) {
+ Log.w(TAG, "Already tracking!")
+ return
+ }
+
+ Log.d(TAG, "=== START TRACKING ===")
+ isTracking = true
+
+ var sensorsRegistered = 0
+
+ // Register STEP_DETECTOR first (priority for responsiveness)
+ if (hasStepDetector) {
+ val success = sensorManager.registerListener(
+ this,
+ stepDetectorSensor,
+ SensorManager.SENSOR_DELAY_FASTEST
+ )
+ Log.d(TAG, "STEP_DETECTOR registration: ${if (success) "SUCCESS" else "FAILED"}")
+ if (success) sensorsRegistered++
+ }
+
+ // Register STEP_COUNTER for accuracy
+ if (hasStepCounter) {
+ val success = sensorManager.registerListener(
+ this,
+ stepCounterSensor,
+ SensorManager.SENSOR_DELAY_UI
+ )
+ Log.d(TAG, "STEP_COUNTER registration: ${if (success) "SUCCESS" else "FAILED"}")
+ if (success) sensorsRegistered++
+ }
+
+ Log.d(TAG, "Total sensors registered: $sensorsRegistered")
+
+ if (sensorsRegistered == 0) {
+ Log.e(TAG, "NO SENSORS REGISTERED!")
+ isTracking = false
+ }
+ }
+
+ fun stopTracking() {
+ if (!isTracking) return
+
+ Log.d(TAG, "=== STOP TRACKING ===")
+ sensorManager.unregisterListener(this)
+ isTracking = false
+ _isWalking.value = false
+ }
+
+ fun resetSteps() {
+ Log.d(TAG, "=== RESET STEPS ===")
+ baselineSteps = -1
+ sessionSteps = 0
+ detectorStepCount = 0
+ _totalSteps.value = 0
+ lastStepTime = 0L
+ }
+
+ override fun onSensorChanged(event: SensorEvent?) {
+ if (!isTracking) return
+
+ event?.let {
+ when (it.sensor.type) {
+ Sensor.TYPE_STEP_COUNTER -> handleStepCounter(it)
+ Sensor.TYPE_STEP_DETECTOR -> handleStepDetector(it)
+ }
+ }
+ }
+
+ private fun handleStepCounter(event: SensorEvent) {
+ val rawSteps = event.values[0].toInt()
+
+ if (baselineSteps == -1) {
+ // First reading - set baseline
+ baselineSteps = rawSteps
+ Log.d(TAG, "STEP_COUNTER: Baseline set to $baselineSteps")
+
+ // If we have detector, use its count
+ if (hasStepDetector && detectorStepCount > 0) {
+ sessionSteps = detectorStepCount
+ Log.d(TAG, "STEP_COUNTER: Using detector count $detectorStepCount")
+ } else {
+ sessionSteps = 0
+ }
+ } else {
+ // Calculate session steps
+ val counterSessionSteps = rawSteps - baselineSteps
+
+ // If we have detector, use detector count (more accurate for short sessions)
+ // Otherwise use counter
+ if (hasStepDetector && detectorStepCount > 0) {
+ sessionSteps = detectorStepCount
+ Log.d(TAG, "STEP_COUNTER: Raw=$rawSteps, Counter=${counterSessionSteps}, Using Detector=$detectorStepCount")
+ } else {
+ sessionSteps = counterSessionSteps
+ Log.d(TAG, "STEP_COUNTER: Raw=$rawSteps, Session=$sessionSteps")
+ }
+ }
+
+ _totalSteps.value = sessionSteps
+ }
+
+ private fun handleStepDetector(event: SensorEvent) {
+ val currentTime = System.currentTimeMillis()
+
+ // Increment counter
+ detectorStepCount++
+ sessionSteps = detectorStepCount
+
+ // Update total immediately
+ _totalSteps.value = sessionSteps
+
+ // Update walking state
+ _isWalking.value = true
+ lastStepTime = currentTime
+
+ // Trigger visual feedback
+ _stepDetected.value = true
+
+ Log.d(TAG, "STEP_DETECTOR: Step #$detectorStepCount detected!")
+
+ // Reset visual feedback after animation
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
+ _stepDetected.value = false
+ }, 200)
+
+ // Schedule walking timeout check
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
+ checkWalkingTimeout(currentTime)
+ }, WALKING_TIMEOUT)
+ }
+
+ private fun checkWalkingTimeout(stepTime: Long) {
+ if (stepTime == lastStepTime) {
+ val timeSinceLastStep = System.currentTimeMillis() - lastStepTime
+ if (timeSinceLastStep >= WALKING_TIMEOUT) {
+ _isWalking.value = false
+ Log.d(TAG, "Walking stopped (timeout)")
+ }
+ }
+ }
+
+ fun getDebugInfo(): String {
+ return """
+ Sensor Status: ${_sensorStatus.value}
+ Tracking: $isTracking
+ Total Steps: ${_totalSteps.value}
+ Detector Count: $detectorStepCount
+ Counter Baseline: $baselineSteps
+ Walking: ${_isWalking.value}
+ """.trimIndent()
+ }
+
+ override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
+ val accuracyStr = when (accuracy) {
+ SensorManager.SENSOR_STATUS_ACCURACY_HIGH -> "HIGH"
+ SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM -> "MEDIUM"
+ SensorManager.SENSOR_STATUS_ACCURACY_LOW -> "LOW"
+ SensorManager.SENSOR_STATUS_NO_CONTACT -> "NO_CONTACT"
+ SensorManager.SENSOR_STATUS_UNRELIABLE -> "UNRELIABLE"
+ else -> "UNKNOWN"
+ }
+ Log.d(TAG, "Sensor accuracy changed: ${sensor?.name} = $accuracyStr")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ui/screen/steps/StepsScreen.kt b/app/src/main/java/ui/screen/steps/StepsScreen.kt
index 86912f7..ec89d86 100644
--- a/app/src/main/java/ui/screen/steps/StepsScreen.kt
+++ b/app/src/main/java/ui/screen/steps/StepsScreen.kt
@@ -1,9 +1,14 @@
package com.example.stepdrink.ui.screen.steps
import android.Manifest
+import android.content.Context
import android.os.Build
+import android.os.VibrationEffect
+import android.os.Vibrator
+import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -18,8 +23,10 @@ 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.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
@@ -31,40 +38,107 @@ fun StepsScreen(
navController: NavController,
viewModel: StepViewModel
) {
+ val context = LocalContext.current
+
val todaySteps by viewModel.todaySteps.collectAsState()
val stepGoal by viewModel.dailyGoal.collectAsState()
- val sensorSteps by viewModel.stepCounterManager.totalSteps.collectAsState()
+ val sensorSteps by viewModel.improvedStepManager.totalSteps.collectAsState()
val last7Days by viewModel.last7DaysSteps.collectAsState()
- val userWeight by viewModel.userWeight.collectAsState() // TAMBAH INI
+ val userWeight by viewModel.userWeight.collectAsState()
+ val sensorStatus by viewModel.improvedStepManager.sensorStatus.collectAsState()
+
+ // Real-time detection states
+ val stepDetected by viewModel.improvedStepManager.stepDetected.collectAsState()
+ val isWalking by viewModel.improvedStepManager.isWalking.collectAsState()
var isTracking by remember { mutableStateOf(false) }
var permissionGranted by remember { mutableStateOf(false) }
+ var showDebugInfo by remember { mutableStateOf(false) }
- // Calculate calories - TAMBAH INI
+ // Calculate calories
val todayCalories = remember(todaySteps?.steps, userWeight) {
viewModel.calculateCalories(todaySteps?.steps ?: 0, userWeight)
}
+ // Log when sensor steps change
+ LaunchedEffect(sensorSteps) {
+ Log.d("StepsScreen", "Sensor steps: $sensorSteps")
+ }
+
+ // Haptic feedback
+ LaunchedEffect(stepDetected) {
+ if (stepDetected && isTracking) {
+ try {
+ val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ vibrator?.vibrate(
+ VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)
+ )
+ } else {
+ @Suppress("DEPRECATION")
+ vibrator?.vibrate(30)
+ }
+ } catch (e: Exception) {
+ Log.e("StepsScreen", "Vibration error: ${e.message}")
+ }
+ }
+ }
+
// Permission launcher
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionGranted = isGranted
+ Log.d("StepsScreen", "Permission result: $isGranted")
+
if (isGranted) {
- viewModel.stepCounterManager.startTracking()
+ viewModel.improvedStepManager.startTracking()
isTracking = true
+ Log.d("StepsScreen", "Tracking started")
+ } else {
+ Log.w("StepsScreen", "Permission denied!")
}
}
Scaffold(
topBar = {
TopAppBar(
- title = { Text("Pelacak Langkah") },
+ title = {
+ Column {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Text("Pelacak Langkah")
+
+ if (isWalking && isTracking) {
+ PulsingDot()
+ }
+ }
+
+ // Sensor status indicator
+ Text(
+ text = sensorStatus,
+ style = MaterialTheme.typography.labelSmall,
+ color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f)
+ )
+ }
+ },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.Default.ArrowBack, "Kembali")
}
},
+ actions = {
+ // Debug info button
+ IconButton(onClick = { showDebugInfo = !showDebugInfo }) {
+ Icon(
+ imageVector = Icons.Default.Info,
+ contentDescription = "Debug Info",
+ tint = if (showDebugInfo) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onPrimaryContainer
+ )
+ }
+ },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
@@ -75,13 +149,15 @@ fun StepsScreen(
ExtendedFloatingActionButton(
onClick = {
if (isTracking) {
- viewModel.stepCounterManager.stopTracking()
+ Log.d("StepsScreen", "Stopping tracking")
+ viewModel.improvedStepManager.stopTracking()
isTracking = false
} else {
+ Log.d("StepsScreen", "Requesting permission")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
permissionLauncher.launch(Manifest.permission.ACTIVITY_RECOGNITION)
} else {
- viewModel.stepCounterManager.startTracking()
+ viewModel.improvedStepManager.startTracking()
isTracking = true
}
}
@@ -97,183 +173,328 @@ fun StepsScreen(
)
}
) { paddingValues ->
- LazyColumn(
- modifier = Modifier
- .fillMaxSize()
- .padding(paddingValues)
- .padding(16.dp),
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- item {
- // Main Steps Card dengan Gradient
- Card(
- modifier = Modifier.fillMaxWidth(),
- colors = CardDefaults.cardColors(
- containerColor = Color.Transparent
- ),
- elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
- ) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .background(
- brush = Brush.verticalGradient(
- colors = listOf(
- Color(0xFF4CAF50),
- Color(0xFF81C784)
- )
- )
- )
- .padding(24.dp)
- ) {
- Column(
+ Box(modifier = Modifier.fillMaxSize()) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // Debug Info Card (collapsible)
+ if (showDebugInfo) {
+ item {
+ Card(
modifier = Modifier.fillMaxWidth(),
- horizontalAlignment = Alignment.CenterHorizontally
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.errorContainer
+ )
) {
- // Animated Icon
- if (isTracking) {
- AnimatedWalkingIcon()
- } else {
- Icon(
- imageVector = Icons.Default.DirectionsWalk,
- contentDescription = null,
- modifier = Modifier.size(64.dp),
- tint = Color.White
+ Column(
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Text(
+ text = "🐛 Debug Info",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = viewModel.getDebugInfo(),
+ style = MaterialTheme.typography.bodySmall,
+ fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace
)
}
+ }
+ }
+ }
- Spacer(modifier = Modifier.height(16.dp))
-
- Text(
- text = "${todaySteps?.steps ?: 0}",
- style = MaterialTheme.typography.displayLarge,
- fontWeight = FontWeight.Bold,
- color = Color.White
- )
-
- Text(
- text = "Langkah Hari Ini",
- style = MaterialTheme.typography.titleMedium,
- color = Color.White.copy(alpha = 0.9f)
- )
-
- Spacer(modifier = Modifier.height(16.dp))
-
- LinearProgressIndicator(
- progress = {
- (todaySteps?.steps ?: 0).toFloat() / stepGoal.toFloat()
- },
- modifier = Modifier
- .fillMaxWidth()
- .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,
- color = Color.White.copy(alpha = 0.9f)
- )
-
- if (isTracking) {
- Spacer(modifier = Modifier.height(12.dp))
- Surface(
- shape = MaterialTheme.shapes.small,
- color = Color.White.copy(alpha = 0.2f)
- ) {
- 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
+ item {
+ // Main Steps Card
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ colors = CardDefaults.cardColors(
+ containerColor = Color.Transparent
+ ),
+ elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ Color(0xFF4CAF50),
+ Color(0xFF81C784)
)
+ )
+ )
+ .padding(24.dp)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (isTracking && isWalking) {
+ AnimatedWalkingIcon()
+ } else {
+ Icon(
+ imageVector = Icons.Default.DirectionsWalk,
+ contentDescription = null,
+ modifier = Modifier.size(64.dp),
+ tint = Color.White
+ )
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Display sensor steps in real-time when tracking
+ Text(
+ text = if (isTracking) "$sensorSteps" else "${todaySteps?.steps ?: 0}",
+ style = MaterialTheme.typography.displayLarge,
+ fontWeight = FontWeight.Bold,
+ color = Color.White
+ )
+
+ Text(
+ text = "Langkah Hari Ini",
+ style = MaterialTheme.typography.titleMedium,
+ color = Color.White.copy(alpha = 0.9f)
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ LinearProgressIndicator(
+ progress = {
+ val steps = if (isTracking) sensorSteps else (todaySteps?.steps ?: 0)
+ steps.toFloat() / stepGoal.toFloat()
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .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,
+ color = Color.White.copy(alpha = 0.9f)
+ )
+
+ if (isTracking) {
+ Spacer(modifier = Modifier.height(12.dp))
+ Surface(
+ shape = MaterialTheme.shapes.small,
+ color = Color.White.copy(alpha = 0.2f)
+ ) {
+ Row(
+ modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Default.Sensors,
+ contentDescription = null,
+ tint = Color.White,
+ modifier = Modifier.size(16.dp)
+ )
+ Text(
+ "Tracking Aktif • Real-time",
+ color = Color.White,
+ style = MaterialTheme.typography.bodySmall,
+ 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)
+ // 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)
+ .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("%.1f", todayCalories / 7.7)}g lemak",
+ style = MaterialTheme.typography.bodySmall,
+ color = Color.White.copy(alpha = 0.8f)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ // Rest of the items (Motivational message, history, etc.)
+ 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(),
+ elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
- Box(
- modifier = Modifier
- .size(56.dp)
- .clip(CircleShape)
- .background(Color.White.copy(alpha = 0.2f)),
- contentAlignment = Alignment.Center
+ Surface(
+ shape = CircleShape,
+ color = MaterialTheme.colorScheme.primaryContainer,
+ modifier = Modifier.size(48.dp)
) {
- Text(
- text = "🔥",
- style = MaterialTheme.typography.headlineMedium
- )
+ Box(contentAlignment = Alignment.Center) {
+ Icon(
+ imageVector = Icons.Default.CalendarToday,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.size(24.dp)
+ )
+ }
}
Column {
Text(
- text = "Kalori Terbakar",
+ text = record.date,
style = MaterialTheme.typography.titleMedium,
- color = Color.White.copy(alpha = 0.9f)
+ fontWeight = FontWeight.Bold
)
Text(
- text = "$todayCalories kkal",
- style = MaterialTheme.typography.headlineMedium,
- fontWeight = FontWeight.Bold,
- color = Color.White
+ text = "${record.steps} langkah • $recordCalories kkal",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
+
Column(
horizontalAlignment = Alignment.End
) {
- Text(
- text = "Berat: ${userWeight}kg",
- style = MaterialTheme.typography.bodySmall,
- color = Color.White.copy(alpha = 0.8f)
+ 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 = "≈ ${String.format("%.2f", todayCalories / 7.7)} gram lemak",
- style = MaterialTheme.typography.bodySmall,
- color = Color.White.copy(alpha = 0.8f)
+ text = "${((record.steps.toFloat() / stepGoal.toFloat()) * 100).toInt()}%",
+ style = MaterialTheme.typography.labelSmall,
+ fontWeight = FontWeight.Bold
)
}
}
@@ -281,111 +502,24 @@ fun StepsScreen(
}
}
- // 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
- )
+ // Floating step indicator
+ AnimatedVisibility(
+ visible = stepDetected && isTracking,
+ enter = scaleIn(initialScale = 0.5f) + fadeIn(),
+ exit = scaleOut(targetScale = 1.5f) + fadeOut(),
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Surface(
+ shape = CircleShape,
+ color = Color.White,
+ shadowElevation = 8.dp,
+ modifier = Modifier.size(80.dp)
) {
- 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(),
- elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp),
- 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,
- style = MaterialTheme.typography.titleMedium,
- fontWeight = FontWeight.Bold
- )
- Text(
- 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
- )
- }
+ Box(contentAlignment = Alignment.Center) {
+ Text(
+ text = "👟",
+ style = MaterialTheme.typography.displaySmall
+ )
}
}
}
@@ -400,7 +534,7 @@ fun AnimatedWalkingIcon() {
initialValue = -10f,
targetValue = 10f,
animationSpec = infiniteRepeatable(
- animation = tween(500, easing = LinearEasing),
+ animation = tween(400, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
label = "rotation"
@@ -419,20 +553,21 @@ fun AnimatedWalkingIcon() {
@Composable
fun PulsingDot() {
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
- val alpha by infiniteTransition.animateFloat(
- initialValue = 1f,
- targetValue = 0.3f,
+ val scale by infiniteTransition.animateFloat(
+ initialValue = 0.8f,
+ targetValue = 1.2f,
animationSpec = infiniteRepeatable(
- animation = tween(1000, easing = LinearEasing),
+ animation = tween(600, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
- label = "alpha"
+ label = "scale"
)
Box(
modifier = Modifier
.size(8.dp)
+ .scale(scale)
.clip(CircleShape)
- .background(Color.White.copy(alpha = alpha))
+ .background(Color(0xFF4CAF50))
)
}
\ No newline at end of file
diff --git a/app/src/main/java/viewmodel/StepViewModel.kt b/app/src/main/java/viewmodel/StepViewModel.kt
index c6cdd4c..565c0d1 100644
--- a/app/src/main/java/viewmodel/StepViewModel.kt
+++ b/app/src/main/java/viewmodel/StepViewModel.kt
@@ -1,22 +1,22 @@
package com.example.stepdrink.viewmodel
import android.app.Application
+import android.util.Log
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.sensor.ImprovedStepManager
import com.example.stepdrink.util.DateUtils
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
class StepViewModel(application: Application) : AndroidViewModel(application) {
- // PERBAIKAN: Initialize repository dulu di init block
private val repository: StepRepository
- val stepCounterManager: StepCounterManager
+ val improvedStepManager: ImprovedStepManager
private val preferencesManager = PreferencesManager(application)
val dailyGoal: StateFlow
@@ -24,11 +24,19 @@ class StepViewModel(application: Application) : AndroidViewModel(application) {
val todaySteps: StateFlow
val last7DaysSteps: StateFlow>
+ companion object {
+ private const val TAG = "StepViewModel"
+ }
+
init {
+ Log.d(TAG, "=== VIEWMODEL INIT ===")
+
// Initialize database & repository
val database = AppDatabase.getDatabase(application)
repository = StepRepository(database.stepDao())
- stepCounterManager = StepCounterManager(application)
+ improvedStepManager = ImprovedStepManager(application)
+
+ Log.d(TAG, "Sensor available: ${improvedStepManager.isSensorAvailable()}")
// Initialize flows
dailyGoal = preferencesManager.stepGoal
@@ -45,16 +53,25 @@ class StepViewModel(application: Application) : AndroidViewModel(application) {
// Observe sensor dan update database
viewModelScope.launch {
- stepCounterManager.totalSteps.collect { steps ->
+ improvedStepManager.totalSteps.collect { steps ->
+ Log.d(TAG, "Steps updated: $steps")
if (steps > 0) {
updateTodaySteps(steps)
}
}
}
+
+ // Log sensor status
+ viewModelScope.launch {
+ improvedStepManager.sensorStatus.collect { status ->
+ Log.d(TAG, "Sensor status: $status")
+ }
+ }
}
private fun updateTodaySteps(steps: Int) {
viewModelScope.launch {
+ Log.d(TAG, "Updating database: $steps steps for ${DateUtils.getCurrentDate()}")
repository.insertOrUpdateSteps(DateUtils.getCurrentDate(), steps)
}
}
@@ -64,22 +81,23 @@ class StepViewModel(application: Application) : AndroidViewModel(application) {
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)
}
+ fun getDebugInfo(): String {
+ return improvedStepManager.getDebugInfo()
+ }
+
override fun onCleared() {
super.onCleared()
- stepCounterManager.stopTracking()
+ Log.d(TAG, "=== VIEWMODEL CLEARED ===")
+ improvedStepManager.stopTracking()
}
}
\ No newline at end of file