5 Commits

Author SHA1 Message Date
e1dbc2c628 LunaEvent: rework sleep event
Make the UI more flexible and
slightly easier to understand.
2026-02-13 15:06:26 +01:00
1bcfc18a71 MainActivity: rename datepicker 2026-02-12 23:01:01 +01:00
e345e98668 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-12 23:01:01 +01:00
09f5a4e27b 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-12 23:01:01 +01:00
b3b432525e StatisticsActivity: rework all statistics
Improve the overall code.
2026-02-12 23:00:58 +01:00
7 changed files with 172 additions and 133 deletions

View File

@@ -148,23 +148,19 @@ class MainActivity : AppCompatActivity() {
val eventTypeStats = mutableMapOf<LunaEvent.Type, Int>()
if (dynamicMenu) {
// populate frequency map from all events of the last two weeks
val lastWeekTime = (System.currentTimeMillis() / 1000) - (14 * 24 * 60 * 60)
allEvents.forEach {
if (it.time > lastWeekTime) {
val sampleSize = 100
// populate frequency map from first 100 events
allEvents.take(sampleSize.coerceAtMost(allEvents.size)).forEach {
eventTypeStats[it.type] = 1 + (eventTypeStats[it.type] ?: 0)
}
}
}
// sort all event types by frequency and ordinal
val eventTypesSorted = LunaEvent.Type.entries.toList().sortedWith(
compareBy({ -1 * (eventTypeStats[it] ?: 0) }, { it.ordinal })
).filter { it != LunaEvent.Type.UNKNOWN }
val usedEventCount = eventTypeStats.count { it.value > 0 }
val maxButtonCount = if (dynamicMenu) { usedEventCount } else { 7 }
fun setupMenu(maxButtonCount: Int, sortedEventTypes: List<LunaEvent.Type>): Int {
val row1 = findViewById<View>(R.id.linear_layout_row1)
val row1Button1 = findViewById<TextView>(R.id.button1_row1)
val row1Button2 = findViewById<TextView>(R.id.button2_row1)
@@ -190,7 +186,7 @@ class MainActivity : AppCompatActivity() {
fun show(vararg tvs: TextView) {
for (tv in tvs) {
val type = eventTypesSorted[showCounter]
val type = sortedEventTypes[showCounter]
tv.text = LunaEvent.getHeaderEmoji(applicationContext, type)
tv.setOnClickListener { showCreateDialog(type) }
tv.visibility = View.VISIBLE
@@ -209,8 +205,15 @@ class MainActivity : AppCompatActivity() {
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
currentPopupItems = eventTypesSorted.subList(showCounter, eventTypesSorted.size)
currentPopupItems = eventTypesSorted.subList(eventsShown, eventTypesSorted.size)
}
override fun onStart() {
@@ -272,9 +275,6 @@ class MainActivity : AppCompatActivity() {
numberPicker.wrapSelectorWheel = false
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 pickedTime = dateTimePicker(event.time, dateTV)
@@ -421,11 +421,11 @@ class MainActivity : AppCompatActivity() {
return dateTime
}
fun addDurationEvent(event: LunaEvent) {
askDurationEvent(event, true) { saveEvent(event) }
fun addSleepEvent(event: LunaEvent) {
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 dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getDialogTitle(this))
@@ -437,7 +437,7 @@ class MainActivity : AppCompatActivity() {
val dateDelimiter = dialogView.findViewById<TextView>(R.id.dialog_date_range_delimiter)
val durationButtons = dialogView.findViewById<LinearLayout>(R.id.duration_buttons)
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)
@@ -445,8 +445,8 @@ class MainActivity : AppCompatActivity() {
val invalidDurationTextColor = ContextCompat.getColor(this, R.color.danger)
// in seconds
var durationStart = event.getStartTime()
var durationEnd = event.getEndTime()
var sleepBegin = event.time
var sleepEnd = event.time + event.quantity
fun isValidTime(timeUnix: Long): Boolean {
val now = System.currentTimeMillis() / 1000
@@ -457,43 +457,46 @@ class MainActivity : AppCompatActivity() {
return (timeBeginUnix <= timeEndUnix) && (timeEndUnix - timeBeginUnix) < (24 * 60 * 60)
}
// prevent printing of seconds
fun adjustToMinute(unixTime: Long): Long {
return unixTime - (unixTime % 60)
}
fun updateFields() {
datePickerBegin.text = DateUtils.formatDateTime(durationStart)
datePickerEnd.text = DateUtils.formatDateTime(durationEnd)
datePickerBegin.text = DateUtils.formatDateTime(sleepBegin)
datePickerEnd.text = DateUtils.formatDateTime(sleepEnd)
durationTextView.setTextColor(currentDurationTextColor)
val duration = durationEnd - durationStart
val duration = sleepEnd - sleepBegin
if (duration == 0L) {
// event is ongoing
// baby is sleeping
durationTextView.text = "💤"
dateDelimiter.visibility = View.GONE
datePickerEnd.visibility = View.GONE
} else {
durationTextView.text = DateUtils.formatTimeDuration(applicationContext, duration)
if (!isValidTimeSpan(durationStart, durationEnd)) {
if (!isValidTimeSpan(sleepBegin, sleepEnd)) {
durationTextView.setTextColor(invalidDurationTextColor)
}
dateDelimiter.visibility = View.VISIBLE
datePickerEnd.visibility = View.VISIBLE
}
datePickerBegin.setTextColor(if (isValidTime(durationStart)) { currentDurationTextColor } else { invalidDurationTextColor })
datePickerEnd.setTextColor(if (isValidTime(durationEnd)) { currentDurationTextColor } else { invalidDurationTextColor })
datePickerBegin.setTextColor(if (isValidTime(sleepBegin)) { currentDurationTextColor } else { invalidDurationTextColor })
datePickerEnd.setTextColor(if (isValidTime(sleepEnd)) { currentDurationTextColor } else { invalidDurationTextColor })
}
val pickedDateTimeBegin = dateTimePicker(event.time, datePickerBegin) { pickedTime: Long ->
durationStart = pickedTime
if (datePickerEnd.visibility == View.GONE) {
durationEnd = pickedTime
}
val pickedDateTimeBegin = dateTimePicker(event.time, datePickerBegin) { time: Long ->
sleepBegin = adjustToMinute(time)
updateFields()
}
val pickedDateTimeEnd = dateTimePicker(event.time + event.quantity, datePickerEnd) { pickedTime: Long ->
durationEnd = pickedTime
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) {
dateDelimiter.visibility = View.GONE
datePickerEnd.visibility = View.GONE
@@ -508,35 +511,31 @@ class MainActivity : AppCompatActivity() {
d.setMessage(event.getDialogMessage(this))
}
durationStart = pickedDateTimeBegin.time.time / 1000
durationEnd = pickedDateTimeEnd.time.time / 1000
updateFields()
durationMinus5Button.setOnClickListener {
durationEnd = (durationEnd - 300).coerceAtLeast(durationStart)
sleepEnd = (sleepEnd - 300).coerceAtLeast(sleepBegin)
updateFields()
}
durationPlus5Button.setOnClickListener {
durationEnd = (durationEnd + 300).coerceAtLeast(durationStart)
sleepEnd = (sleepEnd + 300).coerceAtLeast(sleepBegin)
updateFields()
}
durationClearButton.setOnClickListener {
durationEnd = durationStart
durationAsleepButton.setOnClickListener {
sleepEnd = sleepBegin
updateFields()
}
durationNowButton.setOnClickListener {
durationEnd = System.currentTimeMillis() / 1000
val now = System.currentTimeMillis() / 1000
sleepEnd = adjustToMinute(now)
updateFields()
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
if (isValidTime(durationStart) && isValidTime(durationEnd) && isValidTimeSpan(durationStart, durationEnd)) {
event.time = durationStart
event.quantity = (durationEnd - durationStart).toInt()
if (isValidTime(sleepBegin) && isValidTime(sleepEnd) && isValidTimeSpan(sleepBegin, sleepEnd)) {
event.time = sleepBegin
event.quantity = (sleepEnd - sleepBegin).toInt()
onPositive()
} else {
Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show()
@@ -882,7 +881,7 @@ class MainActivity : AppCompatActivity() {
LunaEvent.Type.PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.Type.TEMPERATURE -> askTemperatureValue(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 -> {
Log.w(TAG, "Unexpected type: ${event.type}")
}
@@ -1309,7 +1308,7 @@ class MainActivity : AppCompatActivity() {
LunaEvent.Type.FOOD -> addNoteEvent(event)
LunaEvent.Type.PUKE -> addAmountEvent(event)
LunaEvent.Type.BATH -> addPlainEvent(event)
LunaEvent.Type.SLEEP -> addDurationEvent(event)
LunaEvent.Type.SLEEP -> addSleepEvent(event)
LunaEvent.Type.UNKNOWN -> {} // ignore
}
}

View File

@@ -14,7 +14,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.toColorInt
import com.github.mikephil.charting.animation.ChartAnimator
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
@@ -181,11 +180,14 @@ class StatisticsActivity : AppCompatActivity() {
val values = ArrayList<BarEntry>()
val stack = ArrayList(List(state.endSpan - state.startSpan + 1) { IntArray(24 * 60 * 60 / SLEEP_PATTERN_GRANULARITY) })
Log.d(TAG, "stack.size: ${stack.size}, array.size: ${stack[0].size}, dayCounter.daysWithData.size: ${state.dayCounter.daysWithData.size}")
fun stackValuePattern(index: Int, spanBegin: Long, spanEnd: Long, begin: Long, end: Long) {
val beginDays = unixToDays(begin)
val endDays = unixToDays(end)
var mid = begin
//Log.d(TAG, "stackValuePattern: ${beginDays}..${endDays}")
for (i in beginDays..endDays) {
// i is the days/weeks/months since unix epoch
val dayBegin = daysToUnix(i)
@@ -198,6 +200,7 @@ class StatisticsActivity : AppCompatActivity() {
assert(sleepBegin <= sleepEnd)
val iBegin = (sleepBegin - dayBegin) / SLEEP_PATTERN_GRANULARITY
val iEnd = iBegin + (sleepEnd - sleepBegin) / SLEEP_PATTERN_GRANULARITY
//Log.d(TAG, "index: $index, iBegin: $iBegin, iEnd: $iEnd, dayBegin: ${Date(dayBegin * 1000)}, dayEnd: ${Date(dayEnd * 1000)}, sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}")
for (j in iBegin..<iEnd) {
stack[index][j.toInt()] += 1
}
@@ -215,14 +218,17 @@ class StatisticsActivity : AppCompatActivity() {
val endIndex = unixToSpan(endUnix)
var mid = startUnix
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
for (i in begIndex..endIndex) {
// i is the days/weeks/months since unix epoch
val spanBegin = spanToUnix(i)
val spanEnd = spanToUnix(i + 1)
//Log.d(TAG, "mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, beginUnix: ${Date(startUnix * 1000)} endUnix: ${Date(endUnix * 1000)}")
val sleepBegin = max(mid, spanBegin)
val sleepEnd = min(endUnix, spanEnd)
val index = i - state.startSpan
val duration = sleepEnd - sleepBegin
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${DateUtils.formatTimeDuration(this, duration)}")
state.dayCounter.setDaysWithData(sleepBegin, sleepEnd)
stackValuePattern(index, spanBegin, spanEnd, sleepBegin, sleepEnd)
@@ -249,6 +255,7 @@ class StatisticsActivity : AppCompatActivity() {
for ((index, dayArray) in stack.withIndex()) {
val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
//Log.d(TAG, "index: $index: daysWithData: $daysWithData, dayArray: ${dayArray.joinToString { it.toString() }}")
val vals = ArrayList<Float>()
var prevIndex = -1 // time slot index
@@ -270,10 +277,14 @@ class StatisticsActivity : AppCompatActivity() {
allColors.add(mapColor(prevValue, daysWithData))
}
assert(values.size == index)
values.add(BarEntry(values.size.toFloat(), vals.toFloatArray()))
//Log.d(TAG, "Range $index, vals: ${vals.joinToString { it.toInt().toString() }}") //, allColors: ${allColors.joinToString { it.toString() }}")
//Log.d(TAG, "index: ${index.toFloat()}")
values.add(BarEntry(index.toFloat(), vals.toFloatArray()))
}
//Log.d(TAG, "daysWithData: ${state.dayCounter.daysWithData.joinToString()}")
barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
override fun onValueSelected(e: Entry?, h: Highlight?) {
if (e == null || h == null) {
@@ -297,6 +308,7 @@ class StatisticsActivity : AppCompatActivity() {
val dayStartUnix = daysToUnix(unixToDays(state.startUnix) + index)
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, x: ${e.x.toInt()}, dayStartUnix: ${Date(dayStartUnix * 1000)}")
val startSeconds =
SLEEP_PATTERN_GRANULARITY * value.yVals.sliceArray(0..<h.stackIndex)
.fold(0) { acc, y -> acc + y.toInt() }
@@ -338,21 +350,28 @@ class StatisticsActivity : AppCompatActivity() {
})
val set1 = BarDataSet(values, "")
val data = BarData(set1)
set1.colors = allColors
set1.setDrawValues(false) // usually too many values
set1.isHighlightEnabled = true
set1.setDrawIcons(false)
val data = BarData(set1)
data.setValueTextSize(12f)
//Log.d(TAG, "showSleepPatternBarGraphSlotted; barChart.xAxis.labelCount: ${barChart.xAxis.labelCount}, barChart.visibleXRange: ${barChart.visibleXRange}, barChart.xAxis.isCenterAxisLabelsEnabled: ${barChart.xAxis.isCenterAxisLabelsEnabled}, barChart.isAutoScaleMinMaxEnabled: ${barChart.isAutoScaleMinMaxEnabled}, barChart.isScaleXEnabled: ${barChart.isScaleXEnabled}, barChart.isScaleYEnabled: ${barChart.isScaleYEnabled}")
val valueCount = min(values.size, 24)
data.setValueTextSize(12f)
barChart.setData(data)
// does not work quite right yet
//Log.d(TAG, "xChartMax: ${barChart.xChartMax}")
//barChart.centerViewTo(barChart.xChartMax, 0F, YAxis.AxisDependency.LEFT)
barChart.legend.isEnabled = false
val valueCount = min(values.size, 24)
barChart.setVisibleXRangeMaximum(valueCount.toFloat())
barChart.xAxis.setLabelCount(valueCount)
barChart.xAxis.setCenterAxisLabels(false)
barChart.moveViewTo(data.getEntryCount().toFloat(), 0f, YAxis.AxisDependency.LEFT)
barChart.invalidate()
}
@@ -383,8 +402,7 @@ class StatisticsActivity : AppCompatActivity() {
// update value
val newYVals = appendToFloatArray(yVals, awakeDuration.toFloat(), sleepDuration.toFloat())
assert(index == x.toInt())
values[index] = BarEntry(index.toFloat(), newYVals)
values[index] = BarEntry(x, newYVals)
}
for (range in ranges) {
@@ -396,14 +414,17 @@ class StatisticsActivity : AppCompatActivity() {
val endIndex = unixToSpan(endUnix)
var mid = startUnix
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
for (i in begIndex..endIndex) {
// i is the days/weeks/months since unix epoch
val spanBegin = spanToUnix(i)
val spanEnd = spanToUnix(i + 1)
//Log.d(TAG, "mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, beginUnix: ${Date(startUnix * 1000)} endUnix: ${Date(endUnix * 1000)}")
val sleepBegin = max(mid, spanBegin)
val sleepEnd = min(endUnix, spanEnd)
val index = i - state.startSpan
val duration = sleepEnd - sleepBegin
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${DateUtils.formatTimeDuration(this, duration)}")
state.dayCounter.setDaysWithData(sleepBegin, sleepEnd)
stackValuePattern(index, spanBegin, spanEnd, sleepBegin, sleepEnd)
@@ -412,11 +433,12 @@ class StatisticsActivity : AppCompatActivity() {
}
}
// awake phase color is transparent
val set1 = BarDataSet(values, "")
val data = BarData(set1)
// awake phase color is transparent
set1.colors = arrayListOf("#00000000".toColorInt(), "#72d7f5".toColorInt())
set1.setDrawValues(false) // usually too many values
set1.setDrawIcons(false)
set1.isHighlightEnabled = true
barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
@@ -456,17 +478,19 @@ class StatisticsActivity : AppCompatActivity() {
override fun onNothingSelected() {}
})
//Log.d(TAG, "showSleepPatternBarGraphDaily: values.size: ${values.size}, barChart.xAxis.labelCount: ${barChart.xAxis.labelCount}")
set1.setDrawIcons(false)
val data = BarData(set1)
data.setValueTextSize(12f)
val valueCount = min(values.size, 24)
barChart.setData(data)
//Log.d(TAG, "showSleepPatternBarGraphDaily: new barChart.xAxis.labelCount: ${barChart.xAxis.labelCount}")
barChart.legend.isEnabled = false
val valueCount = min(values.size, 24)
barChart.setVisibleXRangeMaximum(valueCount.toFloat())
barChart.xAxis.setLabelCount(valueCount)
barChart.xAxis.setCenterAxisLabels(false)
barChart.moveViewTo(data.getEntryCount().toFloat(), 0f, YAxis.AxisDependency.LEFT)
barChart.invalidate()
}
@@ -482,11 +506,13 @@ class StatisticsActivity : AppCompatActivity() {
mValuePaint.setColor(color)
c.drawText(valueText, x.coerceAtLeast(60F), y, mValuePaint)
}
}
fun showSleepBarGraph(state: GraphState) {
val ranges = toSleepRanges(state.events)
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) {
// a sleep event can span to another day
@@ -497,14 +523,18 @@ class StatisticsActivity : AppCompatActivity() {
val endIndex = unixToSpan(endUnix)
var mid = startUnix
//Log.d(TAG, "beginIndex: $begIndex, endIndex: $endIndex, startUnix: ${Date(startUnix * 1000)} ($startUnix), endUnix: ${Date(endUnix * 1000)} ($endUnix)")
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
for (i in begIndex..endIndex) {
// i is the days/weeks/months since unix epoch
val spanBegin = spanToUnix(i)
val spanEnd = spanToUnix(i + 1)
//Log.d(TAG, "i: $i, mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, beginUnix: ${Date(startUnix * 1000)} endUnix: ${Date(endUnix * 1000)}")
val sleepBegin = max(mid, spanBegin)
val sleepEnd = min(endUnix, spanEnd)
val index = i - state.startSpan
val duration = sleepEnd - sleepBegin
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${DateUtils.formatTimeDuration(this, duration)}")
state.dayCounter.setDaysWithData(sleepBegin, sleepEnd)
@@ -531,15 +561,12 @@ class StatisticsActivity : AppCompatActivity() {
}
val set1 = BarDataSet(values, "")
val data = BarData(set1)
set1.setDrawValues(true)
set1.setDrawIcons(false)
set1.isHighlightEnabled = false
val data = BarData(set1)
data.setValueTextSize(12f)
data.setValueFormatter(object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
val prefix = if (timeRangeSelection == TimeRange.DAY) { "" } else { "" }
return when (graphTypeSelection) {
GraphType.SLEEP_EVENTS -> {
@@ -556,14 +583,18 @@ class StatisticsActivity : AppCompatActivity() {
}
})
val valueCount = min(values.size, 24)
barChart.renderer = CustomHorizontalBarChartRenderer(barChart, barChart.animator, barChart.viewPortHandler)
set1.setDrawIcons(false)
data.setValueTextSize(12f)
barChart.setData(data)
barChart.setRenderer(CustomHorizontalBarChartRenderer(barChart, barChart.getAnimator(), barChart.getViewPortHandler()))
barChart.legend.isEnabled = false
val valueCount = min(values.size, 24)
barChart.setVisibleXRangeMaximum(valueCount.toFloat())
barChart.xAxis.setLabelCount(valueCount)
barChart.xAxis.setCenterAxisLabels(false)
barChart.moveViewTo(data.getEntryCount().toFloat(), 0f, YAxis.AxisDependency.LEFT)
barChart.invalidate()
}
@@ -587,6 +618,7 @@ class StatisticsActivity : AppCompatActivity() {
for (index in values.indices) {
val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
//Log.d(TAG, "values[$index].y: ${values[index].y}, daysWithData: $daysWithData")
if (daysWithData == 0) {
assert(values[index].y == 0F)
} else {
@@ -595,14 +627,15 @@ class StatisticsActivity : AppCompatActivity() {
}
val set1 = BarDataSet(values, "")
set1.setDrawValues(true)
set1.isHighlightEnabled = false
val data = BarData(set1)
data.setValueTextSize(12f)
data.setValueFormatter(object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
val prefix = if (timeRangeSelection == TimeRange.DAY) { "" } else { "" }
//Log.d(TAG, "getFormattedValue ${dataTypeSelectionValue} ${eventTypeSelectionValue}")
return when (graphTypeSelection) {
GraphType.BOTTLE_EVENTS -> {
prefix + value.toInt().toString()
@@ -618,13 +651,15 @@ class StatisticsActivity : AppCompatActivity() {
}
})
val valueCount = min(values.size, 24)
barChart.renderer = CustomHorizontalBarChartRenderer(barChart, barChart.animator, barChart.viewPortHandler)
data.setValueTextSize(12f)
barChart.setData(data)
barChart.setRenderer(CustomHorizontalBarChartRenderer(barChart, barChart.getAnimator(), barChart.getViewPortHandler()))
val valueCount = min(values.size, 24)
barChart.setVisibleXRangeMaximum(valueCount.toFloat())
barChart.xAxis.setLabelCount(valueCount)
barChart.xAxis.setCenterAxisLabels(false)
barChart.moveViewTo(data.getEntryCount().toFloat(), 0f, YAxis.AxisDependency.LEFT)
barChart.invalidate()
}
@@ -636,8 +671,10 @@ class StatisticsActivity : AppCompatActivity() {
fun countDaysWithData(beginUnix: Long, endUnix: Long): Int {
val beginDays = unixToDays(beginUnix)
val endDays = unixToDays(endUnix)
//Log.d(TAG, "countDaysWithData: beginDays: $beginDays, endDays: $endDays, ${Date(beginUnix * 1000)} - ${Date(endUnix * 1000)}")
var count = 0
for (i in (beginDays - startDays)..<(endDays - startDays)) {
//Log.d(TAG, "countDaysWithData: i: $i, size: ${daysWithData.size}")
count += if (daysWithData[i]) { 1 } else { 0 }
}
return count
@@ -657,12 +694,14 @@ class StatisticsActivity : AppCompatActivity() {
data class GraphState(val events: List<LunaEvent>, val dayCounter: DayCounter, val startUnix: Long, val endUnix: Long, val startSpan: Int, val endSpan: Int)
fun showGraph() {
//Log.d(TAG, "showGraph: graphTypeSelection: $graphTypeSelection, timeRangeSelection: $timeRangeSelection")
barChart.fitScreen()
barChart.data?.clearValues()
barChart.xAxis.valueFormatter = null
barChart.notifyDataSetChanged()
barChart.clear()
barChart.invalidate()
//Log.d(TAG, "resetBarChart; barChart.xAxis.labelCount: ${barChart.xAxis.labelCount}, barChart.visibleXRange: ${barChart.visibleXRange}, barChart.xAxis.isCenterAxisLabelsEnabled: ${barChart.xAxis.isCenterAxisLabelsEnabled}, barChart.isAutoScaleMinMaxEnabled: ${barChart.isAutoScaleMinMaxEnabled}, barChart.isScaleXEnabled: ${barChart.isScaleXEnabled}, barChart.isScaleYEnabled: ${barChart.isScaleYEnabled}")
val type = when (graphTypeSelection) {
GraphType.BOTTLE_EVENTS,
@@ -672,6 +711,7 @@ class StatisticsActivity : AppCompatActivity() {
GraphType.SLEEP_PATTERN -> LunaEvent.Type.SLEEP
}
val events = MainActivity.allEvents.filter { it.type == type }.sortedBy { it.time }
if (events.isEmpty()) {
@@ -696,6 +736,8 @@ class StatisticsActivity : AppCompatActivity() {
val endDays = unixToDays(spanToUnix(endSpan + 1)) // until end of next span
val dayCounter = DayCounter(startDays, endDays)
//Log.d(TAG, "startUnix: ${Date(1000 * startUnix)}, endUnix: ${Date(1000 * endUnix)}, startSpan: ${Date(1000 * spanToUnix(startSpan))}, endSpan: ${Date(1000 * spanToUnix(endSpan))}, startDaysUnix: ${Date(daysToUnix(startDays) * 1000)}. endDaysUnix: ${Date(daysToUnix(endDays) * 1000)}")
// print dates
barChart.xAxis.valueFormatter = object: ValueFormatter() {
override fun getFormattedValue(value: Float): String {
@@ -709,6 +751,8 @@ class StatisticsActivity : AppCompatActivity() {
val week = dateTime.get(Calendar.WEEK_OF_YEAR)
val day = dateTime.get(Calendar.DAY_OF_MONTH)
//Log.d(TAG, "index: $index, unixSeconds: ${Date(1000 * unixSeconds)}, day: $day, week: $week, month: $month, year: $year")
// Adjust years if the first week of a year starts in the previous year.
val years = if (month == 12 && week == 1) {
year + 1
@@ -760,6 +804,8 @@ class StatisticsActivity : AppCompatActivity() {
val spinnerAdapter =
ArrayAdapter.createFromResource(this, arrayId, R.layout.statistics_spinner_item)
//Log.d(TAG, "spinner ${arrayValues.indexOf(currentValue)} (${arrayValues.joinToString { it }})")
spinner.adapter = spinnerAdapter
spinner.setSelection(arrayValues.indexOf(currentValue))
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {

View File

@@ -174,7 +174,7 @@ class LunaEvent: Comparable<LunaEvent> {
Type.DIAPERCHANGE_PEE,
Type.PUKE -> R.string.log_amount_dialog_description
Type.WEIGHT -> R.string.log_weight_dialog_description
Type.SLEEP -> R.string.log_duration_dialog_description
Type.SLEEP -> R.string.log_sleep_dialog_description
else -> R.string.log_unknown_dialog_description
}
)

View File

@@ -9,15 +9,10 @@ import java.util.Date
class DateUtils {
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.
*/
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
val years = (seconds / (365 * 24 * 60 * 60F)).toLong()

View File

@@ -18,7 +18,6 @@
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/dialog_number_picker_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"

View File

@@ -39,11 +39,11 @@
android:text="@string/dialog_duration_button_now"/>
<Button
android:id="@+id/dialog_date_duration_clear"
android:id="@+id/dialog_date_duration_asleep"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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"

View File

@@ -130,7 +130,7 @@
<string name="log_temperature_dialog_description">Select the temperature:</string>
<string name="log_unknown_dialog_description"></string>
<string name="log_weight_dialog_description">Insert the weight:</string>
<string name="log_duration_dialog_description">Set duration:</string>
<string name="log_sleep_dialog_description">Set sleep duration:</string>
<string name="measurement_unit_liquid_base_metric" translatable="false">ml</string>
<string name="measurement_unit_weight_base_metric" translatable="false">g</string>
@@ -160,9 +160,9 @@
<string name="dialog_event_detail_notes">Notes</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_now">Now</string>
<string name="dialog_duration_button_now">now</string>
<string name="dialog_duration_button_plus_5">+5 min</string>
<string name="dialog_add_logbook_title">Add logbook</string>