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 6a995d6561
32 changed files with 2841 additions and 9 deletions

View File

@@ -0,0 +1,123 @@
package it.danieleverducci.lunatracker.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.fragment.app.Fragment
import it.danieleverducci.lunatracker.R
import it.danieleverducci.lunatracker.StatisticsActivity
import utils.DailySummary
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class DailySummaryFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_daily_summary, 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()
// Get today's summary and average for comparison
val todaySummary = calculator.getTodaySummary()
val feedingStats = calculator.getFeedingStats(period)
val sleepStats = calculator.getSleepStats(period)
val diaperStats = calculator.getDiaperStats(period)
// Date header
val dateFormat = SimpleDateFormat("EEEE, d MMMM", Locale.getDefault())
view.findViewById<TextView>(R.id.date_header).text = dateFormat.format(Date())
// Bottle summary
val bottleSummary = view.findViewById<TextView>(R.id.bottle_summary)
val bottleProgress = view.findViewById<ProgressBar>(R.id.bottle_progress)
val avgBottle = feedingStats.avgBottleMlPerDay.toInt()
bottleSummary.text = "${todaySummary.totalBottleMl} ml (${todaySummary.bottleCount}×) | Ø $avgBottle ml"
if (avgBottle > 0) {
bottleProgress.max = (avgBottle * 1.5).toInt()
bottleProgress.progress = todaySummary.totalBottleMl
}
// Breastfeeding summary
val breastfeedingContainer = view.findViewById<LinearLayout>(R.id.breastfeeding_container)
val breastfeedingSummary = view.findViewById<TextView>(R.id.breastfeeding_summary)
val breastfeedingProgress = view.findViewById<ProgressBar>(R.id.breastfeeding_progress)
if (todaySummary.breastfeedingCount > 0 || feedingStats.avgBreastfeedingMinPerDay > 0) {
breastfeedingContainer.visibility = View.VISIBLE
val avgBf = feedingStats.avgBreastfeedingMinPerDay.toInt()
breastfeedingSummary.text = "${todaySummary.totalBreastfeedingMin} min (${todaySummary.breastfeedingCount}×) | Ø $avgBf min"
if (avgBf > 0) {
breastfeedingProgress.max = (avgBf * 1.5).toInt()
breastfeedingProgress.progress = todaySummary.totalBreastfeedingMin
}
} else {
breastfeedingContainer.visibility = View.GONE
}
// Sleep summary
val sleepSummary = view.findViewById<TextView>(R.id.sleep_summary)
val sleepProgress = view.findViewById<ProgressBar>(R.id.sleep_progress)
val avgSleepMin = sleepStats.avgSleepMinPerDay.toInt()
val todaySleepHours = todaySummary.totalSleepMin / 60f
val avgSleepHours = avgSleepMin / 60f
sleepSummary.text = String.format(Locale.getDefault(), "%.1f h (%d×) | Ø %.1f h",
todaySleepHours, todaySummary.sleepCount, avgSleepHours)
if (avgSleepMin > 0) {
sleepProgress.max = (avgSleepMin * 1.5).toInt()
sleepProgress.progress = todaySummary.totalSleepMin
}
// Diaper summaries
val pooSummary = view.findViewById<TextView>(R.id.poo_summary)
val peeSummary = view.findViewById<TextView>(R.id.pee_summary)
pooSummary.text = String.format(Locale.getDefault(), "%d× | Ø %.1f",
todaySummary.diaperPooCount, diaperStats.avgPooPerDay)
peeSummary.text = String.format(Locale.getDefault(), "%d× | Ø %.1f",
todaySummary.diaperPeeCount, diaperStats.avgPeePerDay)
// Health card (weight/temperature)
val healthCard = view.findViewById<LinearLayout>(R.id.health_card)
val weightSummary = view.findViewById<TextView>(R.id.weight_summary)
val tempSummary = view.findViewById<TextView>(R.id.temperature_summary)
if (todaySummary.latestWeight != null || todaySummary.latestTemperature != null) {
healthCard.visibility = View.VISIBLE
if (todaySummary.latestWeight != null) {
val weightKg = todaySummary.latestWeight / 1000f
weightSummary.text = "⚖️ ${String.format(Locale.getDefault(), "%.2f kg", weightKg)}"
weightSummary.visibility = View.VISIBLE
} else {
weightSummary.visibility = View.GONE
}
if (todaySummary.latestTemperature != null) {
val tempC = todaySummary.latestTemperature / 10f
tempSummary.text = "🌡️ ${String.format(Locale.getDefault(), "%.1f °C", tempC)}"
tempSummary.visibility = View.VISIBLE
} else {
tempSummary.visibility = View.GONE
}
} else {
healthCard.visibility = View.GONE
}
}
}