6 Commits

Author SHA1 Message Date
1763a9cfd0 MainActivity: generate dynamic menu from last two weeks 2026-02-19 11:05:00 +01:00
3779e7e34d LunaEvent: rework sleep event
Make the UI more flexible and
slightly easier to understand.
2026-02-19 11:05:00 +01:00
9a1f489b8b MainActivity: rename datepicker 2026-02-19 11:05:00 +01:00
dd02bbce65 layout: replace menu icon with utf8 character
The three dot menu icosn looks odd when stretched
due to the dynamic menu feature. Thus replace it
with the hamburger menu character that looks better
when scaled.
2026-02-19 11:05:00 +01:00
e6ac11d335 MainActivity: sort events before saving
Also replace notifyItemInserted since it does not
call the adapter to redraw the row striping when
a new event is added.
2026-02-19 11:05:00 +01:00
e1e8832f51 StatisticsActivity: rework all statistics
Improve the overall code.
2026-02-19 11:04:57 +01:00
13 changed files with 171 additions and 260 deletions

View File

@@ -31,7 +31,7 @@
android:label="@string/settings_title" android:label="@string/settings_title"
android:theme="@style/Theme.LunaTracker"/> android:theme="@style/Theme.LunaTracker"/>
<activity <activity
android:name=".LongTermStatisticsActivity" android:name=".StatisticsActivity"
android:label="@string/statistics_title" android:label="@string/statistics_title"
android:theme="@style/Theme.LunaTracker"/> android:theme="@style/Theme.LunaTracker"/>
</application> </application>

View File

