16 Commits

Author SHA1 Message Date
0cc9dc53fe 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-01-11 21:55:07 +01:00
77f5ef28b7 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-01-11 21:55:07 +01:00
9c8bf7c761 StatisticsActivity: rework all statistics
Improve the overall code.
2026-01-11 21:55:07 +01:00
2a446ea7d3 NumericUtils: remove possible trailing whitespace 2026-01-11 21:55:07 +01:00
b753703ff3 MainActivity: do not switch logbook on reload 2026-01-11 21:55:07 +01:00
f5bd345e23 LunaEvent: reorganize event text getters
Use method names that better reflect
the use of the returned text.
2026-01-11 21:55:07 +01:00
dc0cd6353c MainAcitivty: add dynamic header setting
The setting allows to build the menu and
popup list to be populated by the frequency
of events that has been created.
This also makes the 'no breastfeeding' setting irrelevant.
2026-01-11 21:55:07 +01:00
7a0343f464 LunaEvent: use enum class for event types
This helps to have compile errors when some
case it not handled while adding a new type.
The enum class can also be interated over
to create a complete drop down list.
2026-01-11 21:55:03 +01:00
389514ec4f MainActivity: increase bottle volume to 340ml
This is the maximum amount found in sold bottles.
2026-01-11 21:53:46 +01:00
7608fc756b gradle: use uniform implementation directive for sardine-android 2026-01-11 21:53:46 +01:00
ced76d449e gradle: avoid inclusion of apk signing blobs
See https://android.izzysoft.de/articles/named/iod-scan-apkchecks?lang=en#blobs
2026-01-11 21:53:46 +01:00
64e4fbbba2 gradle: set compileSDK/targetSdk to 36 2026-01-11 21:53:46 +01:00
98cf9587e8 StatisticsActivity: add statistics for bottle and sleep events 2026-01-11 21:53:46 +01:00
3faaf6d6f0 MainActivity: show save button if any values has changed 2026-01-11 21:53:46 +01:00
86721fbbae MainActivity: use unique templates for notes 2026-01-11 21:53:46 +01:00
155d53a6f0 LunaEvent: add sleep event 2026-01-11 21:53:41 +01:00
3 changed files with 77 additions and 81 deletions

View File

