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,124 @@
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 utils.DateUtils
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class DiaperStatsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_diaper_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 period = activity.getSelectedPeriod()
val stats = calculator.getDiaperStats(period)
// Draw stacked bar chart
val chartContainer = view.findViewById<LinearLayout>(R.id.chart_container)
val chartLabels = view.findViewById<LinearLayout>(R.id.chart_labels)
chartContainer.removeAllViews()
chartLabels.removeAllViews()
val sortedDays = stats.dailyPooCount.keys.sorted().takeLast(period)
var maxValue = 1
for (day in sortedDays) {
val total = (stats.dailyPooCount[day] ?: 0) + (stats.dailyPeeCount[day] ?: 0)
if (total > maxValue) maxValue = total
}
val dateFormat = SimpleDateFormat("E", Locale.getDefault())
for (day in sortedDays) {
val pooCount = stats.dailyPooCount[day] ?: 0
val peeCount = stats.dailyPeeCount[day] ?: 0
val total = pooCount + peeCount
// 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)
}
// Pee bar (lighter, on bottom)
if (peeCount > 0) {
val peeHeight = (peeCount.toFloat() / maxValue * 100).toInt()
val peeBar = View(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
(peeHeight * resources.displayMetrics.density).toInt()
)
setBackgroundColor(0x66FFE68F.toInt()) // Semi-transparent accent
}
barContainer.addView(peeBar, 0)
}
// Poo bar (solid, on top)
if (pooCount > 0) {
val pooHeight = (pooCount.toFloat() / maxValue * 100).toInt()
val pooBar = View(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
(pooHeight * resources.displayMetrics.density).toInt()
)
setBackgroundColor(resources.getColor(R.color.accent, null))
}
barContainer.addView(pooBar, 0)
}
chartContainer.addView(barContainer)
// Label
val label = TextView(requireContext()).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
text = dateFormat.format(Date(day * 1000))
textSize = 10f
gravity = Gravity.CENTER
}
chartLabels.addView(label)
}
// Summary stats
view.findViewById<TextView>(R.id.avg_diapers).text =
getString(R.string.stats_avg_diapers, stats.avgDiapersPerDay)
view.findViewById<TextView>(R.id.avg_poo).text =
getString(R.string.stats_avg_poo, stats.avgPooPerDay)
view.findViewById<TextView>(R.id.avg_pee).text =
getString(R.string.stats_avg_pee, stats.avgPeePerDay)
// Last poo
val lastPoo = view.findViewById<TextView>(R.id.last_poo)
if (stats.lastPooTime != null) {
val timeAgo = DateUtils.formatTimeAgo(requireContext(), stats.lastPooTime)
lastPoo.text = getString(R.string.stats_last_poo, timeAgo)
lastPoo.visibility = View.VISIBLE
} else {
lastPoo.visibility = View.GONE
}
}
}