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:
@@ -76,6 +76,12 @@ class MainActivity : AppCompatActivity() {
|
||||
var bfTimerHandler: Handler? = null
|
||||
var bfTimerRunnable: Runnable? = null
|
||||
|
||||
// Sleep timer state
|
||||
var sleepTimerStartTime: Long = 0
|
||||
var sleepTimerDialog: AlertDialog? = null
|
||||
var sleepTimerHandler: Handler? = null
|
||||
var sleepTimerRunnable: Runnable? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -134,6 +140,9 @@ class MainActivity : AppCompatActivity() {
|
||||
findViewById<View>(R.id.button_settings).setOnClickListener {
|
||||
showSettings()
|
||||
}
|
||||
findViewById<View>(R.id.button_statistics).setOnClickListener {
|
||||
showStatistics()
|
||||
}
|
||||
findViewById<View>(R.id.button_no_connection_retry).setOnClickListener {
|
||||
// This may happen at start, when logbook is still null: better ask the logbook list
|
||||
loadLogbookList()
|
||||
@@ -164,6 +173,12 @@ class MainActivity : AppCompatActivity() {
|
||||
startActivity(i)
|
||||
}
|
||||
|
||||
fun showStatistics() {
|
||||
val i = Intent(this, StatisticsActivity::class.java)
|
||||
i.putExtra(StatisticsActivity.EXTRA_LOGBOOK_NAME, logbook?.name ?: "")
|
||||
startActivity(i)
|
||||
}
|
||||
|
||||
fun showLogbook() {
|
||||
// Show logbook
|
||||
if (logbook == null)
|
||||
@@ -204,6 +219,9 @@ class MainActivity : AppCompatActivity() {
|
||||
// Check for ongoing breastfeeding timer
|
||||
restoreBreastfeedingTimerIfNeeded()
|
||||
|
||||
// Check for ongoing sleep timer
|
||||
restoreSleepTimerIfNeeded()
|
||||
|
||||
if (logbook != null) {
|
||||
// Already running: reload data for currently selected logbook
|
||||
loadLogbook(logbook!!.name)
|
||||
@@ -220,6 +238,10 @@ class MainActivity : AppCompatActivity() {
|
||||
bfTimerRunnable?.let { bfTimerHandler?.removeCallbacks(it) }
|
||||
bfTimerDialog?.dismiss()
|
||||
|
||||
// Clean up sleep timer UI (state is preserved in SharedPreferences)
|
||||
sleepTimerRunnable?.let { sleepTimerHandler?.removeCallbacks(it) }
|
||||
sleepTimerDialog?.dismiss()
|
||||
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
@@ -452,6 +474,117 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep timer methods
|
||||
fun startSleepTimer() {
|
||||
// Check if timer already running
|
||||
if (sleepTimerStartTime > 0) {
|
||||
Toast.makeText(this, R.string.sleep_timer_already_running, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
// Save timer state
|
||||
sleepTimerStartTime = System.currentTimeMillis()
|
||||
saveSleepTimerState()
|
||||
|
||||
// Show timer dialog
|
||||
showSleepTimerDialog()
|
||||
}
|
||||
|
||||
fun showSleepTimerDialog() {
|
||||
val d = AlertDialog.Builder(this)
|
||||
val dialogView = layoutInflater.inflate(R.layout.sleep_timer_dialog, null)
|
||||
d.setTitle(R.string.sleep_timer_title)
|
||||
d.setView(dialogView)
|
||||
d.setCancelable(false)
|
||||
|
||||
val timerDisplay = dialogView.findViewById<TextView>(R.id.sleep_timer_display)
|
||||
|
||||
// Set up timer updates
|
||||
sleepTimerHandler = Handler(mainLooper)
|
||||
sleepTimerRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
val elapsed = (System.currentTimeMillis() - sleepTimerStartTime) / 1000
|
||||
val hours = elapsed / 3600
|
||||
val minutes = (elapsed % 3600) / 60
|
||||
val seconds = elapsed % 60
|
||||
timerDisplay.text = if (hours > 0) {
|
||||
String.format("%d:%02d:%02d", hours, minutes, seconds)
|
||||
} else {
|
||||
String.format("%02d:%02d", minutes, seconds)
|
||||
}
|
||||
sleepTimerHandler?.postDelayed(this, 1000)
|
||||
}
|
||||
}
|
||||
sleepTimerHandler?.post(sleepTimerRunnable!!)
|
||||
|
||||
d.setPositiveButton(R.string.sleep_timer_stop) { _, _ ->
|
||||
stopSleepTimer()
|
||||
}
|
||||
d.setNegativeButton(android.R.string.cancel) { dialogInterface, _ ->
|
||||
cancelSleepTimer()
|
||||
dialogInterface.dismiss()
|
||||
}
|
||||
|
||||
sleepTimerDialog = d.create()
|
||||
sleepTimerDialog?.show()
|
||||
}
|
||||
|
||||
fun stopSleepTimer() {
|
||||
sleepTimerHandler?.removeCallbacks(sleepTimerRunnable!!)
|
||||
|
||||
val durationMillis = System.currentTimeMillis() - sleepTimerStartTime
|
||||
val durationMinutes = Math.max(1, (durationMillis / 60000).toInt()) // Minimum 1 minute
|
||||
|
||||
clearSleepTimerState()
|
||||
|
||||
logEvent(LunaEvent(LunaEvent.TYPE_SLEEP, durationMinutes))
|
||||
}
|
||||
|
||||
fun cancelSleepTimer() {
|
||||
sleepTimerHandler?.removeCallbacks(sleepTimerRunnable!!)
|
||||
clearSleepTimerState()
|
||||
}
|
||||
|
||||
fun askSleepDuration() {
|
||||
val d = AlertDialog.Builder(this)
|
||||
val dialogView = layoutInflater.inflate(R.layout.sleep_duration_dialog, null)
|
||||
d.setTitle(R.string.sleep_duration_title)
|
||||
d.setMessage(R.string.sleep_duration_description)
|
||||
d.setView(dialogView)
|
||||
|
||||
val numberPicker = dialogView.findViewById<NumberPicker>(R.id.sleep_duration_picker)
|
||||
numberPicker.minValue = 1
|
||||
numberPicker.maxValue = 180 // Up to 3 hours
|
||||
numberPicker.value = 30 // Default 30 minutes
|
||||
numberPicker.wrapSelectorWheel = false
|
||||
|
||||
d.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
logEvent(LunaEvent(LunaEvent.TYPE_SLEEP, numberPicker.value))
|
||||
}
|
||||
d.setNegativeButton(android.R.string.cancel) { dialogInterface, _ ->
|
||||
dialogInterface.dismiss()
|
||||
}
|
||||
d.create().show()
|
||||
}
|
||||
|
||||
fun saveSleepTimerState() {
|
||||
LocalSettingsRepository(this).saveSleepTimer(sleepTimerStartTime)
|
||||
}
|
||||
|
||||
fun clearSleepTimerState() {
|
||||
sleepTimerStartTime = 0
|
||||
sleepTimerDialog = null
|
||||
LocalSettingsRepository(this).clearSleepTimer()
|
||||
}
|
||||
|
||||
fun restoreSleepTimerIfNeeded() {
|
||||
val startTime = LocalSettingsRepository(this).loadSleepTimer()
|
||||
if (startTime > 0) {
|
||||
sleepTimerStartTime = startTime
|
||||
showSleepTimerDialog()
|
||||
}
|
||||
}
|
||||
|
||||
fun askToTrimLogbook() {
|
||||
val d = AlertDialog.Builder(this)
|
||||
d.setTitle(R.string.trim_logbook_dialog_title)
|
||||
@@ -539,24 +672,32 @@ class MainActivity : AppCompatActivity() {
|
||||
}, startYear, startMonth, startDay).show()
|
||||
}
|
||||
|
||||
// Make quantity editable for breastfeeding events
|
||||
// Make quantity editable for breastfeeding and sleep events
|
||||
val quantityTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity)
|
||||
if (event.type in listOf(
|
||||
val isBreastfeeding = event.type in listOf(
|
||||
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE,
|
||||
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE,
|
||||
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE
|
||||
) && event.quantity > 0) {
|
||||
)
|
||||
val isSleep = event.type == LunaEvent.TYPE_SLEEP
|
||||
if ((isBreastfeeding || isSleep) && event.quantity > 0) {
|
||||
quantityTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_edit, 0)
|
||||
quantityTextView.compoundDrawableTintList = ColorStateList.valueOf(getColor(R.color.accent))
|
||||
quantityTextView.setOnClickListener {
|
||||
val pickerDialog = AlertDialog.Builder(this@MainActivity)
|
||||
val pickerView = layoutInflater.inflate(R.layout.breastfeeding_duration_dialog, null)
|
||||
val picker = pickerView.findViewById<NumberPicker>(R.id.breastfeeding_duration_picker)
|
||||
val pickerView = if (isSleep) {
|
||||
layoutInflater.inflate(R.layout.sleep_duration_dialog, null)
|
||||
} else {
|
||||
layoutInflater.inflate(R.layout.breastfeeding_duration_dialog, null)
|
||||
}
|
||||
val picker = pickerView.findViewById<NumberPicker>(
|
||||
if (isSleep) R.id.sleep_duration_picker else R.id.breastfeeding_duration_picker
|
||||
)
|
||||
picker.minValue = 1
|
||||
picker.maxValue = 60
|
||||
picker.value = if (event.quantity > 0) event.quantity else 15
|
||||
picker.maxValue = if (isSleep) 180 else 60
|
||||
picker.value = if (event.quantity > 0) Math.min(event.quantity, picker.maxValue) else if (isSleep) 30 else 15
|
||||
|
||||
pickerDialog.setTitle(R.string.breastfeeding_duration_title)
|
||||
pickerDialog.setTitle(if (isSleep) R.string.sleep_duration_title else R.string.breastfeeding_duration_title)
|
||||
pickerDialog.setView(pickerView)
|
||||
pickerDialog.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
event.quantity = picker.value
|
||||
@@ -971,6 +1112,15 @@ class MainActivity : AppCompatActivity() {
|
||||
isOutsideTouchable = true
|
||||
val inflater = LayoutInflater.from(anchor.context)
|
||||
contentView = inflater.inflate(R.layout.more_events_popup, null)
|
||||
contentView.findViewById<View>(R.id.button_sleep).setOnClickListener {
|
||||
startSleepTimer()
|
||||
dismiss()
|
||||
}
|
||||
contentView.findViewById<View>(R.id.button_sleep).setOnLongClickListener {
|
||||
askSleepDuration()
|
||||
dismiss()
|
||||
true
|
||||
}
|
||||
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener {
|
||||
askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE))
|
||||
dismiss()
|
||||
|
||||
Reference in New Issue
Block a user