2 Commits

Author SHA1 Message Date
b271f63cb7 MainActivity: preset quantity of bottle, weight and temperature
Use the quantity of previous events
to initialize new events.
2025-11-14 09:26:16 +01:00
563e431c1d 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-13 11:40:59 +01:00
14 changed files with 495 additions and 221 deletions

View File

@@ -13,24 +13,7 @@ Dedicated to my daughter Luna.
![Screenshot](fastlane/metadata/android/en-US/images/phoneScreenshots/1.png) ![Screenshot](fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)
## Contributions ## Thanks for the valuable contributions to:
### Why isn't this hosted on GitHub?
I'm using a private git server just because I'm worried for the vast majority of open source code being hosted in a server property of Microsoft and being used to train theirs AI.
I didn't find a better option, BTW the Gitea project is working on implementing federation, so soon it will be possible to contribute using any other gitea server, selfhosted or not.
### How to contribute
The project is open to contribution, but with some limits:
- I'm sorry I can't accept AI-generated contributions. Reviewing a contribution requires time and effort from my side, while generating code with AI requires very little time and produces non reliable code that must be reviewed in detail. This is effectively shifting the work on my side, and in a forced way. If you feel you need a feature but you're not able to implement it by yourself, I prefer you to create an issue in the repository so I can implement it when I can, in a more mantainable way.
- I prefer to make project-wide changes (i.e. updating Android target, app name and icon, release number...) by myself.
To contribute, you'll have to create an account on this git instance. Unfortunately, I had to disable registration to avoid huge waves of fake accounts created by bots.
You can request an account writing to daniele.verducci@ichibi.eu
### Thanks for the valuable contributions to:
- Chepycou (French translation) - Chepycou (French translation)
- Daniel Neubauer (German translation) - Daniel Neubauer (German translation)

View File