@@ -162,9 +162,7 @@ class MainActivity : AppCompatActivity() {
compareBy({ -1 * (eventTypeStats[it] ?: 0) }, { it.ordinal }) compareBy({ -1 * (eventTypeStats[it] ?: 0) }, { it.ordinal })
).filter { it != LunaEvent.Type.UNKNOWN } ).filter { it != LunaEvent.Type.UNKNOWN }
val usedEventCount = eventTypeStats.count { it.value > 0 } fun setupMenu(maxButtonCount: Int, sortedEventTypes: List<LunaEvent.Type>): Int {
val maxButtonCount = if (dynamicMenu) { usedEventCount } else { 7 }
val row1 = findViewById<View>(R.id.linear_layout_row1) val row1 = findViewById<View>(R.id.linear_layout_row1)
val row1Button1 = findViewById<TextView>(R.id.button1_row1) val row1Button1 = findViewById<TextView>(R.id.button1_row1)
val row1Button2 = findViewById<TextView>(R.id.button2_row1) val row1Button2 = findViewById<TextView>(R.id.button2_row1)
@@ -190,7 +188,7 @@ class MainActivity : AppCompatActivity() {
fun show(vararg tvs: TextView) { fun show(vararg tvs: TextView) {
for (tv in tvs) { for (tv in tvs) {
val type = eventTypesSorted[showCounter] val type = sortedEventTypes[showCounter]
tv.text = LunaEvent.getHeaderEmoji(applicationContext, type) tv.text = LunaEvent.getHeaderEmoji(applicationContext, type)
tv.setOnClickListener { showCreateDialog(type) } tv.setOnClickListener { showCreateDialog(type) }
tv.visibility = View.VISIBLE tv.visibility = View.VISIBLE
@@ -209,8 +207,15 @@ class MainActivity : AppCompatActivity() {
else -> show(row1Button1, row1Button2, row2Button1, row2Button2, row2Button3, row3Button1, row3Button2) else -> show(row1Button1, row1Button2, row2Button1, row2Button2, row2Button3, row3Button1, row3Button2)
} }
return showCounter
}
val usedEventCount = eventTypeStats.count { it.value > 0 }
val maxButtonCount = if (dynamicMenu) { usedEventCount } else { 7 }
val eventsShown = setupMenu(maxButtonCount, eventTypesSorted)
// store left over events for popup menu // store left over events for popup menu
currentPopupItems = eventTypesSorted.subList(showCounter, eventTypesSorted.size) currentPopupItems = eventTypesSorted.subList(eventsShown, eventTypesSorted.size)
} }
override fun onStart() { override fun onStart() {
@@ -272,9 +277,6 @@ class MainActivity : AppCompatActivity() {
numberPicker.wrapSelectorWheel = false numberPicker.wrapSelectorWheel = false
numberPicker.value = event.quantity / 10 numberPicker.value = event.quantity / 10
val numberPickerUnit = dialogView.findViewById<TextView>(R.id.dialog_number_picker_unit)
numberPickerUnit.text = NumericUtils(this).measurement_unit_liquid_base
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker) val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = dateTimePicker(event.time, dateTV) val pickedTime = dateTimePicker(event.time, dateTV)
@@ -313,9 +315,6 @@ class MainActivity : AppCompatActivity() {
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()) weightET.setText(event.quantity.toString())
val unitTV = dialogView.findViewById<TextView>(R.id.dialog_number_unit)
unitTV.text = NumericUtils(this).measurement_unit_weight_base
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker) val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = dateTimePicker(event.time, dateTV) val pickedTime = dateTimePicker(event.time, dateTV)
@@ -344,53 +343,6 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun addHeightEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askHeightValue(event, true) { saveEvent(event) }
}
fun askHeightValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
// Show number picker dialog
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_height, null)
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
val heightET = dialogView.findViewById<EditText>(R.id.dialog_number_edittext)
heightET.setText(event.quantity.toString())
val unitTV = dialogView.findViewById<TextView>(R.id.dialog_number_unit)
unitTV.text = NumericUtils(this).measurement_unit_height_base
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val height = heightET.text.toString().toIntOrNull()
if (height != null) {
event.time = pickedTime.time.time / 1000
event.quantity = height
onPositive()
} else {
Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
}
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
}
fun addTemperatureEvent(event: LunaEvent) { fun addTemperatureEvent(event: LunaEvent) {
setToPreviousQuantity(event) setToPreviousQuantity(event)
askTemperatureValue(event, true) { saveEvent(event) } askTemperatureValue(event, true) { saveEvent(event) }
@@ -471,30 +423,32 @@ class MainActivity : AppCompatActivity() {
return dateTime return dateTime
} }
fun addDurationEvent(event: LunaEvent) { fun addSleepEvent(event: LunaEvent) {
askDurationEvent(event, true) { saveEvent(event) } askSleepValue(event, true) { saveEvent(event) }
} }
fun askDurationEvent(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { fun askSleepValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getDialogTitle(this))
d.setView(dialogView) d.setView(dialogView)
val durationTV = dialogView.findViewById<TextView>(R.id.dialog_date_duration) val durationTextView = dialogView.findViewById<TextView>(R.id.dialog_date_duration)
val datePickerBeginTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker_begin) val datePickerBegin = dialogView.findViewById<TextView>(R.id.dialog_date_picker_begin)
val datePickerEndTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker_end) val datePickerEnd = dialogView.findViewById<TextView>(R.id.dialog_date_picker_end)
val dateDelimiterTV = dialogView.findViewById<TextView>(R.id.dialog_date_range_delimiter) val dateDelimiter = dialogView.findViewById<TextView>(R.id.dialog_date_range_delimiter)
val durationButtons = dialogView.findViewById<LinearLayout>(R.id.duration_buttons) val durationButtons = dialogView.findViewById<LinearLayout>(R.id.duration_buttons)
val durationNowButton = dialogView.findViewById<Button>(R.id.dialog_date_duration_now) val durationNowButton = dialogView.findViewById<Button>(R.id.dialog_date_duration_now)
val durationClearButton = dialogView.findViewById<Button>(R.id.dialog_date_duration_clear) val durationAsleepButton = dialogView.findViewById<Button>(R.id.dialog_date_duration_asleep)
val durationMinus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_minus5)
val durationPlus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_plus5)
val currentDurationTextColor = durationTV.currentTextColor val currentDurationTextColor = durationTextView.currentTextColor
val invalidDurationTextColor = ContextCompat.getColor(this, R.color.danger) val invalidDurationTextColor = ContextCompat.getColor(this, R.color.danger)
// in seconds // in seconds
var durationStart = event.getStartTime() var sleepBegin = event.time
var durationEnd = event.getEndTime() var sleepEnd = event.time + event.quantity
fun isValidTime(timeUnix: Long): Boolean { fun isValidTime(timeUnix: Long): Boolean {
val now = System.currentTimeMillis() / 1000 val now = System.currentTimeMillis() / 1000
@@ -505,76 +459,85 @@ class MainActivity : AppCompatActivity() {
return (timeBeginUnix <= timeEndUnix) && (timeEndUnix - timeBeginUnix) < (24 * 60 * 60) return (timeBeginUnix <= timeEndUnix) && (timeEndUnix - timeBeginUnix) < (24 * 60 * 60)
} }
// prevent printing of seconds
fun adjustToMinute(unixTime: Long): Long {
return unixTime - (unixTime % 60)
}
fun updateFields() { fun updateFields() {
datePickerBeginTV.text = DateUtils.formatDateTime(durationStart) datePickerBegin.text = DateUtils.formatDateTime(sleepBegin)
datePickerEndTV.text = DateUtils.formatDateTime(durationEnd) datePickerEnd.text = DateUtils.formatDateTime(sleepEnd)
dateTimePicker(durationStart, datePickerBeginTV) { pickedTime: Long -> durationTextView.setTextColor(currentDurationTextColor)
durationStart = pickedTime val duration = sleepEnd - sleepBegin
if (datePickerEndTV.visibility == View.GONE) {
durationEnd = pickedTime
}
updateFields()
}
dateTimePicker(durationEnd, datePickerEndTV) { pickedTime: Long ->
durationEnd = pickedTime
updateFields()
}
durationTV.setTextColor(currentDurationTextColor)
val duration = durationEnd - durationStart
if (duration == 0L) { if (duration == 0L) {
// event is ongoing // baby is sleeping
durationTV.text = "💤" durationTextView.text = "💤"
dateDelimiterTV.visibility = View.GONE
datePickerEndTV.visibility = View.GONE
} else { } else {
durationTV.text = DateUtils.formatTimeDuration(applicationContext, duration) durationTextView.text = DateUtils.formatTimeDuration(applicationContext, duration)
if (!isValidTimeSpan(durationStart, durationEnd)) { if (!isValidTimeSpan(sleepBegin, sleepEnd)) {
durationTV.setTextColor(invalidDurationTextColor) durationTextView.setTextColor(invalidDurationTextColor)
} }
dateDelimiterTV.visibility = View.VISIBLE
datePickerEndTV.visibility = View.VISIBLE
} }
val colorBegin = if (isValidTime(durationStart)) { currentDurationTextColor } else { invalidDurationTextColor } datePickerBegin.setTextColor(if (isValidTime(sleepBegin)) { currentDurationTextColor } else { invalidDurationTextColor })
datePickerBeginTV.setTextColor(colorBegin) datePickerEnd.setTextColor(if (isValidTime(sleepEnd)) { currentDurationTextColor } else { invalidDurationTextColor })
val colorEnd = if (isValidTime(durationEnd)) { currentDurationTextColor } else { invalidDurationTextColor }
datePickerEndTV.setTextColor(colorEnd)
} }
val pickedDateTimeBegin = dateTimePicker(event.time, datePickerBegin) { time: Long ->
sleepBegin = adjustToMinute(time)
updateFields()
}
val pickedDateTimeEnd = dateTimePicker(event.time + event.quantity, datePickerEnd) { time: Long ->
sleepEnd = adjustToMinute(time)
updateFields()
}
sleepBegin = adjustToMinute(pickedDateTimeBegin.time.time / 1000)
sleepEnd = adjustToMinute(pickedDateTimeEnd.time.time / 1000)
updateFields()
if (showTime) { if (showTime) {
dateDelimiterTV.visibility = View.GONE dateDelimiter.visibility = View.GONE
datePickerEndTV.visibility = View.GONE datePickerEnd.visibility = View.GONE
durationTV.visibility = View.GONE durationTextView.visibility = View.GONE
durationButtons.visibility = View.GONE durationButtons.visibility = View.GONE
//d.setMessage("") //d.setMessage("")
} else { } else {
dateDelimiterTV.visibility = View.VISIBLE dateDelimiter.visibility = View.VISIBLE
datePickerEndTV.visibility = View.VISIBLE datePickerEnd.visibility = View.VISIBLE
durationTV.visibility = View.VISIBLE durationTextView.visibility = View.VISIBLE
durationButtons.visibility = View.VISIBLE durationButtons.visibility = View.VISIBLE
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
} }
durationMinus5Button.setOnClickListener {
sleepEnd = (sleepEnd - 300).coerceAtLeast(sleepBegin)
updateFields() updateFields()
}
durationClearButton.setOnClickListener { durationPlus5Button.setOnClickListener {
durationEnd = durationStart sleepEnd = (sleepEnd + 300).coerceAtLeast(sleepBegin)
updateFields()
}
durationAsleepButton.setOnClickListener {
sleepEnd = sleepBegin
updateFields() updateFields()
} }
durationNowButton.setOnClickListener { durationNowButton.setOnClickListener {
durationEnd = System.currentTimeMillis() / 1000 val now = System.currentTimeMillis() / 1000
sleepEnd = adjustToMinute(now)
updateFields() updateFields()
} }
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
if (isValidTime(durationStart) && isValidTime(durationEnd) && isValidTimeSpan(durationStart, durationEnd)) { if (isValidTime(sleepBegin) && isValidTime(sleepEnd) && isValidTimeSpan(sleepBegin, sleepEnd)) {
event.time = durationStart event.time = sleepBegin
event.quantity = (durationEnd - durationStart).toInt() event.quantity = (sleepEnd - sleepBegin).toInt()
onPositive() onPositive()
} else { } else {
Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show()
@@ -695,6 +658,9 @@ class MainActivity : AppCompatActivity() {
var nextEvent = getNextSameEvent(current, templates) var nextEvent = getNextSameEvent(current, templates)
notesET.setText(current.notes) notesET.setText(current.notes)
if (useQuantity) {
qtyET.setText(current.quantity.toString())
}
if (nextEvent == null && current != event) { if (nextEvent == null && current != event) {
nextEvent = event nextEvent = event
@@ -703,6 +669,9 @@ class MainActivity : AppCompatActivity() {
if (nextEvent != null) { if (nextEvent != null) {
nextTextView.setOnClickListener { nextTextView.setOnClickListener {
notesET.setText(nextEvent.notes) notesET.setText(nextEvent.notes)
if (useQuantity) {
qtyET.setText(nextEvent.quantity.toString())
}
updateContent(nextEvent) updateContent(nextEvent)
} }
nextTextView.alpha = 1.0f nextTextView.alpha = 1.0f
@@ -714,6 +683,9 @@ class MainActivity : AppCompatActivity() {
if (prevEvent != null) { if (prevEvent != null) {
prevTextView.setOnClickListener { prevTextView.setOnClickListener {
notesET.setText(prevEvent.notes) notesET.setText(prevEvent.notes)
if (useQuantity) {
qtyET.setText(prevEvent.quantity.toString())
}
updateContent(prevEvent) updateContent(prevEvent)
} }
prevTextView.alpha = 1.0f prevTextView.alpha = 1.0f
@@ -906,15 +878,12 @@ class MainActivity : AppCompatActivity() {
when (event.type) { when (event.type) {
LunaEvent.Type.BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues) LunaEvent.Type.BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
LunaEvent.Type.WEIGHT -> askWeightValue(event, false, updateValues) LunaEvent.Type.WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.Type.HEIGHT -> askHeightValue(event, false, updateValues)
LunaEvent.Type.DIAPERCHANGE_POO, LunaEvent.Type.DIAPERCHANGE_POO,
LunaEvent.Type.DIAPERCHANGE_PEE, LunaEvent.Type.DIAPERCHANGE_PEE,
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.FOOD,
LunaEvent.Type.MEDICINE,
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues) LunaEvent.Type.NOTE -> askNotes(event, false, updateValues)
LunaEvent.Type.SLEEP -> askDurationEvent(event, false, updateValues) LunaEvent.Type.SLEEP -> askSleepValue(event, false, updateValues)
else -> { else -> {
Log.w(TAG, "Unexpected type: ${event.type}") Log.w(TAG, "Unexpected type: ${event.type}")
} }
@@ -1328,7 +1297,6 @@ class MainActivity : AppCompatActivity() {
when (type) { when (type) {
LunaEvent.Type.BABY_BOTTLE -> addBabyBottleEvent(event) LunaEvent.Type.BABY_BOTTLE -> addBabyBottleEvent(event)
LunaEvent.Type.WEIGHT -> addWeightEvent(event) LunaEvent.Type.WEIGHT -> addWeightEvent(event)
LunaEvent.Type.HEIGHT -> addHeightEvent(event)
LunaEvent.Type.BREASTFEEDING_LEFT_NIPPLE -> addPlainEvent(event) LunaEvent.Type.BREASTFEEDING_LEFT_NIPPLE -> addPlainEvent(event)
LunaEvent.Type.BREASTFEEDING_BOTH_NIPPLE -> addPlainEvent(event) LunaEvent.Type.BREASTFEEDING_BOTH_NIPPLE -> addPlainEvent(event)
LunaEvent.Type.BREASTFEEDING_RIGHT_NIPPLE -> addPlainEvent(event) LunaEvent.Type.BREASTFEEDING_RIGHT_NIPPLE -> addPlainEvent(event)
@@ -1342,7 +1310,7 @@ class MainActivity : AppCompatActivity() {
LunaEvent.Type.FOOD -> addNoteEvent(event) LunaEvent.Type.FOOD -> addNoteEvent(event)
LunaEvent.Type.PUKE -> addAmountEvent(event) LunaEvent.Type.PUKE -> addAmountEvent(event)
LunaEvent.Type.BATH -> addPlainEvent(event) LunaEvent.Type.BATH -> addPlainEvent(event)
LunaEvent.Type.SLEEP -> addDurationEvent(event) LunaEvent.Type.SLEEP -> addSleepEvent(event)
LunaEvent.Type.UNKNOWN -> {} // ignore LunaEvent.Type.UNKNOWN -> {} // ignore
} }
} }
@@ -1359,7 +1327,7 @@ class MainActivity : AppCompatActivity() {
// Add statistics (hard coded) // Add statistics (hard coded)
contentView.findViewById<View>(R.id.button_statistics).setOnClickListener { contentView.findViewById<View>(R.id.button_statistics).setOnClickListener {
if (logbook != null && !pauseLogbookUpdate) { if (logbook != null && !pauseLogbookUpdate) {
val i = Intent(applicationContext, LongTermStatisticsActivity::class.java) val i = Intent(applicationContext, StatisticsActivity::class.java)
i.putExtra("LOOGBOOK_NAME", logbook!!.name) i.putExtra("LOOGBOOK_NAME", logbook!!.name)
startActivity(i) startActivity(i)
} else { } else {

View File

@@ -35,7 +35,7 @@ import java.util.Locale
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
class LongTermStatisticsActivity : AppCompatActivity() { class StatisticsActivity : AppCompatActivity() {
var lastToastShown = 0L var lastToastShown = 0L
lateinit var barChart: BarChart lateinit var barChart: BarChart
@@ -297,6 +297,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
val dayStartUnix = daysToUnix(unixToDays(state.startUnix) + index) val dayStartUnix = daysToUnix(unixToDays(state.startUnix) + index)
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, x: ${e.x.toInt()}, dayStartUnix: ${Date(dayStartUnix * 1000)}")
val startSeconds = val startSeconds =
SLEEP_PATTERN_GRANULARITY * value.yVals.sliceArray(0..<h.stackIndex) SLEEP_PATTERN_GRANULARITY * value.yVals.sliceArray(0..<h.stackIndex)
.fold(0) { acc, y -> acc + y.toInt() } .fold(0) { acc, y -> acc + y.toInt() }
@@ -487,6 +488,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
fun showSleepBarGraph(state: GraphState) { fun showSleepBarGraph(state: GraphState) {
val ranges = toSleepRanges(state.events) val ranges = toSleepRanges(state.events)
val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), 0F) }) val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), 0F) })
//Log.d(TAG, "startUnix: ${Date(state.startUnix * 1000)}, endUnix: ${Date(state.endUnix * 1000)}")
for (range in ranges) { for (range in ranges) {
// a sleep event can span to another day // a sleep event can span to another day
@@ -603,6 +605,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
data.setValueFormatter(object : ValueFormatter() { data.setValueFormatter(object : ValueFormatter() {
override fun getFormattedValue(value: Float): String { override fun getFormattedValue(value: Float): String {
val prefix = if (timeRangeSelection == TimeRange.DAY) { "" } else { "" } val prefix = if (timeRangeSelection == TimeRange.DAY) { "" } else { "" }
//Log.d(TAG, "getFormattedValue ${dataTypeSelectionValue} ${eventTypeSelectionValue}")
return when (graphTypeSelection) { return when (graphTypeSelection) {
GraphType.BOTTLE_EVENTS -> { GraphType.BOTTLE_EVENTS -> {
prefix + value.toInt().toString() prefix + value.toInt().toString()
@@ -638,6 +641,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
val endDays = unixToDays(endUnix) val endDays = unixToDays(endUnix)
var count = 0 var count = 0
for (i in (beginDays - startDays)..<(endDays - startDays)) { for (i in (beginDays - startDays)..<(endDays - startDays)) {
//Log.d(TAG, "countDaysWithData: i: $i, size: ${daysWithData.size}")
count += if (daysWithData[i]) { 1 } else { 0 } count += if (daysWithData[i]) { 1 } else { 0 }
} }
return count return count
@@ -657,6 +661,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
data class GraphState(val events: List<LunaEvent>, val dayCounter: DayCounter, val startUnix: Long, val endUnix: Long, val startSpan: Int, val endSpan: Int) data class GraphState(val events: List<LunaEvent>, val dayCounter: DayCounter, val startUnix: Long, val endUnix: Long, val startSpan: Int, val endSpan: Int)
fun showGraph() { fun showGraph() {
//Log.d(TAG, "showGraph: graphTypeSelection: $graphTypeSelection, timeRangeSelection: $timeRangeSelection")
barChart.fitScreen() barChart.fitScreen()
barChart.data?.clearValues() barChart.data?.clearValues()
barChart.xAxis.valueFormatter = null barChart.xAxis.valueFormatter = null
@@ -672,6 +677,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
GraphType.SLEEP_PATTERN -> LunaEvent.Type.SLEEP GraphType.SLEEP_PATTERN -> LunaEvent.Type.SLEEP
} }
val events = MainActivity.allEvents.filter { it.type == type }.sortedBy { it.time } val events = MainActivity.allEvents.filter { it.type == type }.sortedBy { it.time }
if (events.isEmpty()) { if (events.isEmpty()) {
@@ -767,7 +773,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
if (pos >= arrayValues.size) { if (pos >= arrayValues.size) {
Toast.makeText( Toast.makeText(
this@LongTermStatisticsActivity, this@StatisticsActivity,
"pos out of bounds: $arrayValues", Toast.LENGTH_SHORT "pos out of bounds: $arrayValues", Toast.LENGTH_SHORT
).show() ).show()
return return
@@ -784,7 +790,7 @@ class LongTermStatisticsActivity : AppCompatActivity() {
} }
companion object { companion object {
const val TAG = "LongTermStatisticsActivity" const val TAG = "StatisticsActivity"
// 15 min steps // 15 min steps
const val SLEEP_PATTERN_GRANULARITY = 15 * 60 const val SLEEP_PATTERN_GRANULARITY = 15 * 60

View File

@@ -63,7 +63,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
// if the event is weight, show difference with the last one // if the event is weight, show difference with the last one
if (item.type == LunaEvent.Type.WEIGHT) { if (item.type == LunaEvent.Type.WEIGHT) {
val lastWeight = getPreviousEvent(position, LunaEvent.Type.WEIGHT) val lastWeight = getPreviousWeightEvent(position)
if (lastWeight != null) { if (lastWeight != null) {
val differenceInWeight = item.quantity - lastWeight.quantity val differenceInWeight = item.quantity - lastWeight.quantity
val sign = if (differenceInWeight > 0) "+" else "" val sign = if (differenceInWeight > 0) "+" else ""
@@ -74,19 +74,6 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
} }
} }
// if the event is height, show difference with the last one
if (item.type == LunaEvent.Type.HEIGHT) {
val lastHeight = getPreviousEvent(position, LunaEvent.Type.HEIGHT)
if (lastHeight != null) {
val differenceInHeight = item.quantity - lastHeight.quantity
val sign = if (differenceInHeight > 0) "+" else ""
quantityText += "\n($sign$differenceInHeight)"
if (differenceInHeight < 0) {
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.danger))
}
}
}
holder.quantity.text = quantityText holder.quantity.text = quantityText
// Listeners // Listeners
@@ -101,12 +88,12 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
return items.size return items.size
} }
private fun getPreviousEvent(startFromPosition: Int, type: LunaEvent.Type): LunaEvent? { private fun getPreviousWeightEvent(startFromPosition: Int): LunaEvent? {
if (startFromPosition == items.size - 1) if (startFromPosition == items.size - 1)
return null return null
for (pos in startFromPosition + 1 until items.size) { for (pos in startFromPosition + 1 until items.size) {
val item = items.get(pos) val item = items.get(pos)
if (item.type != type) if (item.type != LunaEvent.Type.WEIGHT)
continue continue
return item return item
} }

View File

@@ -22,7 +22,6 @@ class LunaEvent: Comparable<LunaEvent> {
DIAPERCHANGE_PEE, DIAPERCHANGE_PEE,
SLEEP, SLEEP,
WEIGHT, WEIGHT,
HEIGHT,
MEDICINE, MEDICINE,
ENEMA, ENEMA,
NOTE, NOTE,
@@ -146,7 +145,6 @@ class LunaEvent: Comparable<LunaEvent> {
when (type) { when (type) {
Type.BABY_BOTTLE -> R.string.event_bottle_type Type.BABY_BOTTLE -> R.string.event_bottle_type
Type.WEIGHT -> R.string.event_weight_type Type.WEIGHT -> R.string.event_weight_type
Type.HEIGHT -> R.string.event_height_type
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
@@ -176,8 +174,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.HEIGHT -> R.string.log_height_dialog_description Type.SLEEP -> R.string.log_sleep_dialog_description
Type.SLEEP -> R.string.log_duration_dialog_description
else -> R.string.log_unknown_dialog_description else -> R.string.log_unknown_dialog_description
} }
) )
@@ -188,7 +185,6 @@ class LunaEvent: Comparable<LunaEvent> {
when (type) { when (type) {
Type.BABY_BOTTLE -> R.string.event_bottle_desc Type.BABY_BOTTLE -> R.string.event_bottle_desc
Type.WEIGHT -> R.string.event_weight_desc Type.WEIGHT -> R.string.event_weight_desc
Type.HEIGHT -> R.string.event_height_desc
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc
@@ -214,7 +210,6 @@ class LunaEvent: Comparable<LunaEvent> {
when (type) { when (type) {
Type.BABY_BOTTLE -> R.string.event_type_item_bottle Type.BABY_BOTTLE -> R.string.event_type_item_bottle
Type.WEIGHT -> R.string.event_type_item_weight Type.WEIGHT -> R.string.event_type_item_weight
Type.HEIGHT -> R.string.event_type_item_height
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_type_item_breastfeeding_left Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_type_item_breastfeeding_left
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_type_item_breastfeeding_both Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_type_item_breastfeeding_both
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_type_item_breastfeeding_right Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_type_item_breastfeeding_right

View File

@@ -9,15 +9,10 @@ import java.util.Date
class DateUtils { class DateUtils {
companion object { companion object {
/** /**
* Format time duration in seconds as e.g. "2 hours, 1 min", rounded to minutes. * Format time duration in seconds as e.g. "2 hours, 1 min".
* Used for the duration to the next/previous event in the event details dialog. * Used for the duration to the next/previous event in the event details dialog.
*/ */
fun formatTimeDuration(context: Context, secondsDiff: Long): String { fun formatTimeDuration(context: Context, secondsDiff: Long): String {
val adjusted = (secondsDiff + 30) - (secondsDiff + 30) % 60
return formatTimeDurationExact(context, adjusted)
}
fun formatTimeDurationExact(context: Context, secondsDiff: Long): String {
var seconds = secondsDiff var seconds = secondsDiff
val years = (seconds / (365 * 24 * 60 * 60F)).toLong() val years = (seconds / (365 * 24 * 60 * 60F)).toLong()

View File

@@ -11,10 +11,10 @@ import utils.DateUtils.Companion.formatTimeDuration
import java.text.NumberFormat import java.text.NumberFormat
class NumericUtils (val context: Context) { class NumericUtils (val context: Context) {
val numberFormat: NumberFormat
val measurement_unit_liquid_base: String val measurement_unit_liquid_base: String
val measurement_unit_weight_base: String val measurement_unit_weight_base: String
val measurement_unit_weight_tiny: String val measurement_unit_weight_tiny: String
val measurement_unit_height_base: String
val measurement_unit_temperature_base: String val measurement_unit_temperature_base: String
private fun isMetricSystem(): Boolean { private fun isMetricSystem(): Boolean {
@@ -35,6 +35,7 @@ class NumericUtils (val context: Context) {
} }
init { init {
this.numberFormat = NumberFormat.getInstance()
this.measurement_unit_liquid_base = context.getString( this.measurement_unit_liquid_base = context.getString(
if (isMetricSystem()) if (isMetricSystem())
R.string.measurement_unit_liquid_base_metric R.string.measurement_unit_liquid_base_metric
@@ -53,12 +54,6 @@ class NumericUtils (val context: Context) {
else else
R.string.measurement_unit_weight_tiny_imperial R.string.measurement_unit_weight_tiny_imperial
) )
this.measurement_unit_height_base = context.getString(
if (isMetricSystem())
R.string.measurement_unit_height_base_metric
else
R.string.measurement_unit_height_base_imperial
)
this.measurement_unit_temperature_base = context.getString( this.measurement_unit_temperature_base = context.getString(
if (isMetricSystem()) if (isMetricSystem())
R.string.measurement_unit_temperature_base_metric R.string.measurement_unit_temperature_base_metric
@@ -95,7 +90,6 @@ class NumericUtils (val context: Context) {
when (type) { when (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.HEIGHT -> measurement_unit_height_base
LunaEvent.Type.MEDICINE -> measurement_unit_weight_tiny LunaEvent.Type.MEDICINE -> measurement_unit_weight_tiny
LunaEvent.Type.TEMPERATURE -> measurement_unit_temperature_base LunaEvent.Type.TEMPERATURE -> measurement_unit_temperature_base
else -> "" else -> ""

View File

@@ -259,7 +259,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginHorizontal="10dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:text="@string/no_connection_explain"/> android:text="@string/no_connection_explain"/>

View File

@@ -18,7 +18,6 @@
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<TextView <TextView
android:id="@+id/dialog_number_picker_unit"
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_marginStart="10dp"

View File

@@ -24,6 +24,13 @@
android:layout_marginHorizontal="10dp" android:layout_marginHorizontal="10dp"
android:orientation="horizontal"> android:orientation="horizontal">
<Button
android:id="@+id/dialog_date_duration_minus5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dialog_duration_button_minus_5"/>
<Button <Button
android:id="@+id/dialog_date_duration_now" android:id="@+id/dialog_date_duration_now"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -32,11 +39,18 @@
android:text="@string/dialog_duration_button_now"/> android:text="@string/dialog_duration_button_now"/>
<Button <Button
android:id="@+id/dialog_date_duration_clear" android:id="@+id/dialog_date_duration_asleep"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/dialog_duration_button_clear"/> android:text="@string/dialog_duration_button_asleep"/>
<Button
android:id="@+id/dialog_date_duration_plus5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dialog_duration_button_plus_5"/>
</LinearLayout> </LinearLayout>

View File

@@ -1,39 +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: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:id="@+id/dialog_number_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="cm"/>
</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

@@ -21,7 +21,6 @@
android:background="@drawable/textview_background"/> android:background="@drawable/textview_background"/>
<TextView <TextView
android:id="@+id/dialog_number_unit"
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_marginStart="10dp"

View File

@@ -7,7 +7,6 @@
<string name="event_bottle_type" translatable="false">🍼</string> <string name="event_bottle_type" translatable="false">🍼</string>
<string name="event_food_type" translatable="false">🥣</string> <string name="event_food_type" translatable="false">🥣</string>
<string name="event_weight_type" translatable="false">⚖️</string> <string name="event_weight_type" translatable="false">⚖️</string>
<string name="event_height_type" translatable="false">📏</string>
<string name="event_breastfeeding_left_type" translatable="false">🤱⬅️</string> <string name="event_breastfeeding_left_type" translatable="false">🤱⬅️</string>
<string name="event_breastfeeding_both_type" translatable="false">🤱↔️</string> <string name="event_breastfeeding_both_type" translatable="false">🤱↔️</string>
<string name="event_breastfeeding_right_type" translatable="false">🤱➡️️</string> <string name="event_breastfeeding_right_type" translatable="false">🤱➡️️</string>
@@ -27,7 +26,6 @@
<string name="event_type_item_bottle">🍼 Bottle</string> <string name="event_type_item_bottle">🍼 Bottle</string>
<string name="event_type_item_food">🥣 Food</string> <string name="event_type_item_food">🥣 Food</string>
<string name="event_type_item_weight">⚖️ Weight</string> <string name="event_type_item_weight">⚖️ Weight</string>
<string name="event_type_item_height">📏 Height</string>
<string name="event_type_item_breastfeeding_left">🤱⬅️ Nursing</string> <string name="event_type_item_breastfeeding_left">🤱⬅️ Nursing</string>
<string name="event_type_item_breastfeeding_both">🤱↔️ Nursing</string> <string name="event_type_item_breastfeeding_both">🤱↔️ Nursing</string>
<string name="event_type_item_breastfeeding_right">🤱➡️️ Nursing</string> <string name="event_type_item_breastfeeding_right">🤱➡️️ Nursing</string>
@@ -47,7 +45,6 @@
<string name="event_bottle_desc">Milk Bottle</string> <string name="event_bottle_desc">Milk Bottle</string>
<string name="event_food_desc">Food</string> <string name="event_food_desc">Food</string>
<string name="event_weight_desc">Weight</string> <string name="event_weight_desc">Weight</string>
<string name="event_height_desc">Height</string>
<string name="event_breastfeeding_left_desc">Nursing (left)</string> <string name="event_breastfeeding_left_desc">Nursing (left)</string>
<string name="event_breastfeeding_both_desc">Nursing (both)</string> <string name="event_breastfeeding_both_desc">Nursing (both)</string>
<string name="event_breastfeeding_right_desc">Nursing (right)</string> <string name="event_breastfeeding_right_desc">Nursing (right)</string>
@@ -133,17 +130,14 @@
<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_height_dialog_description">Insert the height:</string> <string name="log_sleep_dialog_description">Set sleep duration:</string>
<string name="log_duration_dialog_description">Set duration:</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>
<string name="measurement_unit_weight_tiny_metric" translatable="false">mg</string> <string name="measurement_unit_weight_tiny_metric" translatable="false">mg</string>
<string name="measurement_unit_height_base_metric" translatable="false">cm</string>
<string name="measurement_unit_liquid_base_imperial" translatable="false">fl oz.</string> <string name="measurement_unit_liquid_base_imperial" translatable="false">fl oz.</string>
<string name="measurement_unit_weight_base_imperial" translatable="false">oz</string> <string name="measurement_unit_weight_base_imperial" translatable="false">oz</string>
<string name="measurement_unit_weight_tiny_imperial" translatable="false">gr</string> <string name="measurement_unit_weight_tiny_imperial" translatable="false">gr</string>
<string name="measurement_unit_height_base_imperial" translatable="false">in</string>
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string> <string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string>
<string name="measurement_unit_temperature_base_metric" translatable="false">°C</string> <string name="measurement_unit_temperature_base_metric" translatable="false">°C</string>
@@ -166,9 +160,9 @@
<string name="dialog_event_detail_notes">Notes</string> <string name="dialog_event_detail_notes">Notes</string>
<string name="dialog_event_detail_signature">by %s</string> <string name="dialog_event_detail_signature">by %s</string>
<string name="dialog_duration_button_clear">Clear</string> <string name="dialog_duration_button_asleep">asleep</string>
<string name="dialog_duration_button_minus_5">-5 min</string> <string name="dialog_duration_button_minus_5">-5 min</string>
<string name="dialog_duration_button_now">Now</string> <string name="dialog_duration_button_now">now</string>
<string name="dialog_duration_button_plus_5">+5 min</string> <string name="dialog_duration_button_plus_5">+5 min</string>
<string name="dialog_add_logbook_title">Add logbook</string> <string name="dialog_add_logbook_title">Add logbook</string>