7 Commits

Author SHA1 Message Date
99c7e5f94d LunaEvent: add sleep event 2025-11-14 22:00:05 +01:00
aa2347f802 MainActivity: allow amount for poo and pee events
An unspecified amount has also been added
to have the same semantics as before.

During these actions, the strings for title
and description of dialogs have been cleaned up.
2025-11-14 21:59:09 +01:00
367f092ff3 strings: rename scale to weight in identifiers 2025-11-14 21:59:09 +01:00
d258fcd1cb LunaEvent: remove quantity when the value is invalid 2025-11-14 21:59:09 +01:00
c0f90b55bc notes: add icons to use previous/next event as template 2025-11-14 21:59:09 +01:00
f34d7c4291 MainActivity: preset quantity of bottle, weight and temperature
Use the quantity of previous events
to initialize new events.
2025-11-14 21:59:09 +01:00
d4a80c275a events: allow editing of all used values
1. Allow to change the date/time and
other relevant values of an event
on creation and after it was created.

2. Harmonize layout file names and
variable names.
2025-11-14 21:59:05 +01:00
10 changed files with 219 additions and 20 deletions

View File

@@ -85,7 +85,7 @@ class MainActivity : AppCompatActivity() {
showAddLogbookDialog(true) showAddLogbookDialog(true)
} }
findViewById<View>(R.id.button_bottle).setOnClickListener { findViewById<View>(R.id.button_bottle).setOnClickListener {
addBabyBottleEvent() addBabyBottleEvent(LunaEvent(LunaEvent.TYPE_BABY_BOTTLE))
} }
findViewById<View>(R.id.button_food).setOnClickListener { findViewById<View>(R.id.button_food).setOnClickListener {
addNoteEvent(LunaEvent(LunaEvent.TYPE_FOOD)) addNoteEvent(LunaEvent(LunaEvent.TYPE_FOOD))
@@ -195,8 +195,8 @@ class MainActivity : AppCompatActivity() {
return logbook?.logs ?: arrayListOf() return logbook?.logs ?: arrayListOf()
} }
fun addBabyBottleEvent() { fun addBabyBottleEvent(event: LunaEvent) {
val event = LunaEvent(LunaEvent.TYPE_BABY_BOTTLE) setToPreviousQuantity(event)
askBabyBottleContent(event, true) { askBabyBottleContent(event, true) {
saveEvent(event) saveEvent(event)
} }
@@ -239,6 +239,7 @@ class MainActivity : AppCompatActivity() {
} }
fun addWeightEvent(event: LunaEvent) { fun addWeightEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askWeightValue(event, true) { saveEvent(event) } askWeightValue(event, true) { saveEvent(event) }
} }
@@ -282,6 +283,7 @@ class MainActivity : AppCompatActivity() {
} }
fun addTemperatureEvent(event: LunaEvent) { fun addTemperatureEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askTemperatureValue(event, true) { saveEvent(event) } askTemperatureValue(event, true) { saveEvent(event) }
} }
@@ -328,7 +330,7 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun datePickerHelper(time: Long, dateTextView: TextView): Calendar { fun datePickerHelper(time: Long, dateTextView: TextView, onChange: () -> Unit = {}): Calendar {
dateTextView.text = DateUtils.formatDateTime(time) dateTextView.text = DateUtils.formatDateTime(time)
val dateTime = Calendar.getInstance() val dateTime = Calendar.getInstance()
@@ -347,6 +349,7 @@ class MainActivity : AppCompatActivity() {
{ _, hour, minute -> { _, hour, minute ->
dateTime.set(year, month, day, hour, minute) dateTime.set(year, month, day, hour, minute)
dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000) dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000)
onChange.invoke()
}, },
startHour, startHour,
startMinute, startMinute,
@@ -369,6 +372,74 @@ class MainActivity : AppCompatActivity() {
saveLogbook() saveLogbook()
} }
fun addSleepEvent(event: LunaEvent) {
askSleepValue(event) { saveEvent(event) }
}
fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
val fromTextView = dialogView.findViewById<TextView>(R.id.dialog_date_from)
val toTextView = dialogView.findViewById<TextView>(R.id.dialog_date_to)
val durationTextView = dialogView.findViewById<TextView>(R.id.dialog_date_duration)
var pickedFromTime = Calendar.getInstance()
var pickedToTime = Calendar.getInstance()
fun isValidTime(fromSeconds: Long, toSeconds: Long): Boolean {
if (fromSeconds < toSeconds) {
val durationSeconds = toSeconds - fromSeconds
// sleep between 0 seconds and 12 hours
return durationSeconds > 0 && durationSeconds < (12 * 60 * 60)
} else {
return false
}
}
val onDateChange = {
val fromSeconds = pickedFromTime.time.time / 1000
val toSeconds = pickedToTime.time.time / 1000
durationTextView.text = DateUtils.formatTimeDuration(applicationContext, toSeconds - fromSeconds)
if (isValidTime(fromSeconds, toSeconds)) {
// valid duration: set default color
durationTextView.setTextColor(durationTextView.textColors.defaultColor)
} else {
// invalid duration: set danger color
durationTextView.setTextColor(ContextCompat.getColor(this, R.color.danger))
}
}
pickedFromTime = datePickerHelper(event.time, fromTextView, onDateChange)
pickedToTime = datePickerHelper(event.time + event.quantity, toTextView, onDateChange)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val fromSeconds = pickedFromTime.time.time / 1000
val toSeconds = pickedToTime.time.time / 1000
if (isValidTime(fromSeconds, toSeconds)) {
event.time = fromSeconds
event.quantity = (toSeconds - fromSeconds).toInt()
onPositive()
} else {
Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show()
}
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
}
fun addAmountEvent(event: LunaEvent) { fun addAmountEvent(event: LunaEvent) {
askAmountValue(event, true) { saveEvent(event) } askAmountValue(event, true) { saveEvent(event) }
} }
@@ -457,13 +528,59 @@ class MainActivity : AppCompatActivity() {
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext) val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext) val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext)
val dateTV = dialogView.findViewById<TextView>(R.id.notes_date_picker) val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV) val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) { if (!showTime) {
dateTV.visibility = View.GONE dateTV.visibility = View.GONE
} }
val nextTextView = dialogView.findViewById<TextView>(R.id.notes_template_next)
val prevTextView = dialogView.findViewById<TextView>(R.id.notes_template_prev)
fun updateContent(current: LunaEvent) {
val allEvents = getAllEvents()
val prevEvent = getPreviousSameEvent(current, allEvents)
var nextEvent = getNextSameEvent(current, allEvents)
notesET.setText(current.notes)
if (useQuantity) {
qtyET.setText(current.quantity.toString())
}
if (nextEvent == null && current != event) {
nextEvent = event
}
if (nextEvent != null) {
nextTextView.setOnClickListener {
notesET.setText(nextEvent.notes)
if (useQuantity) {
qtyET.setText(nextEvent.quantity.toString())
}
updateContent(nextEvent)
}
nextTextView.alpha = 1.0f
} else {
nextTextView.setOnClickListener {}
nextTextView.alpha = 0.5f
}
if (prevEvent != null) {
prevTextView.setOnClickListener {
notesET.setText(prevEvent.notes)
if (useQuantity) {
qtyET.setText(prevEvent.quantity.toString())
}
updateContent(prevEvent)
}
prevTextView.alpha = 1.0f
} else {
prevTextView.setOnClickListener {}
prevTextView.alpha = 0.5f
}
}
notesET.setText(event.notes) notesET.setText(event.notes)
if (useQuantity) { if (useQuantity) {
@@ -472,6 +589,8 @@ class MainActivity : AppCompatActivity() {
qtyET.visibility = View.GONE qtyET.visibility = View.GONE
} }
updateContent(event)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val notes = notesET.text.toString() val notes = notesET.text.toString()
@@ -483,7 +602,7 @@ class MainActivity : AppCompatActivity() {
event.quantity = quantity event.quantity = quantity
onPositive() onPositive()
} else { } else {
Toast.makeText(applicationContext, R.string.toast_integer_error, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
} }
} else { } else {
@@ -523,6 +642,13 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun setToPreviousQuantity(event: LunaEvent) {
val prev = getPreviousSameEvent(event, getAllEvents())
if (prev != null) {
event.quantity = prev.quantity
}
}
fun getPreviousSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? { fun getPreviousSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
var previousEvent: LunaEvent? = null var previousEvent: LunaEvent? = null
for (item in items) { for (item in items) {
@@ -586,6 +712,7 @@ class MainActivity : AppCompatActivity() {
LunaEvent.TYPE_PUKE -> askAmountValue(event, false, updateValues) LunaEvent.TYPE_PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues) LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues) LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
LunaEvent.TYPE_SLEEP -> askSleepValue(event, updateValues)
} }
} }
@@ -772,7 +899,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)
loadLogbookList() loadLogbookList()
Toast.makeText(applicationContext, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show() Toast.makeText(this@MainActivity, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show()
}) })
} }
@@ -929,7 +1056,7 @@ class MainActivity : AppCompatActivity() {
setLoading(false) setLoading(false)
Toast.makeText( Toast.makeText(
applicationContext, this@MainActivity,
if (lastEventAdded != null) if (lastEventAdded != null)
R.string.toast_event_added R.string.toast_event_added
else else
@@ -991,7 +1118,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)
Toast.makeText(applicationContext, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show() Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
recyclerView.adapter?.notifyDataSetChanged() recyclerView.adapter?.notifyDataSetChanged()
savingEvent(false) savingEvent(false)
}) })
@@ -1043,6 +1170,10 @@ class MainActivity : AppCompatActivity() {
addAmountEvent(LunaEvent(LunaEvent.TYPE_PUKE)) addAmountEvent(LunaEvent(LunaEvent.TYPE_PUKE))
dismiss() dismiss()
} }
contentView.findViewById<View>(R.id.button_sleep).setOnClickListener {
addSleepEvent(LunaEvent(LunaEvent.TYPE_SLEEP))
dismiss()
}
contentView.findViewById<View>(R.id.button_colic).setOnClickListener { contentView.findViewById<View>(R.id.button_colic).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_COLIC)) addPlainEvent(LunaEvent(LunaEvent.TYPE_COLIC))
dismiss() dismiss()

