sensor update
This commit is contained in:
parent
a23005280b
commit
acc4e48a1a
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@ -4,6 +4,14 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-12-18T05:31:18.470754700Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=RRCX303Z3VY" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@ -7,6 +7,8 @@
|
||||
<!-- Permission untuk notifikasi (opsional, untuk reminder minum) -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<!-- Require step counter sensor -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.sensor.stepcounter"
|
||||
|
||||
259
app/src/main/java/sensor/HybridStepManager.kt
Normal file
259
app/src/main/java/sensor/HybridStepManager.kt
Normal file
@ -0,0 +1,259 @@
|
||||
package com.example.stepdrink.sensor
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* ImprovedStepManager - Fixed version untuk Samsung dan device lain
|
||||
*
|
||||
* Improvements:
|
||||
* - Better sensor detection
|
||||
* - Automatic fallback
|
||||
* - More logging
|
||||
* - Samsung compatibility fixes
|
||||
*/
|
||||
class ImprovedStepManager(context: Context) : SensorEventListener {
|
||||
|
||||
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
|
||||
// Try both sensors
|
||||
private val stepCounterSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
|
||||
private val stepDetectorSensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
|
||||
|
||||
// Flow states
|
||||
private val _totalSteps = MutableStateFlow(0)
|
||||
val totalSteps: StateFlow<Int> = _totalSteps.asStateFlow()
|
||||
|
||||
private val _stepDetected = MutableStateFlow(false)
|
||||
val stepDetected: StateFlow<Boolean> = _stepDetected.asStateFlow()
|
||||
|
||||
private val _isWalking = MutableStateFlow(false)
|
||||
val isWalking: StateFlow<Boolean> = _isWalking.asStateFlow()
|
||||
|
||||
private val _sensorStatus = MutableStateFlow("")
|
||||
val sensorStatus: StateFlow<String> = _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")
|
||||
}
|
||||
}
|
||||
@ -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,6 +173,7 @@ fun StepsScreen(
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@ -104,8 +181,36 @@ fun StepsScreen(
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// Debug Info Card (collapsible)
|
||||
if (showDebugInfo) {
|
||||
item {
|
||||
// Main Steps Card dengan Gradient
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||
)
|
||||
) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
// Main Steps Card
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
@ -130,8 +235,7 @@ fun StepsScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Animated Icon
|
||||
if (isTracking) {
|
||||
if (isTracking && isWalking) {
|
||||
AnimatedWalkingIcon()
|
||||
} else {
|
||||
Icon(
|
||||
@ -144,8 +248,9 @@ fun StepsScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Display sensor steps in real-time when tracking
|
||||
Text(
|
||||
text = "${todaySteps?.steps ?: 0}",
|
||||
text = if (isTracking) "$sensorSteps" else "${todaySteps?.steps ?: 0}",
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
@ -161,7 +266,8 @@ fun StepsScreen(
|
||||
|
||||
LinearProgressIndicator(
|
||||
progress = {
|
||||
(todaySteps?.steps ?: 0).toFloat() / stepGoal.toFloat()
|
||||
val steps = if (isTracking) sensorSteps else (todaySteps?.steps ?: 0)
|
||||
steps.toFloat() / stepGoal.toFloat()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@ -190,11 +296,16 @@ fun StepsScreen(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PulsingDot()
|
||||
Icon(
|
||||
imageVector = Icons.Default.Sensors,
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
"Sedang Tracking: $sensorSteps langkah",
|
||||
"Tracking Aktif • Real-time",
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
@ -205,7 +316,7 @@ fun StepsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// TAMBAH INI - Calories Card
|
||||
// Calories Card
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@ -271,7 +382,7 @@ fun StepsScreen(
|
||||
color = Color.White.copy(alpha = 0.8f)
|
||||
)
|
||||
Text(
|
||||
text = "≈ ${String.format("%.2f", todayCalories / 7.7)} gram lemak",
|
||||
text = "≈ ${String.format("%.1f", todayCalories / 7.7)}g lemak",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = Color.White.copy(alpha = 0.8f)
|
||||
)
|
||||
@ -281,7 +392,7 @@ fun StepsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Motivational Message - TAMBAH INI
|
||||
// Rest of the items (Motivational message, history, etc.)
|
||||
item {
|
||||
val message = when {
|
||||
(todaySteps?.steps ?: 0) >= stepGoal -> "🎉 Luar biasa! Target tercapai!"
|
||||
@ -390,6 +501,29 @@ fun StepsScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
) {
|
||||
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))
|
||||
)
|
||||
}
|
||||
@ -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<Int>
|
||||
@ -24,11 +24,19 @@ class StepViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val todaySteps: StateFlow<StepRecord?>
|
||||
val last7DaysSteps: StateFlow<List<StepRecord>>
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user