@@ -81,34 +81,30 @@ class MainActivity : AppCompatActivity() {
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext)) recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
// Set listeners // Set listeners
findViewById<View>(R.id.logbooks_add_button).setOnClickListener { showAddLogbookDialog(true) } findViewById<View>(R.id.logbooks_add_button).setOnClickListener {
findViewById<View>(R.id.button_bottle).setOnClickListener { askBabyBottleContent() } showAddLogbookDialog(true)
findViewById<View>(R.id.button_food).setOnClickListener { askNotes(LunaEvent(LunaEvent.TYPE_FOOD)) } }
findViewById<View>(R.id.button_nipple_left).setOnClickListener { logEvent( findViewById<View>(R.id.button_bottle).setOnClickListener {
LunaEvent( addBabyBottleEvent(LunaEvent(LunaEvent.TYPE_BABY_BOTTLE))
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE }
) findViewById<View>(R.id.button_food).setOnClickListener {
) } addNoteEvent(LunaEvent(LunaEvent.TYPE_FOOD))
findViewById<View>(R.id.button_nipple_both).setOnClickListener { logEvent( }
LunaEvent( findViewById<View>(R.id.button_nipple_left).setOnClickListener {
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE))
) }
) } findViewById<View>(R.id.button_nipple_both).setOnClickListener {
findViewById<View>(R.id.button_nipple_right).setOnClickListener { logEvent( addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE))
LunaEvent( }
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE findViewById<View>(R.id.button_nipple_right).setOnClickListener {
) addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE))
) } }
findViewById<View>(R.id.button_change_poo).setOnClickListener { logEvent( findViewById<View>(R.id.button_change_poo).setOnClickListener {
LunaEvent( addPlainEvent(LunaEvent(LunaEvent.TYPE_DIAPERCHANGE_POO))
LunaEvent.TYPE_DIAPERCHANGE_POO }
) findViewById<View>(R.id.button_change_pee).setOnClickListener {
) } addPlainEvent(LunaEvent(LunaEvent.TYPE_DIAPERCHANGE_PEE))
findViewById<View>(R.id.button_change_pee).setOnClickListener { logEvent( }
LunaEvent(
LunaEvent.TYPE_DIAPERCHANGE_PEE
)
) }
val moreButton = findViewById<View>(R.id.button_more) val moreButton = findViewById<View>(R.id.button_more)
moreButton.setOnClickListener { moreButton.setOnClickListener {
showOverflowPopupWindow(moreButton) showOverflowPopupWindow(moreButton)
@@ -132,7 +128,7 @@ class MainActivity : AppCompatActivity() {
val adapter = LunaEventRecyclerAdapter(this, items) val adapter = LunaEventRecyclerAdapter(this, items)
adapter.onItemClickListener = object : LunaEventRecyclerAdapter.OnItemClickListener { adapter.onItemClickListener = object : LunaEventRecyclerAdapter.OnItemClickListener {
override fun onItemClick(event: LunaEvent) { override fun onItemClick(event: LunaEvent) {
showEventDetailDialog(event, items) showEventDetailDialog(event)
} }
} }
recyclerView.adapter = adapter recyclerView.adapter = adapter
@@ -195,118 +191,315 @@ class MainActivity : AppCompatActivity() {
super.onStop() super.onStop()
} }
fun askBabyBottleContent() { fun getAllEvents(): ArrayList<LunaEvent> {
// Show number picker dialog return logbook?.logs ?: arrayListOf()
val localSettings = LocalSettingsRepository(this) }
fun addBabyBottleEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askBabyBottleContent(event, true) {
saveEvent(event)
}
}
fun askBabyBottleContent(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.number_picker_dialog, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_bottle, null)
d.setTitle(R.string.log_bottle_dialog_title) d.setTitle(R.string.log_bottle_dialog_title)
d.setMessage(R.string.log_bottle_dialog_description) d.setMessage(R.string.log_bottle_dialog_description)
d.setView(dialogView) d.setView(dialogView)
val numberPicker = dialogView.findViewById<NumberPicker>(R.id.dialog_number_picker) val numberPicker = dialogView.findViewById<NumberPicker>(R.id.dialog_number_picker)
numberPicker.minValue = 1 // "10" numberPicker.minValue = 1 // "10"
numberPicker.maxValue = 25 // "250 numberPicker.maxValue = 25 // "250
numberPicker.displayedValues = ((10..250 step 10).map { it.toString() }.toTypedArray()) numberPicker.displayedValues = ((10..250 step 10).map { it.toString() }.toTypedArray())
numberPicker.wrapSelectorWheel = false numberPicker.wrapSelectorWheel = false
numberPicker.value = localSettings.loadBabyBottleContent() numberPicker.value = event.quantity / 10
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
logEvent(LunaEvent(LunaEvent.TYPE_BABY_BOTTLE, numberPicker.value * 10)) val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
localSettings.saveBabyBottleContent(numberPicker.value) val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
} }
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
event.time = pickedTime.time.time / 1000
event.quantity = numberPicker.value * 10
onPositive()
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create() val alertDialog = d.create()
alertDialog.show() alertDialog.show()
} }
fun askWeightValue() { fun addWeightEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askWeightValue(event, true) { saveEvent(event) }
}
fun askWeightValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
// Show number picker dialog // Show number picker dialog
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.number_edit_dialog, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_weight, null)
d.setTitle(R.string.log_weight_dialog_title) d.setTitle(R.string.log_weight_dialog_title)
d.setMessage(R.string.log_weight_dialog_description) d.setMessage(R.string.log_weight_dialog_description)
d.setView(dialogView) d.setView(dialogView)
val weightET = dialogView.findViewById<EditText>(R.id.dialog_number_edittext) val weightET = dialogView.findViewById<EditText>(R.id.dialog_number_edittext)
weightET.setText(event.quantity.toString())
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val weight = weightET.text.toString().toIntOrNull() val weight = weightET.text.toString().toIntOrNull()
if (weight != null) if (weight != null) {
logEvent(LunaEvent(LunaEvent.TYPE_WEIGHT, weight)) event.time = pickedTime.time.time / 1000
else event.quantity = weight
onPositive()
} else {
Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
} }
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create() val alertDialog = d.create()
alertDialog.show() alertDialog.show()
} }
fun askTemperatureValue() { fun addTemperatureEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askTemperatureValue(event, true) { saveEvent(event) }
}
fun askTemperatureValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
// Show number picker dialog // Show number picker dialog
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.temperature_dialog, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_temperature, null)
d.setTitle(R.string.log_temperature_dialog_title) d.setTitle(R.string.log_temperature_dialog_title)
d.setMessage(R.string.log_temperature_dialog_description) d.setMessage(R.string.log_temperature_dialog_description)
d.setView(dialogView) d.setView(dialogView)
val tempSlider = dialogView.findViewById<Slider>(R.id.dialog_temperature_value) val tempSlider = dialogView.findViewById<Slider>(R.id.dialog_temperature_value)
val range = NumericUtils(this).getValidEventQuantityRange(LunaEvent.TYPE_TEMPERATURE)!! val range = NumericUtils(this).getValidEventQuantityRange(LunaEvent.TYPE_TEMPERATURE)!!
tempSlider.valueFrom = range.first.toFloat() tempSlider.valueFrom = range.first.toFloat()
tempSlider.valueTo = range.second.toFloat() tempSlider.valueTo = range.second.toFloat()
tempSlider.value = range.third.toFloat() tempSlider.value = if (event.quantity == 0) {
val tempTextView = dialogView.findViewById<TextView>(R.id.dialog_temperature_display) range.third.toFloat() // default
tempTextView.text = range.third.toString() } else {
tempSlider.addOnChangeListener({s, v, b -> tempTextView.text = v.toString()}) event.quantity.toFloat() / 10
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val temperature = (tempSlider.value * 10).toInt() // In tenth of a grade
logEvent(LunaEvent(LunaEvent.TYPE_TEMPERATURE, temperature))
} }
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
val tempTextView = dialogView.findViewById<TextView>(R.id.dialog_temperature_display)
tempTextView.text = tempSlider.value.toString()
tempSlider.addOnChangeListener({ s, v, b -> tempTextView.text = v.toString() })
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
event.time = pickedTime.time.time / 1000
event.quantity = (tempSlider.value * 10).toInt() // temperature in tenth of a grade
onPositive()
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create() val alertDialog = d.create()
alertDialog.show() alertDialog.show()
} }
fun askPukeValue() { fun datePickerHelper(time: Long, dateTextView: TextView): Calendar {
dateTextView.text = DateUtils.formatDateTime(time)
val dateTime = Calendar.getInstance()
dateTime.time = Date(time * 1000)
dateTextView.setOnClickListener {
// Show datetime picker
val startYear = dateTime.get(Calendar.YEAR)
val startMonth = dateTime.get(Calendar.MONTH)
val startDay = dateTime.get(Calendar.DAY_OF_MONTH)
val startHour = dateTime.get(Calendar.HOUR_OF_DAY)
val startMinute = dateTime.get(Calendar.MINUTE)
DatePickerDialog(this, { _, year, month, day ->
TimePickerDialog(
this,
{ _, hour, minute ->
dateTime.set(year, month, day, hour, minute)
dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000)
},
startHour,
startMinute,
android.text.format.DateFormat.is24HourFormat(this@MainActivity)
).show()
}, startYear, startMonth, startDay).show()
}
return dateTime
}
fun saveEvent(event: LunaEvent) {
if (!getAllEvents().contains(event)) {
// new event
logEvent(event)
}
logbook?.sort()
recyclerView.adapter?.notifyDataSetChanged()
saveLogbook()
}
fun addPukeEvent(event: LunaEvent) {
askPukeValue(event, true) { saveEvent(event) }
}
fun askPukeValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.puke_dialog, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_puke, null)
d.setTitle(R.string.log_puke_dialog_title) d.setTitle(R.string.log_puke_dialog_title)
d.setMessage(R.string.log_puke_dialog_description) d.setMessage(R.string.log_puke_dialog_description)
d.setView(dialogView) d.setView(dialogView)
val spinner = dialogView.findViewById<Spinner>(R.id.dialog_puke_value) val spinner = dialogView.findViewById<Spinner>(R.id.dialog_puke_value)
spinner.adapter = ArrayAdapter.createFromResource(this, R.array.AmountLabels, android.R.layout.simple_spinner_dropdown_item) spinner.adapter = ArrayAdapter.createFromResource(
spinner.setSelection(1) this,
R.array.AmountLabels,
android.R.layout.simple_spinner_dropdown_item
)
spinner.setSelection(event.quantity - 1)
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val pos = spinner.selectedItemPosition event.time = pickedTime.time.time / 1000
logEvent(LunaEvent(LunaEvent.TYPE_PUKE, pos + 1)) event.quantity = spinner.selectedItemPosition + 1
onPositive()
dialogInterface.dismiss()
} }
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create() val alertDialog = d.create()
alertDialog.show() alertDialog.show()
} }
fun askNotes(lunaEvent: LunaEvent) { fun addPlainEvent(event: LunaEvent) {
askDateValue(event, true) { saveEvent(event) }
}
// Ask to edit events to be edited (only affects date)
fun askDateValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_notes, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_plain, null)
d.setTitle(lunaEvent.getTypeDescription(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(lunaEvent.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedDateTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
event.time = pickedDateTime.time.time / 1000
onPositive()
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
}
fun addNoteEvent(event: LunaEvent) {
askNotes(event, true) { saveEvent(event) }
}
fun askNotes(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val useQuantity = (event.type != LunaEvent.TYPE_NOTE && event.type != LunaEvent.TYPE_CUSTOM)
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_notes, null)
d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
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)
if (lunaEvent.type == LunaEvent.TYPE_NOTE || lunaEvent.type == LunaEvent.TYPE_CUSTOM)
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
notesET.setText(event.notes)
if (useQuantity) {
qtyET.setText(event.quantity.toString())
} else {
qtyET.visibility = View.GONE qtyET.visibility = View.GONE
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val qtyStr = qtyET.text.toString()
if (qtyStr.isNotEmpty()) {
val qty = qtyStr.toIntOrNull()
if (qty == null) {
Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
return@setPositiveButton
}
lunaEvent.quantity = qty
}
val notes = notesET.text.toString() val notes = notesET.text.toString()
lunaEvent.notes = notes
logEvent(lunaEvent) if (useQuantity) {
val quantity = qtyET.text.toString().toIntOrNull()
if (quantity != null) {
event.time = pickedTime.time.time / 1000
event.notes = notes
event.quantity = quantity
onPositive()
} else {
Toast.makeText(applicationContext, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
} }
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
} else {
event.time = pickedTime.time.time / 1000
event.notes = notes
onPositive()
}
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create() val alertDialog = d.create()
alertDialog.show() alertDialog.show()
} }
@@ -331,6 +524,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) {
@@ -359,55 +559,83 @@ class MainActivity : AppCompatActivity() {
return nextEvent return nextEvent
} }
fun showEventDetailDialog(event: LunaEvent, items: ArrayList<LunaEvent>) { fun showEventDetailDialog(originalEvent: LunaEvent) {
val event = LunaEvent(originalEvent)
// Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user // Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user
pauseLogbookUpdate = true pauseLogbookUpdate = true
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
d.setTitle(R.string.dialog_event_detail_title) d.setTitle(R.string.dialog_event_detail_title)
val dialogView = layoutInflater.inflate(R.layout.dialog_event_detail, null)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji).text = event.getTypeEmoji(this)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description).text = event.getTypeDescription(this)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity).text =
NumericUtils(this).formatEventQuantity(event)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes).text = event.notes
val currentDateTime = Calendar.getInstance()
currentDateTime.time = Date(event.time * 1000)
val dialogView = layoutInflater.inflate(R.layout.dialog_event_details, null)
val emojiTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji)
val descriptionTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description)
val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date) val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date)
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time)) val quantityTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity)
dateTextView.setOnClickListener { val notesTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes)
// Show datetime picker
val startYear = currentDateTime.get(Calendar.YEAR)
val startMonth = currentDateTime.get(Calendar.MONTH)
val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
val startHour = currentDateTime.get(Calendar.HOUR_OF_DAY)
val startMinute = currentDateTime.get(Calendar.MINUTE)
DatePickerDialog(this, { _, year, month, day -> emojiTextView.text = event.getTypeEmoji(this)
TimePickerDialog(this, { _, hour, minute -> descriptionTextView.text = event.getTypeDescription(this)
val pickedDateTime = Calendar.getInstance()
pickedDateTime.set(year, month, day, hour, minute) val pickedTime = datePickerHelper(event.time, dateTextView)
// Save event and move it to the right position in the logbook val updateValues = {
event.time = pickedDateTime.time.time / 1000 // Seconds since epoch quantityTextView.text = NumericUtils(this).formatEventQuantity(event)
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time)) notesTextView.text = event.notes
logbook?.sort() }
recyclerView.adapter?.notifyDataSetChanged() updateValues()
saveLogbook()
}, startHour, startMinute, android.text.format.DateFormat.is24HourFormat(this@MainActivity)).show() quantityTextView.setOnClickListener {
}, startYear, startMonth, startDay).show() when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
LunaEvent.TYPE_WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.TYPE_PUKE -> askPukeValue(event, false, updateValues)
LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
}
}
notesTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_FOOD,
LunaEvent.TYPE_MEDICINE,
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
}
} }
d.setView(dialogView) d.setView(dialogView)
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i -> dialogInterface.dismiss() }
d.setNeutralButton(R.string.dialog_event_detail_delete_button) { dialogInterface, i -> deleteEvent(event) } d.setNeutralButton(R.string.dialog_event_detail_delete_button) { dialogInterface, i ->
deleteEvent(originalEvent)
dialogInterface.dismiss()
}
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i ->
event.time = pickedTime.time.time / 1000
if (event.time != originalEvent.time
|| event.quantity != originalEvent.quantity
|| event.notes != originalEvent.notes) {
originalEvent.time = event.time
originalEvent.quantity = event.quantity
originalEvent.notes = event.notes
saveEvent(originalEvent)
}
dialogInterface.dismiss()
}
val alertDialog = d.create() val alertDialog = d.create()
alertDialog.show() alertDialog.show()
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(ContextCompat.getColor(this, R.color.danger))
alertDialog.setOnDismissListener({ alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(
ContextCompat.getColor(this, R.color.danger)
)
alertDialog.setOnDismissListener {
// Resume logbook update // Resume logbook update
pauseLogbookUpdate = false pauseLogbookUpdate = false
}) }
// show optional signature // show optional signature
if (event.signature.isNotEmpty()) { if (event.signature.isNotEmpty()) {
@@ -416,32 +644,33 @@ class MainActivity : AppCompatActivity() {
signatureTextEdit.visibility = View.VISIBLE signatureTextEdit.visibility = View.VISIBLE
} }
// create next/previous links to events of the same type val allEvents = getAllEvents()
// create link to prevent event of the same type
val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous) val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next) val previousEvent = getPreviousSameEvent(event, allEvents)
val nextEvent = getNextSameEvent(event, items)
val previousEvent = getPreviousSameEvent(event, items)
if (previousEvent != null) { if (previousEvent != null) {
val emoji = previousEvent.getTypeEmoji(applicationContext) val emoji = previousEvent.getTypeEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time) val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time)
previousTextView.text = String.format("⬅️ %s %s", emoji, time) previousTextView.text = String.format("⬅️ %s %s", emoji, time)
previousTextView.setOnClickListener { previousTextView.setOnClickListener {
alertDialog.cancel() alertDialog.cancel()
showEventDetailDialog(previousEvent, items) showEventDetailDialog(previousEvent)
} }
} else { } else {
previousTextView.visibility = View.GONE previousTextView.visibility = View.GONE
} }
// create link to next event of the same type
val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next)
val nextEvent = getNextSameEvent(event, allEvents)
if (nextEvent != null) { if (nextEvent != null) {
val emoji = nextEvent.getTypeEmoji(applicationContext) val emoji = nextEvent.getTypeEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time) val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time)
nextTextView.text = String.format("%s %s ➡️", time, emoji) nextTextView.text = String.format("%s %s ➡️", time, emoji)
nextTextView.setOnClickListener { nextTextView.setOnClickListener {
alertDialog.cancel() alertDialog.cancel()
showEventDetailDialog(nextEvent, items) showEventDetailDialog(nextEvent)
} }
} else { } else {
nextTextView.visibility = View.GONE nextTextView.visibility = View.GONE
@@ -549,7 +778,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)
loadLogbookList() loadLogbookList()
Toast.makeText(this@MainActivity, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show()
}) })
} }
@@ -706,7 +935,7 @@ class MainActivity : AppCompatActivity() {
setLoading(false) setLoading(false)
Toast.makeText( Toast.makeText(
this@MainActivity, applicationContext,
if (lastEventAdded != null) if (lastEventAdded != null)
R.string.toast_event_added R.string.toast_event_added
else else
@@ -768,7 +997,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)
Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
recyclerView.adapter?.notifyDataSetChanged() recyclerView.adapter?.notifyDataSetChanged()
savingEvent(false) savingEvent(false)
}) })
@@ -801,41 +1030,37 @@ class MainActivity : AppCompatActivity() {
val inflater = LayoutInflater.from(anchor.context) val inflater = LayoutInflater.from(anchor.context)
contentView = inflater.inflate(R.layout.more_events_popup, null) contentView = inflater.inflate(R.layout.more_events_popup, null)
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener { contentView.findViewById<View>(R.id.button_medicine).setOnClickListener {
askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE)) addNoteEvent(LunaEvent(LunaEvent.TYPE_MEDICINE))
dismiss() dismiss()
} }
contentView.findViewById<View>(R.id.button_enema).setOnClickListener({ contentView.findViewById<View>(R.id.button_enema).setOnClickListener {
logEvent(LunaEvent(LunaEvent.TYPE_ENEMA)) addPlainEvent(LunaEvent(LunaEvent.TYPE_ENEMA))
dismiss() dismiss()
}) }
contentView.findViewById<View>(R.id.button_note).setOnClickListener({ contentView.findViewById<View>(R.id.button_note).setOnClickListener {
askNotes(LunaEvent(LunaEvent.TYPE_NOTE)) addNoteEvent(LunaEvent(LunaEvent.TYPE_NOTE))
dismiss() dismiss()
}) }
contentView.findViewById<View>(R.id.button_temperature).setOnClickListener({ contentView.findViewById<View>(R.id.button_temperature).setOnClickListener {
askTemperatureValue() addTemperatureEvent(LunaEvent(LunaEvent.TYPE_TEMPERATURE))
dismiss() dismiss()
}) }
contentView.findViewById<View>(R.id.button_puke).setOnClickListener({ contentView.findViewById<View>(R.id.button_puke).setOnClickListener {
askPukeValue() addPukeEvent(LunaEvent(LunaEvent.TYPE_PUKE, 1))
dismiss() dismiss()
}) }
contentView.findViewById<View>(R.id.button_colic).setOnClickListener({ contentView.findViewById<View>(R.id.button_colic).setOnClickListener {
logEvent( addPlainEvent(LunaEvent(LunaEvent.TYPE_COLIC))
LunaEvent(LunaEvent.TYPE_COLIC)
)
dismiss() dismiss()
}) }
contentView.findViewById<View>(R.id.button_scale).setOnClickListener({ contentView.findViewById<View>(R.id.button_scale).setOnClickListener {
askWeightValue() addWeightEvent(LunaEvent(LunaEvent.TYPE_WEIGHT))
dismiss() dismiss()
}) }
contentView.findViewById<View>(R.id.button_bath).setOnClickListener({ contentView.findViewById<View>(R.id.button_bath).setOnClickListener {
logEvent( addPlainEvent(LunaEvent(LunaEvent.TYPE_BATH))
LunaEvent(LunaEvent.TYPE_BATH)
)
dismiss() dismiss()
}) }
}.also { popupWindow -> }.also { popupWindow ->
popupWindow.setOnDismissListener({ popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({ Handler(mainLooper).postDelayed({

View File

@@ -69,6 +69,15 @@ class LunaEvent: Comparable<LunaEvent> {
throw IllegalArgumentException("JSONObject is not a LunaEvent") throw IllegalArgumentException("JSONObject is not a LunaEvent")
} }
constructor(event: LunaEvent) {
this.jo = JSONObject()
this.type = event.type
this.time = event.time
this.quantity = event.quantity
this.notes = event.notes
this.signature = event.signature
}
constructor(type: String) { constructor(type: String) {
this.jo = JSONObject() this.jo = JSONObject()
this.time = System.currentTimeMillis() / 1000 this.time = System.currentTimeMillis() / 1000

View File

@@ -23,14 +23,6 @@ class LocalSettingsRepository(val context: Context) {
sharedPreferences = context.getSharedPreferences(SHARED_PREFS_FILE_NAME, MODE_PRIVATE) sharedPreferences = context.getSharedPreferences(SHARED_PREFS_FILE_NAME, MODE_PRIVATE)
} }
fun saveBabyBottleContent(content: Int) {
sharedPreferences.edit { putInt(SHARED_PREFS_BB_CONTENT, content) }
}
fun loadBabyBottleContent(): Int {
return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
}
fun saveSignature(content: String) { fun saveSignature(content: String) {
sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) } sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
} }

View File

@@ -60,21 +60,21 @@ class NumericUtils (val context: Context) {
) )
} }
fun formatEventQuantity(item: LunaEvent): String { fun formatEventQuantity(event: LunaEvent): String {
val formatted = StringBuilder() val formatted = StringBuilder()
if (item.quantity > 0) { if (event.quantity > 0) {
formatted.append(when (item.type) { formatted.append(when (event.type) {
LunaEvent.TYPE_TEMPERATURE -> LunaEvent.TYPE_TEMPERATURE ->
(item.quantity / 10.0f).toString() (event.quantity / 10.0f).toString()
LunaEvent.TYPE_PUKE -> LunaEvent.TYPE_PUKE ->
context.resources.getStringArray(R.array.AmountLabels)[item.quantity - 1] context.resources.getStringArray(R.array.AmountLabels)[event.quantity - 1]
else -> else ->
item.quantity event.quantity
}) })
formatted.append(" ") formatted.append(" ")
formatted.append( formatted.append(
when (item.type) { when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny

View File

@@ -0,0 +1,35 @@
<?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:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal">
<NumberPicker
android:id="@+id/dialog_number_picker"
android:layout_width="150dp"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="ml"/>
</LinearLayout>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"/>
</LinearLayout>

View File

@@ -24,4 +24,11 @@
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"/>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -4,16 +4,13 @@
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:layout_gravity="center"
android:gravity="center"> android:gravity="center"
android:orientation="vertical">
<NumberPicker
android:id="@+id/dialog_number_picker"
android:layout_width="150dp"
android:layout_height="wrap_content"/>
<TextView <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_marginStart="10dp" android:layout_marginTop="20dp"/>
android:text="ml"/>
</LinearLayout> </LinearLayout>

View File

@@ -14,4 +14,10 @@
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:paddingVertical="8dp"/> 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> </LinearLayout>

View File

@@ -23,4 +23,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="30sp" android:textSize="30sp"
android:textColor="@color/accent"/> android:textColor="@color/accent"/>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,38 @@
<?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:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal">
<EditText
android:id="@+id/dialog_number_edittext"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="0"
android:background="@drawable/textview_background"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="g"/>
</LinearLayout>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"/>
</LinearLayout>

View File

@@ -36,9 +36,7 @@
android:drawablePadding="10dp" android:drawablePadding="10dp"
android:drawableTint="@color/accent" android:drawableTint="@color/accent"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"/>
android:text="@string/dialog_event_detail_datetime_icon"
app:drawableEndCompat="@drawable/ic_edit" />
<TextView <TextView
android:id="@+id/dialog_event_detail_type_quantity" android:id="@+id/dialog_event_detail_type_quantity"

View File

@@ -1,22 +0,0 @@
<?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">
<EditText
android:id="@+id/dialog_number_edittext"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="0"
android:background="@drawable/textview_background"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="g"/>
</LinearLayout>

View File

@@ -133,7 +133,6 @@
<string name="row_luna_event_time">Time</string> <string name="row_luna_event_time">Time</string>
<string name="dialog_event_detail_title">Event detail</string> <string name="dialog_event_detail_title">Event detail</string>
<string name="dialog_event_detail_datetime_icon" translatable="false">🕒 %s</string>
<string name="dialog_event_detail_close_button">OK</string> <string name="dialog_event_detail_close_button">OK</string>
<string name="dialog_event_detail_delete_button">Delete</string> <string name="dialog_event_detail_delete_button">Delete</string>
<string name="dialog_event_detail_quantity">Quantity</string> <string name="dialog_event_detail_quantity">Quantity</string>