Add sleep tracking, statistics module and backup features

Features:
- Sleep tracking with timer and manual duration input
- Statistics module with 5 tabs (daily summary, feeding, diapers, sleep, growth)
- Export/Import backup functionality in settings
- Complete German, French and Italian translations
This commit is contained in:
2026-01-08 09:33:36 +01:00
parent 587fc5d3e3
commit 2355dd4390
32 changed files with 2841 additions and 9 deletions

View File

@@ -0,0 +1,139 @@
package it.danieleverducci.lunatracker.fragments
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import it.danieleverducci.lunatracker.R
import it.danieleverducci.lunatracker.StatisticsActivity
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class GrowthStatsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_growth_stats, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateUI(view)
}
private fun updateUI(view: View) {
val activity = activity as? StatisticsActivity ?: return
val calculator = activity.getCalculator()
val weightHistory = calculator.getWeightHistory()
val noDataMessage = view.findViewById<TextView>(R.id.no_data_message)
if (weightHistory.isEmpty()) {
noDataMessage.visibility = View.VISIBLE
view.findViewById<View>(R.id.chart_container).visibility = View.GONE
view.findViewById<View>(R.id.chart_labels).visibility = View.GONE
view.findViewById<TextView>(R.id.current_weight).visibility = View.GONE
view.findViewById<TextView>(R.id.weight_gain_week).visibility = View.GONE
view.findViewById<TextView>(R.id.weight_gain_month).visibility = View.GONE
return
}
noDataMessage.visibility = View.GONE
// Draw weight chart (line chart approximated with bars)
val chartContainer = view.findViewById<LinearLayout>(R.id.chart_container)
val chartLabels = view.findViewById<LinearLayout>(R.id.chart_labels)
chartContainer.removeAllViews()
chartLabels.removeAllViews()
val recentWeights = weightHistory.takeLast(10) // Show last 10 measurements
val minWeight = recentWeights.minOfOrNull { it.weightGrams } ?: 0
val maxWeight = recentWeights.maxOfOrNull { it.weightGrams } ?: 1
val weightRange = (maxWeight - minWeight).coerceAtLeast(100) // At least 100g range
val dateFormat = SimpleDateFormat("d/M", Locale.getDefault())
for (point in recentWeights) {
// Bar container
val barContainer = LinearLayout(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
orientation = LinearLayout.VERTICAL
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
setPadding(4, 0, 4, 0)
}
// Calculate relative height (showing weight above minimum)
val relativeWeight = point.weightGrams - minWeight + (weightRange * 0.1).toInt()
val barHeight = (relativeWeight.toFloat() / (weightRange * 1.2) * 100).toInt()
val bar = View(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
(barHeight * resources.displayMetrics.density).toInt()
)
setBackgroundColor(resources.getColor(R.color.accent, null))
}
barContainer.addView(bar)
// Weight value on top
val weightLabel = TextView(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val kg = point.weightGrams / 1000f
text = String.format(Locale.getDefault(), "%.1f", kg)
textSize = 8f
gravity = Gravity.CENTER
}
barContainer.addView(weightLabel, 0)
chartContainer.addView(barContainer)
// Date label
val label = TextView(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
text = dateFormat.format(Date(point.time * 1000))
textSize = 10f
gravity = Gravity.CENTER
}
chartLabels.addView(label)
}
// Current weight
val currentWeight = weightHistory.lastOrNull()
if (currentWeight != null) {
val kg = currentWeight.weightGrams / 1000f
view.findViewById<TextView>(R.id.current_weight).text =
getString(R.string.stats_current_weight, String.format(Locale.getDefault(), "%.2f kg", kg))
}
// Weight gain calculations
val gainWeek = calculator.getWeightGainForDays(7)
val gainMonth = calculator.getWeightGainForDays(30)
val weekView = view.findViewById<TextView>(R.id.weight_gain_week)
val monthView = view.findViewById<TextView>(R.id.weight_gain_month)
if (gainWeek != null) {
weekView.text = getString(R.string.stats_weight_gain_week, gainWeek)
weekView.visibility = View.VISIBLE
} else {
weekView.visibility = View.GONE
}
if (gainMonth != null) {
monthView.text = getString(R.string.stats_weight_gain_month, gainMonth)
monthView.visibility = View.VISIBLE
} else {
monthView.visibility = View.GONE
}
}
}