@@ -421,10 +421,10 @@ class MainActivity : AppCompatActivity() {
} }
fun addSleepEvent(event: LunaEvent) { fun addSleepEvent(event: LunaEvent) {
askSleepValue(event) { saveEvent(event) } askSleepValue(event, true) { saveEvent(event) }
} }
fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) { fun askSleepValue(event: LunaEvent, hideDurationButtons: 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))
@@ -432,8 +432,9 @@ class MainActivity : AppCompatActivity() {
d.setView(dialogView) d.setView(dialogView)
val durationTextView = dialogView.findViewById<TextView>(R.id.dialog_date_duration) val durationTextView = dialogView.findViewById<TextView>(R.id.dialog_date_duration)
val durationNowButton = dialogView.findViewById<Button>(R.id.dialog_date_duration_now)
val datePicker = dialogView.findViewById<TextView>(R.id.dialog_date_picker) val datePicker = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val durationButtons = dialogView.findViewById<LinearLayout>(R.id.duration_buttons)
val durationNowButton = dialogView.findViewById<Button>(R.id.dialog_date_duration_now)
val durationMinus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_minus5) val durationMinus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_minus5)
val durationPlus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_plus5) val durationPlus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_plus5)
@@ -465,25 +466,31 @@ class MainActivity : AppCompatActivity() {
onDateChange(pickedDateTime.time.time / 1000) onDateChange(pickedDateTime.time.time / 1000)
fun adjust(minutes: Int) { if (hideDurationButtons) {
duration += minutes * 60 durationButtons.visibility = View.GONE
if (duration < 0) { } else {
duration = 0 durationButtons.visibility = View.VISIBLE
}
onDateChange(pickedDateTime.time.time / 1000)
}
durationMinus5Button.setOnClickListener { adjust(-5) } fun adjust(minutes: Int) {
durationPlus5Button.setOnClickListener { adjust(5) } duration += minutes * 60
if (duration < 0) {
durationNowButton.setOnClickListener { duration = 0
val now = System.currentTimeMillis() / 1000 }
val start = pickedDateTime.time.time / 1000
if (now > start) {
duration = (now - start).toInt()
duration -= duration % 60 // prevent printing of seconds
onDateChange(pickedDateTime.time.time / 1000) onDateChange(pickedDateTime.time.time / 1000)
} }
durationMinus5Button.setOnClickListener { adjust(-5) }
durationPlus5Button.setOnClickListener { adjust(5) }
durationNowButton.setOnClickListener {
val now = System.currentTimeMillis() / 1000
val start = pickedDateTime.time.time / 1000
if (now > start) {
duration = (now - start).toInt()
duration -= duration % 60 // prevent printing of seconds
onDateChange(pickedDateTime.time.time / 1000)
}
}
} }
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
@@ -835,7 +842,7 @@ class MainActivity : AppCompatActivity() {
LunaEvent.Type.PUKE -> askAmountValue(event, false, updateValues) LunaEvent.Type.PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.Type.TEMPERATURE -> askTemperatureValue(event, false, updateValues) LunaEvent.Type.TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues) LunaEvent.Type.NOTE -> askNotes(event, false, updateValues)
LunaEvent.Type.SLEEP -> askSleepValue(event, updateValues) LunaEvent.Type.SLEEP -> askSleepValue(event, false, updateValues)
else -> { else -> {
Log.w(TAG, "Unexpected type: ${event.type}") Log.w(TAG, "Unexpected type: ${event.type}")
} }

View File

@@ -247,7 +247,7 @@ class StatisticsActivity : AppCompatActivity() {
return ranges return ranges
} }
fun showSleepPatternBarGraph(state: GraphState) { fun showSleepPatternBarGraphSlotted(state: GraphState) {
val ranges = toSleepRanges(state.events) val ranges = toSleepRanges(state.events)
val values = ArrayList<BarEntry>() val values = ArrayList<BarEntry>()
val stack = ArrayList(List(state.endSpan - state.startSpan + 1) { IntArray(24 * 60 * 60 / SLEEP_PATTERN_GRANULARITY) }) val stack = ArrayList(List(state.endSpan - state.startSpan + 1) { IntArray(24 * 60 * 60 / SLEEP_PATTERN_GRANULARITY) })
@@ -311,11 +311,13 @@ class StatisticsActivity : AppCompatActivity() {
} }
fun mapColor(occurrences: Int, maxOccurrences: Int): Int { fun mapColor(occurrences: Int, maxOccurrences: Int): Int {
// occurences: number of reported sleeps in a specific time slice //Log.d(TAG, "$occurrences <= $maxOccurrences")
// occurrences: number of reported sleeps in a specific time slice
// maxOccurrences: maximum number of days with data that can contribute to maxOccurrences // maxOccurrences: maximum number of days with data that can contribute to maxOccurrences
assert(maxOccurrences > 0) assert(maxOccurrences > 0)
assert(occurrences <= maxOccurrences) assert(occurrences <= maxOccurrences)
// map to color // map to color
val q = occurrences.toFloat() / maxOccurrences.toFloat() val q = occurrences.toFloat() / maxOccurrences.toFloat()
val i = q * (SLEEP_PATTERN_COLORS.size - 1).toFloat() val i = q * (SLEEP_PATTERN_COLORS.size - 1).toFloat()
@@ -328,7 +330,7 @@ class StatisticsActivity : AppCompatActivity() {
for ((index, dayArray) in stack.withIndex()) { for ((index, dayArray) in stack.withIndex()) {
val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1)) val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
//Log.d(TAG, "index: $index: dayArray: ${dayArray.joinToString { it.toString() }}") //Log.d(TAG, "index: $index: daysWithData: $daysWithData, dayArray: ${dayArray.joinToString { it.toString() }}")
val vals = ArrayList<Float>() val vals = ArrayList<Float>()
var prevIndex = -1 // time slice index var prevIndex = -1 // time slice index
@@ -339,7 +341,7 @@ class StatisticsActivity : AppCompatActivity() {
prevValue = v prevValue = v
} else if (prevValue != v) { } else if (prevValue != v) {
vals.add((i - prevIndex).toFloat()) vals.add((i - prevIndex).toFloat())
allColors.add(mapColor(prevValue, daysWithData)) allColors.add(mapColor(prevValue.coerceAtMost(daysWithData), daysWithData))
prevIndex = i prevIndex = i
prevValue = v prevValue = v
} }
@@ -355,7 +357,7 @@ class StatisticsActivity : AppCompatActivity() {
values.add(BarEntry(index.toFloat(), vals.toFloatArray())) values.add(BarEntry(index.toFloat(), vals.toFloatArray()))
} }
Log.d(TAG, "daysWithData: ${state.dayCounter.daysWithData.joinToString()}") //Log.d(TAG, "daysWithData: ${state.dayCounter.daysWithData.joinToString()}")
barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
override fun onValueSelected(e: Entry?, h: Highlight?) { override fun onValueSelected(e: Entry?, h: Highlight?) {
@@ -373,7 +375,7 @@ class StatisticsActivity : AppCompatActivity() {
return return
} }
if ((lastToastShown + 3500) > System.currentTimeMillis()) { if ((lastToastShown + TOAST_FREQUENCY_MS) > System.currentTimeMillis()) {
// only show one Toast message after another // only show one Toast message after another
return return
} }
@@ -441,6 +443,8 @@ class StatisticsActivity : AppCompatActivity() {
barChart.invalidate() barChart.invalidate()
} }
// Sleep pattern bars that do not use time slots.
// This is useful/nicer for bars that only represent data of a singlur days.
fun showSleepPatternBarGraphDaily(state: GraphState) { fun showSleepPatternBarGraphDaily(state: GraphState) {
val ranges = toSleepRanges(state.events) val ranges = toSleepRanges(state.events)
val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), FloatArray(0)) }) val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), FloatArray(0)) })
@@ -523,7 +527,7 @@ class StatisticsActivity : AppCompatActivity() {
return return
} }
if ((lastToastShown + 3500) > System.currentTimeMillis()) { if ((lastToastShown + TOAST_FREQUENCY_MS) > System.currentTimeMillis()) {
// only show one Toast message after another // only show one Toast message after another
return return
} }
@@ -558,7 +562,7 @@ class StatisticsActivity : 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)}") //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
@@ -569,12 +573,13 @@ class StatisticsActivity : AppCompatActivity() {
val endIndex = unixToSpan(endUnix) val endIndex = unixToSpan(endUnix)
var mid = startUnix 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})") //Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
for (i in begIndex..endIndex) { for (i in begIndex..endIndex) {
// i is the days/weeks/months since unix epoch // i is the days/weeks/months since unix epoch
val spanBegin = spanToUnix(i) val spanBegin = spanToUnix(i)
val spanEnd = spanToUnix(i + 1) 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)}") //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 sleepBegin = max(mid, spanBegin)
val sleepEnd = min(endUnix, spanEnd) val sleepEnd = min(endUnix, spanEnd)
val index = i - state.startSpan val index = i - state.startSpan
@@ -729,8 +734,10 @@ class StatisticsActivity : AppCompatActivity() {
fun countDaysWithData(beginUnix: Long, endUnix: Long): Int { fun countDaysWithData(beginUnix: Long, endUnix: Long): Int {
val beginDays = unixToDays(beginUnix) val beginDays = unixToDays(beginUnix)
val endDays = unixToDays(endUnix) val endDays = unixToDays(endUnix)
//Log.d(TAG, "countDaysWithData: beginDays: $beginDays, endDays: $endDays, ${Date(beginUnix * 1000)} - ${Date(endUnix * 1000)}")
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
@@ -750,7 +757,7 @@ 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) data class GraphState(val events: List<LunaEvent>, val dayCounter: DayCounter, val startUnix: Long, val endUnix: Long, val startSpan: Int, val endSpan: Int)
// wrapper for comon graph setup // wrapper for comon graph setup
fun prepareGraph(type: LunaEvent.Type, cb: (GraphState) -> Unit) { fun prepareGraph(type: LunaEvent.Type, callback: (GraphState) -> Unit) {
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()) {
@@ -795,20 +802,27 @@ class StatisticsActivity : AppCompatActivity() {
} }
} }
Log.d(TAG, "startDaysUnix: ${Date(daysToUnix(startDays) * 1000)}. endDaysUnix: ${Date(daysToUnix(endDays) * 1000)}") //Log.d(TAG, "startDaysUnix: ${Date(daysToUnix(startDays) * 1000)}. endDaysUnix: ${Date(daysToUnix(endDays) * 1000)}")
cb(GraphState(events, dayCounter, startUnix, endUnix, startSpan, endSpan)) callback(GraphState(events, dayCounter, startUnix, endUnix, startSpan, endSpan))
} }
fun showGraph() { fun showGraph() {
Log.d(TAG, "showGraph: graphTypeSelection: $graphTypeSelection, timeRangeSelection: $timeRangeSelection") //Log.d(TAG, "showGraph: graphTypeSelection: $graphTypeSelection, timeRangeSelection: $timeRangeSelection")
when (graphTypeSelection) { when (graphTypeSelection) {
GraphType.BOTTLE_EVENTS, GraphType.BOTTLE_EVENTS,
GraphType.BOTTLE_SUM -> prepareGraph(LunaEvent.Type.BABY_BOTTLE) { state -> showBottleBarGraph(state) } GraphType.BOTTLE_SUM -> prepareGraph(LunaEvent.Type.BABY_BOTTLE) { state -> showBottleBarGraph(state) }
GraphType.SLEEP_EVENTS, GraphType.SLEEP_EVENTS,
GraphType.SLEEP_SUM -> prepareGraph(LunaEvent.Type.SLEEP) { state -> showSleepBarGraph(state) } GraphType.SLEEP_SUM -> prepareGraph(LunaEvent.Type.SLEEP) { state -> showSleepBarGraph(state) }
GraphType.SLEEP_PATTERN -> prepareGraph(LunaEvent.Type.SLEEP) { state -> showSleepPatternBarGraph(state) } GraphType.SLEEP_PATTERN -> prepareGraph(LunaEvent.Type.SLEEP) { state ->
if (timeRangeSelection == TimeRange.DAY) {
// specialized pattern bar for daily view
showSleepPatternBarGraphDaily(state)
} else {
showSleepPatternBarGraphSlotted(state)
}
}
GraphType.MEDICINE_EVENTS -> prepareGraph(LunaEvent.Type.MEDICINE) { state -> showMedicineBarGraph(state) } GraphType.MEDICINE_EVENTS -> prepareGraph(LunaEvent.Type.MEDICINE) { state -> showMedicineBarGraph(state) }
} }
} }
@@ -858,7 +872,10 @@ class StatisticsActivity : AppCompatActivity() {
const val TAG = "StatisticsActivity" const val TAG = "StatisticsActivity"
// 15 min steps // 15 min steps
val SLEEP_PATTERN_GRANULARITY = 15 * 60 const val SLEEP_PATTERN_GRANULARITY = 15 * 60
// Time between toast messages (to prevent jams)
const val TOAST_FREQUENCY_MS = 3500
// color gradient // color gradient
val SLEEP_PATTERN_COLORS = arrayOf( val SLEEP_PATTERN_COLORS = arrayOf(
@@ -867,20 +884,22 @@ class StatisticsActivity : AppCompatActivity() {
"#77B1BF".toColorInt(), "#66A7B7".toColorInt(), "#559DAF".toColorInt(), "#4493A7".toColorInt(), "#77B1BF".toColorInt(), "#66A7B7".toColorInt(), "#559DAF".toColorInt(), "#4493A7".toColorInt(),
"#33899F".toColorInt(), "#228097".toColorInt(), "#11768F".toColorInt(), "#006C87".toColorInt() "#33899F".toColorInt(), "#228097".toColorInt(), "#11768F".toColorInt(), "#006C87".toColorInt()
) )
private val dateTime = Calendar.getInstance() // scratch pad
var graphTypeSelection = GraphType.SLEEP_SUM var graphTypeSelection = GraphType.SLEEP_SUM
var timeRangeSelection = TimeRange.DAY var timeRangeSelection = TimeRange.DAY
private val dateTime = Calendar.getInstance() // scratch pad
// convert month to seconds since epoch
fun unixToMonths(seconds: Long): Int { fun unixToMonths(seconds: Long): Int {
//val dateTime = Calendar.getInstance()
dateTime.time = Date(seconds * 1000) dateTime.time = Date(seconds * 1000)
val years = dateTime.get(Calendar.YEAR) val years = dateTime.get(Calendar.YEAR)
val months = dateTime.get(Calendar.MONTH) val months = dateTime.get(Calendar.MONTH)
return 12 * years + months return 12 * years + months
} }
// convert month to seconds since epoch
fun monthsToUnix(months: Int): Long { fun monthsToUnix(months: Int): Long {
//val dateTime = Calendar.getInstance()
dateTime.time = Date(0) dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, months / 12) dateTime.set(Calendar.YEAR, months / 12)
dateTime.set(Calendar.MONTH, months % 12) dateTime.set(Calendar.MONTH, months % 12)
@@ -890,18 +909,24 @@ class StatisticsActivity : AppCompatActivity() {
return dateTime.time.time / 1000 return dateTime.time.time / 1000
} }
// convert seconds to weeks since epoch
fun unixToWeeks(seconds: Long): Int { fun unixToWeeks(seconds: Long): Int {
//val dateTime = Calendar.getInstance()
dateTime.time = Date(seconds * 1000) dateTime.time = Date(seconds * 1000)
val years = dateTime.get(Calendar.YEAR) val years = dateTime.get(Calendar.YEAR) - 1970
val weeks = dateTime.get(Calendar.WEEK_OF_YEAR) val weeks = dateTime.get(Calendar.WEEK_OF_YEAR)
val month = dateTime.get(Calendar.MONTH)
// dirty hack to get monotone number of weeks
if (month == 11 && weeks == 1) {
return 52 * (years + 1) + weeks
}
return 52 * years + weeks return 52 * years + weeks
} }
// convert weeks to seconds since epoch
fun weeksToUnix(weeks: Int): Long { fun weeksToUnix(weeks: Int): Long {
//val dateTime = Calendar.getInstance()
dateTime.time = Date(0) dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, weeks / 52) dateTime.set(Calendar.YEAR, 1970 + weeks / 52)
dateTime.set(Calendar.WEEK_OF_YEAR, weeks % 52) dateTime.set(Calendar.WEEK_OF_YEAR, weeks % 52)
dateTime.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) dateTime.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
dateTime.set(Calendar.HOUR, 0) dateTime.set(Calendar.HOUR, 0)
@@ -910,17 +935,16 @@ class StatisticsActivity : AppCompatActivity() {
return dateTime.time.time / 1000 return dateTime.time.time / 1000
} }
// convert seconds to days since epoch
fun unixToDays(seconds: Long): Int { fun unixToDays(seconds: Long): Int {
//val dateTime = Calendar.getInstance()
dateTime.time = Date(seconds * 1000) dateTime.time = Date(seconds * 1000)
val years = dateTime.get(Calendar.YEAR) val years = dateTime.get(Calendar.YEAR)
val days = dateTime.get(Calendar.DAY_OF_YEAR) val days = dateTime.get(Calendar.DAY_OF_YEAR)
return 365 * years + days return 365 * years + days
} }
// convert from days to Date // convert days to seconds since epoch
fun daysToUnix(days: Int): Long { fun daysToUnix(days: Int): Long {
//val dateTime = Calendar.getInstance()
dateTime.time = Date(0) dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, days / 365) dateTime.set(Calendar.YEAR, days / 365)
dateTime.set(Calendar.DAY_OF_YEAR, days % 365) dateTime.set(Calendar.DAY_OF_YEAR, days % 365)
@@ -944,42 +968,6 @@ class StatisticsActivity : AppCompatActivity() {
return newArray return newArray
} }
/*
fun colorGradient(fromColor: Int, toColor: Int, percent: Int): Int {
assert(percent in 0..100)
val a1 = fromColor.shr(24).and(0xff)
val r1 = fromColor.shr(16).and(0xff)
val g1 = fromColor.shr(8).and(0xff)
val b1 = fromColor.shr(0).and(0xff)
//Log.d(TAG, "${a1.toHexString()} ${r1.toHexString()} ${g1.toHexString()} ${b1.toHexString()}")
val a2 = toColor.shr(24).and(0xff)
val r2 = toColor.shr(16).and(0xff)
val g2 = toColor.shr(8).and(0xff)
val b2 = toColor.shr(0).and(0xff)
//Log.d(TAG, "${a2.toHexString()} ${r2.toHexString()} ${g2.toHexString()} ${b2.toHexString()}")
val pc = (percent.toFloat() / 100F).coerceIn(0F, 1F)
val a = a1 + (pc * abs(a2 - a1)).toInt()
val r = r1 + (pc * abs(r2 - r1)).toInt()
val g = g1 + (pc * abs(g2 - g1)).toInt()
val b = a1 + (pc * abs(b2 - b1)).toInt()
//Log.d(TAG, "${a.toHexString()} ${r.toHexString()} ${g.toHexString()} ${b.toHexString()}")
val Red = r.shl(16).and(0x00FF0000)
val Green = g.shl(8).and(0x0000FF00)
val Blue = b.and(0x000000FF)
val aa = a.shl(24).and(0xFF000000.toInt())
val color = aa.or(Red).or(Green).or(Blue)
return color
//Log.d(TAG, "c: ${c.toHexString()} ${color.toInt().toHexString()}")
//return Color.argb(a, r, g, b)
}
*/
// for debugging // for debugging
fun debugBarValues(values: ArrayList<BarEntry>) { fun debugBarValues(values: ArrayList<BarEntry>) {
for (value in values) { for (value in values) {

View File

@@ -15,6 +15,7 @@
android:text="💤"/> android:text="💤"/>
<LinearLayout <LinearLayout
android:id="@+id/duration_buttons"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"