View File

@@ -30,6 +30,7 @@ class LunaEvent: Comparable<LunaEvent> {
const val TYPE_FOOD = "FOOD" const val TYPE_FOOD = "FOOD"
const val TYPE_PUKE = "PUKE" const val TYPE_PUKE = "PUKE"
const val TYPE_BATH = "BATH" const val TYPE_BATH = "BATH"
const val TYPE_SLEEP = "SLEEP"
} }
private val jo: JSONObject private val jo: JSONObject
@@ -45,10 +46,12 @@ class LunaEvent: Comparable<LunaEvent> {
jo.put("type", value) jo.put("type", value)
} }
var quantity: Int var quantity: Int
get() = jo.optInt("quantity", -1) get() = jo.optInt("quantity")
set(value) { set(value) {
if (value >= 0) if (value > 0)
jo.put("quantity", value) jo.put("quantity", value)
else
jo.remove("quantity")
} }
var notes: String var notes: String
get(): String = jo.optString("notes") get(): String = jo.optString("notes")
@@ -109,6 +112,7 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_FOOD -> R.string.event_food_type TYPE_FOOD -> R.string.event_food_type
TYPE_PUKE -> R.string.event_puke_type TYPE_PUKE -> R.string.event_puke_type
TYPE_BATH -> R.string.event_bath_type TYPE_BATH -> R.string.event_bath_type
TYPE_SLEEP -> R.string.event_sleep_type
else -> R.string.event_unknown_type else -> R.string.event_unknown_type
} }
) )
@@ -132,6 +136,7 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_FOOD -> R.string.event_food_desc TYPE_FOOD -> R.string.event_food_desc
TYPE_PUKE -> R.string.event_puke_desc TYPE_PUKE -> R.string.event_puke_desc
TYPE_BATH -> R.string.event_bath_desc TYPE_BATH -> R.string.event_bath_desc
TYPE_SLEEP -> R.string.event_sleep_desc
else -> R.string.event_unknown_desc else -> R.string.event_unknown_desc
} }
) )
@@ -147,6 +152,7 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_DIAPERCHANGE_PEE, TYPE_DIAPERCHANGE_PEE,
TYPE_PUKE -> R.string.log_amount_dialog_description TYPE_PUKE -> R.string.log_amount_dialog_description
TYPE_WEIGHT -> R.string.log_weight_dialog_description TYPE_WEIGHT -> R.string.log_weight_dialog_description
TYPE_SLEEP -> R.string.log_sleep_dialog_description
else -> R.string.log_unknown_dialog_description else -> R.string.log_unknown_dialog_description
} }
) )

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.util.Log import android.util.Log
import it.danieleverducci.lunatracker.R import it.danieleverducci.lunatracker.R
import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.entities.LunaEvent
import utils.DateUtils.Companion.formatTimeDuration
import java.text.NumberFormat import java.text.NumberFormat
class NumericUtils (val context: Context) { class NumericUtils (val context: Context) {
@@ -63,7 +64,7 @@ class NumericUtils (val context: Context) {
fun formatEventQuantity(event: LunaEvent): String { fun formatEventQuantity(event: LunaEvent): String {
val formatted = StringBuilder() val formatted = StringBuilder()
if (event.quantity >= 0) { if (event.quantity > 0) {
formatted.append(when (event.type) { formatted.append(when (event.type) {
LunaEvent.TYPE_TEMPERATURE -> LunaEvent.TYPE_TEMPERATURE ->
(event.quantity / 10.0f).toString() (event.quantity / 10.0f).toString()
@@ -76,6 +77,7 @@ class NumericUtils (val context: Context) {
return "" return ""
} }
} }
LunaEvent.TYPE_SLEEP -> formatTimeDuration(context, event.quantity.toLong())
else -> else ->
event.quantity event.quantity
}) })

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<Spinner
android:id="@+id/dialog_amount_value"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"/>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"/>
</LinearLayout>

View File

@@ -7,7 +7,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:orientation="horizontal"> android:orientation="horizontal">
@@ -30,6 +30,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginEnd="20dp"/> android:layout_marginTop="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -24,8 +24,31 @@
android:hint="@string/log_notes_dialog_note_hint" android:hint="@string/log_notes_dialog_note_hint"
android:background="@drawable/textview_background"/> android:background="@drawable/textview_background"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:gravity="end"
android:orientation="horizontal">
<TextView <TextView
android:id="@+id/notes_date_picker" android:id="@+id/notes_template_prev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18dp"
android:text="⬅️"/>
<TextView
android:id="@+id/notes_template_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18dp"
android:text="➡️"/>
</LinearLayout>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"

View File

@@ -3,14 +3,14 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/dialog_date_picker" android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginTop="20dp"/>
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -28,7 +28,6 @@
android:id="@+id/dialog_date_picker" android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_marginTop="20dp"/>
android:layout_marginEnd="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -49,6 +49,16 @@
style="@style/OverflowMenuText" style="@style/OverflowMenuText"
android:text="@string/overflow_event_puke"/> android:text="@string/overflow_event_puke"/>
<TextView
android:id="@+id/button_sleep"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_sleep"/>
<TextView <TextView
android:id="@+id/button_colic" android:id="@+id/button_colic"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -18,6 +18,7 @@
<string name="event_colic_type" translatable="false">💨</string> <string name="event_colic_type" translatable="false">💨</string>
<string name="event_puke_type" translatable="false">🤮</string> <string name="event_puke_type" translatable="false">🤮</string>
<string name="event_bath_type" translatable="false">🛁</string> <string name="event_bath_type" translatable="false">🛁</string>
<string name="event_sleep_type" translatable="false">💤</string>
<string name="event_unknown_type" translatable="false">\?</string> <string name="event_unknown_type" translatable="false">\?</string>
<string name="event_bottle_desc">Baby bottle</string> <string name="event_bottle_desc">Baby bottle</string>
@@ -35,6 +36,7 @@
<string name="event_colic_desc">Gaseous colic</string> <string name="event_colic_desc">Gaseous colic</string>
<string name="event_puke_desc">Puke</string> <string name="event_puke_desc">Puke</string>
<string name="event_bath_desc">Bath</string> <string name="event_bath_desc">Bath</string>
<string name="event_sleep_desc">Sleep</string>
<string name="event_unknown_desc"></string> <string name="event_unknown_desc"></string>
<string name="overflow_event_weight">⚖️ Weight</string> <string name="overflow_event_weight">⚖️ Weight</string>
@@ -44,12 +46,14 @@
<string name="overflow_event_temperature">🌡️ Temperature</string> <string name="overflow_event_temperature">🌡️ Temperature</string>
<string name="overflow_event_colic">💨 Gaseous colic</string> <string name="overflow_event_colic">💨 Gaseous colic</string>
<string name="overflow_event_puke">🤮 Puke</string> <string name="overflow_event_puke">🤮 Puke</string>
<string name="overflow_event_sleep">💤 Sleep</string>
<string name="overflow_event_bath">🛁 Bath</string> <string name="overflow_event_bath">🛁 Bath</string>
<string name="toast_event_added">Event logged</string> <string name="toast_event_added">Event logged</string>
<string name="toast_logbook_saved">Logbook saved</string> <string name="toast_logbook_saved">Logbook saved</string>
<string name="toast_event_add_error">Unable to log the event</string> <string name="toast_event_add_error">Unable to log the event</string>
<string name="toast_integer_error">Invalid value. Insert an integer.</string> <string name="toast_integer_error">Invalid value. Insert an integer.</string>
<string name="toast_date_error">Invalid date.</string>
<string name="now">now</string> <string name="now">now</string>
<string name="second_ago">sec</string> <string name="second_ago">sec</string>
@@ -112,6 +116,7 @@
<string name="log_temperature_dialog_description">Select the temperature:</string> <string name="log_temperature_dialog_description">Select the temperature:</string>
<string name="log_unknown_dialog_description"></string> <string name="log_unknown_dialog_description"></string>
<string name="log_weight_dialog_description">Insert the weight:</string> <string name="log_weight_dialog_description">Insert the weight:</string>
<string name="log_sleep_dialog_description">Select sleep range:</string>
<string name="measurement_unit_liquid_base_metric" translatable="false">ml</string> <string name="measurement_unit_liquid_base_metric" translatable="false">ml</string>
<string name="measurement_unit_weight_base_metric" translatable="false">g</string> <string name="measurement_unit_weight_base_metric" translatable="false">g</string>